From f9adcd4545978eab5dab6faad51fe574c9cbd062 Mon Sep 17 00:00:00 2001 From: Paul DeVito Date: Wed, 30 Sep 2026 17:11:27 +0300 Subject: [PATCH] fix(exceptions): stop leaking internal type names in ParsingException ParsingException used to append the wrapped exception's raw Message, which could include .NET type names and Sprache parser internals. It now shows only the line and column from a parse failure, and keeps the original exception as InnerException for full server-side detail. --- QueryKit.UnitTests/FilterParserTests.cs | 16 ++++++++++++++++ QueryKit/Exceptions/ParsingException.cs | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/QueryKit.UnitTests/FilterParserTests.cs b/QueryKit.UnitTests/FilterParserTests.cs index a72fadd..f981682 100644 --- a/QueryKit.UnitTests/FilterParserTests.cs +++ b/QueryKit.UnitTests/FilterParserTests.cs @@ -516,6 +516,22 @@ public void can_throw_error_when_missing_double_quotes_not_recognized() .WithMessage("There was a parsing failure, likely due to an invalid comparison or logical operator. You may also be missing double quotes surrounding a string or guid.*"); } + [Theory] + [InlineData("""Age ^#$%^%@ 25""")] + [InlineData("""Title == "temp" %$@#^ Age == 25""")] + [InlineData("""BirthMonth == invalid""")] + public void parsing_exception_message_has_no_dotnet_type_names(string input) + { + var act = () => FilterParser.ParseFilter(input); + var exception = act.Should().Throw().Which; + + exception.Message.Should().NotContain("System."); + exception.Message.Should().NotContain("Sprache."); + exception.Message.Should().NotMatch("*InvalidOperationException*"); + exception.Message.Should().NotMatch("*ParseException*"); + exception.InnerException.Should().NotBeNull(); + } + [Fact] public void can_throw_error_when_property_has_space() { diff --git a/QueryKit/Exceptions/ParsingException.cs b/QueryKit/Exceptions/ParsingException.cs index d7bee9e..c3dbb3e 100644 --- a/QueryKit/Exceptions/ParsingException.cs +++ b/QueryKit/Exceptions/ParsingException.cs @@ -1,11 +1,25 @@ namespace QueryKit.Exceptions; +using Sprache; + public sealed class ParsingException : QueryKitException { public ParsingException(Exception exception) - : base(@$"There was a parsing failure, likely due to an invalid comparison or logical operator. You may also be missing double quotes surrounding a string or guid. + : base(BuildMessage(exception), exception) + { + } -{exception.Message}", exception) + private static string BuildMessage(Exception exception) { + const string baseMessage = "There was a parsing failure, likely due to an invalid comparison or logical operator. You may also be missing double quotes surrounding a string or guid."; + + // Sprache.Position holds only a line and a column, so it is safe to expose to a client. + // The full exception.Message can name internal parser rules or .NET types, so it stays server-side on InnerException. + if (exception is ParseException parseException) + { + return $"{baseMessage} Failed at {parseException.Position}."; + } + + return baseMessage; } }