1
+ using Xunit ;
2
+
3
+ namespace Serilog . Expressions . Tests ;
4
+
5
+ public class ExpressionValidationTests
6
+ {
7
+ [ Theory ]
8
+ [ InlineData ( "IsMatch(Name, '[invalid')" , "Invalid regular expression" ) ]
9
+ [ InlineData ( "IndexOfMatch(Text, '(?<')" , "Invalid regular expression" ) ]
10
+ [ InlineData ( "IsMatch(Name, '(?P<name>)')" , "Invalid regular expression" ) ]
11
+ [ InlineData ( "IsMatch(Name, '(unclosed')" , "Invalid regular expression" ) ]
12
+ [ InlineData ( "IndexOfMatch(Text, '*invalid')" , "Invalid regular expression" ) ]
13
+ public void InvalidRegularExpressionsAreReportedGracefully ( string expression , string expectedErrorFragment )
14
+ {
15
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
16
+ Assert . False ( result ) ;
17
+ Assert . Contains ( expectedErrorFragment , error ) ;
18
+ Assert . Null ( compiled ) ;
19
+ }
20
+
21
+ [ Theory ]
22
+ [ InlineData ( "UnknownFunction()" , "The function name `UnknownFunction` was not recognized." ) ]
23
+ [ InlineData ( "foo(1, 2, 3)" , "The function name `foo` was not recognized." ) ]
24
+ [ InlineData ( "MyCustomFunc(Name)" , "The function name `MyCustomFunc` was not recognized." ) ]
25
+ [ InlineData ( "notAFunction()" , "The function name `notAFunction` was not recognized." ) ]
26
+ public void UnknownFunctionNamesAreReportedGracefully ( string expression , string expectedError )
27
+ {
28
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
29
+ Assert . False ( result ) ;
30
+ Assert . Equal ( expectedError , error ) ;
31
+ Assert . Null ( compiled ) ;
32
+ }
33
+
34
+ [ Theory ]
35
+ [ InlineData ( "Length(Name) ci" ) ]
36
+ [ InlineData ( "Round(Value, 2) ci" ) ]
37
+ [ InlineData ( "Now() ci" ) ]
38
+ [ InlineData ( "TypeOf(Value) ci" ) ]
39
+ [ InlineData ( "IsDefined(Prop) ci" ) ]
40
+ public void InvalidCiModifierUsageCompilesWithWarning ( string expression )
41
+ {
42
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
43
+ Assert . True ( result , $ "Failed to compile: { error } ") ;
44
+ Assert . NotNull ( compiled ) ;
45
+ Assert . Null ( error ) ;
46
+ }
47
+
48
+ [ Theory ]
49
+ [ InlineData ( "Contains(Name, 'test') ci" ) ]
50
+ [ InlineData ( "StartsWith(Path, '/api') ci" ) ]
51
+ [ InlineData ( "EndsWith(File, '.txt') ci" ) ]
52
+ [ InlineData ( "IsMatch(Email, '@example') ci" ) ]
53
+ [ InlineData ( "IndexOfMatch(Text, 'pattern') ci" ) ]
54
+ [ InlineData ( "IndexOf(Name, 'x') ci" ) ]
55
+ [ InlineData ( "Name = 'test' ci" ) ]
56
+ [ InlineData ( "Name <> 'test' ci" ) ]
57
+ [ InlineData ( "Name like '%test%' ci" ) ]
58
+ public void ValidCiModifierUsageCompiles ( string expression )
59
+ {
60
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
61
+ Assert . True ( result , $ "Failed to compile: { error } ") ;
62
+ Assert . NotNull ( compiled ) ;
63
+ Assert . Null ( error ) ;
64
+ }
65
+
66
+ [ Fact ]
67
+ public void FirstErrorIsReportedInComplexExpressions ( )
68
+ {
69
+ var expression = "UnknownFunc() and Length(Value) > 5" ;
70
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
71
+
72
+ Assert . False ( result ) ;
73
+ Assert . Null ( compiled ) ;
74
+
75
+ // Should report the first error encountered
76
+ Assert . Contains ( "UnknownFunc" , error ) ;
77
+ Assert . NotNull ( error ) ;
78
+ }
79
+
80
+ [ Fact ]
81
+ public void ValidExpressionsStillCompileWithoutErrors ( )
82
+ {
83
+ var validExpressions = new [ ]
84
+ {
85
+ "IsMatch(Name, '^[A-Z]')" ,
86
+ "IndexOfMatch(Text, '\\ d+')" ,
87
+ "Contains(Name, 'test') ci" ,
88
+ "Length(Items) > 5" ,
89
+ "Round(Value, 2)" ,
90
+ "TypeOf(Data) = 'String'" ,
91
+ "Name like '%test%'" ,
92
+ "StartsWith(Path, '/') and EndsWith(Path, '.json')"
93
+ } ;
94
+
95
+ foreach ( var expr in validExpressions )
96
+ {
97
+ var result = SerilogExpression . TryCompile ( expr , out var compiled , out var error ) ;
98
+ Assert . True ( result , $ "Failed to compile: { expr } . Error: { error } ") ;
99
+ Assert . NotNull ( compiled ) ;
100
+ Assert . Null ( error ) ;
101
+ }
102
+ }
103
+
104
+ [ Fact ]
105
+ public void CompileMethodStillThrowsForInvalidExpressions ( )
106
+ {
107
+ // Ensure Compile() method (not TryCompile) maintains throwing behavior for invalid expressions
108
+ Assert . Throws < ArgumentException > ( ( ) =>
109
+ SerilogExpression . Compile ( "UnknownFunction()" ) ) ;
110
+
111
+ Assert . Throws < ArgumentException > ( ( ) =>
112
+ SerilogExpression . Compile ( "IsMatch(Name, '[invalid')" ) ) ;
113
+
114
+ // CI modifier on non-supporting functions compiles with warning
115
+ var compiledWithCi = SerilogExpression . Compile ( "Length(Name) ci" ) ;
116
+ Assert . NotNull ( compiledWithCi ) ;
117
+
118
+ Assert . Throws < ArgumentException > ( ( ) =>
119
+ SerilogExpression . Compile ( "IndexOfMatch(Text, '(?<')" ) ) ;
120
+ }
121
+
122
+ [ Theory ]
123
+ [ InlineData ( "IsMatch(Name, Name)" ) ] // Non-constant pattern
124
+ [ InlineData ( "IndexOfMatch(Text, Value)" ) ] // Non-constant pattern
125
+ public void NonConstantRegexPatternsHandledGracefully ( string expression )
126
+ {
127
+ // These should compile but may log warnings (not errors)
128
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
129
+
130
+ // These compile successfully but return undefined at runtime
131
+ Assert . True ( result ) ;
132
+ Assert . NotNull ( compiled ) ;
133
+ Assert . Null ( error ) ;
134
+ }
135
+
136
+ [ Fact ]
137
+ public void RegexTimeoutIsRespected ( )
138
+ {
139
+ // This regex should compile fine - timeout only matters at runtime
140
+ var expression = @"IsMatch(Text, '(a+)+b')" ;
141
+
142
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
143
+
144
+ Assert . True ( result ) ;
145
+ Assert . NotNull ( compiled ) ;
146
+ Assert . Null ( error ) ;
147
+ }
148
+
149
+ [ Fact ]
150
+ public void ComplexExpressionsReportFirstError ( )
151
+ {
152
+ var expression = "UnknownFunc1() or Length(Value) > 5" ;
153
+ var result = SerilogExpression . TryCompile ( expression , out var compiled , out var error ) ;
154
+
155
+ Assert . False ( result ) ;
156
+ Assert . Null ( compiled ) ;
157
+ Assert . NotNull ( error ) ;
158
+
159
+ // Should report the first error encountered during compilation
160
+ Assert . Contains ( "UnknownFunc1" , error ) ;
161
+ }
162
+
163
+ [ Fact ]
164
+ public void BackwardCompatibilityPreservedForInvalidCiUsage ( )
165
+ {
166
+ // These previously compiled (CI was silently ignored)
167
+ // They should still compile with the new changes
168
+ var expressions = new [ ]
169
+ {
170
+ "undefined() ci" ,
171
+ "null = undefined() ci" ,
172
+ "Length(Name) ci" ,
173
+ "Round(Value, 2) ci"
174
+ } ;
175
+
176
+ foreach ( var expr in expressions )
177
+ {
178
+ var result = SerilogExpression . TryCompile ( expr , out var compiled , out var error ) ;
179
+ Assert . True ( result , $ "Breaking change detected: { expr } no longer compiles. Error: { error } ") ;
180
+ Assert . NotNull ( compiled ) ;
181
+ Assert . Null ( error ) ;
182
+ }
183
+ }
184
+ }
0 commit comments