Skip to content

Commit e9d3861

Browse files
authored
Add missing filter operators (#207)
1 parent 8ca8a01 commit e9d3861

7 files changed

Lines changed: 411 additions & 39 deletions

File tree

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,36 @@ or advanced filtering:
125125
}
126126
```
127127

128+
#### Advanced Filter Operators
129+
130+
Each advanced filter requires an `operatorType`, a `key` (the event property to filter on), and either a `value`, `values`, or no value property depending on the operator type. Up to 25 advanced filters can be configured per subscription.
131+
132+
| Operator | Property | Description |
133+
|----------|----------|-------------|
134+
| `NumberGreaterThan` | `value` | Event value must be greater than the specified number |
135+
| `NumberGreaterThanOrEquals` | `value` | Event value must be greater than or equal to the specified number |
136+
| `NumberLessThan` | `value` | Event value must be less than the specified number |
137+
| `NumberLessThanOrEquals` | `value` | Event value must be less than or equal to the specified number |
138+
| `NumberIn` | `values` | Event value must match one of the specified numbers (max 5 values) |
139+
| `NumberNotIn` | `values` | Event value must not match any of the specified numbers (max 5 values) |
140+
| `NumberInRange` | `values` | Event value must be within one of the specified ranges (e.g., `[[0, 10], [20, 30]]`) |
141+
| `NumberNotInRange` | `values` | Event value must not be within any of the specified ranges |
142+
| `BoolEquals` | `value` | Event value must equal the specified boolean |
143+
| `StringContains` | `values` | Event value must contain at least one of the specified strings |
144+
| `StringNotContains` | `values` | Event value must not contain any of the specified strings |
145+
| `StringBeginsWith` | `values` | Event value must begin with at least one of the specified strings |
146+
| `StringNotBeginsWith` | `values` | Event value must not begin with any of the specified strings |
147+
| `StringEndsWith` | `values` | Event value must end with at least one of the specified strings |
148+
| `StringNotEndsWith` | `values` | Event value must not end with any of the specified strings |
149+
| `StringIn` | `values` | Event value must match one of the specified strings (max 5 values) |
150+
| `StringNotIn` | `values` | Event value must not match any of the specified strings (max 5 values) |
151+
| `IsNullOrUndefined` | _(none)_ | Key must be null or not exist |
152+
| `IsNotNull` | _(none)_ | Key must exist and have a non-null value |
153+
154+
**Note:** The `key` property supports nested data properties using dot notation, e.g., `Data.MyProperty` or `Data.Nested.Value`.
155+
156+
**Note:** String comparisons in advanced filters are case-insensitive. "Not" operators (`StringNotIn`, `NumberNotIn`, etc.) return `true` when the key doesn't exist.
157+
128158
**Note:** you can also specify the configuration file to use by setting the `ConfigFile` command line argument, e.g.
129159

130160
```

src/AzureEventGridSimulator.Tests/UnitTests/Filtering/FilterSettingsValidationTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ private static AdvancedFilterSetting GetValidAdvancedFilter()
4343
[InlineData(0)]
4444
[InlineData(1)]
4545
[InlineData(5)]
46+
[InlineData(25)]
4647
public void TestFilterSettingsValidationWithValidNumberOfAdvancedFilterSettings(byte n)
4748
{
4849
Should.NotThrow(() =>
@@ -61,13 +62,14 @@ public void TestFilterSettingsValidationWithValidNumberOfAdvancedFilterSettings(
6162
}
6263

6364
[Fact]
64-
public void TestFilterSettingsValidationWithSixAdvancedFilters()
65+
public void TestFilterSettingsValidationWithTooManyAdvancedFilters()
6566
{
6667
var filterConfig = new FilterSetting
6768
{
6869
AdvancedFilters = new List<AdvancedFilterSetting>(),
6970
};
70-
for (var i = 0; i < 6; i++)
71+
// Azure Event Grid allows up to 25 filters, so 26 should fail
72+
for (var i = 0; i < 26; i++)
7173
{
7274
filterConfig.AdvancedFilters.Add(GetValidAdvancedFilter());
7375
}
@@ -78,7 +80,7 @@ public void TestFilterSettingsValidationWithSixAdvancedFilters()
7880

7981
exception.ParamName.ShouldBe(nameof(filterConfig.AdvancedFilters));
8082
exception.Message.ShouldBe(
81-
"Advanced filtering is limited to five advanced filters per event grid subscription. (Parameter 'AdvancedFilters')"
83+
"Advanced filtering is limited to 25 advanced filters per event grid subscription. (Parameter 'AdvancedFilters')"
8284
);
8385
}
8486
}

src/AzureEventGridSimulator.Tests/UnitTests/Filtering/NegativeFilterTestCaseContainer.cs

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,37 @@ private static AdvancedFilterSetting[] GetNegativeIdFilterConfigurations()
8686
{
8787
Key = "Id",
8888
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
89-
Value = "b",
89+
Values = new[] { "b" },
9090
},
9191
new AdvancedFilterSetting
9292
{
9393
Key = "Id",
9494
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
95-
Value = string.Empty,
95+
Values = new[] { string.Empty },
9696
},
9797
new AdvancedFilterSetting
9898
{
9999
Key = "Id",
100100
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
101-
Value = null,
101+
Values = null,
102+
},
103+
new AdvancedFilterSetting
104+
{
105+
Key = "Id",
106+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
107+
Values = Array.Empty<object>(),
108+
},
109+
new AdvancedFilterSetting
110+
{
111+
Key = "Id",
112+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
113+
Values = new[] { "B" },
102114
},
103115
new AdvancedFilterSetting
104116
{
105117
Key = "Id",
106118
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
107-
Value = "B",
119+
Values = new[] { "nomatch1", "nomatch2" }, // test multiple values - none should match
108120
},
109121
new AdvancedFilterSetting
110122
{
@@ -171,7 +183,7 @@ private static AdvancedFilterSetting[] GetNegativeTopicFilterConfigurations()
171183
{
172184
Key = "Topic",
173185
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
174-
Value = "123",
186+
Values = new[] { "123" },
175187
},
176188
new AdvancedFilterSetting
177189
{
@@ -214,7 +226,7 @@ private static AdvancedFilterSetting[] GetNegativeSubjectFilterConfigurations()
214226
{
215227
Key = "Subject",
216228
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
217-
Value = "sub",
229+
Values = new[] { "sub" },
218230
},
219231
new AdvancedFilterSetting
220232
{
@@ -257,7 +269,7 @@ private static AdvancedFilterSetting[] GetNegativeEventTypeFilterConfigurations(
257269
{
258270
Key = "EventType",
259271
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
260-
Value = "EVENTTYPE",
272+
Values = new[] { "EVENTTYPE" },
261273
},
262274
new AdvancedFilterSetting
263275
{
@@ -294,7 +306,7 @@ private static AdvancedFilterSetting[] GetNegativeDataVersionFilterConfiguration
294306
{
295307
Key = "DataVersion",
296308
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
297-
Value = "7",
309+
Values = new[] { "7" },
298310
},
299311
new AdvancedFilterSetting
300312
{
@@ -552,6 +564,82 @@ private static AdvancedFilterSetting[] GetNegativeEventDataFilterConfigurations(
552564
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberIn,
553565
Values = new object[] { 10, 11, 12 },
554566
},
567+
// New operators: StringNotContains - should NOT match when value DOES contain a filter value
568+
new AdvancedFilterSetting
569+
{
570+
Key = "Data.Name",
571+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotContains,
572+
Values = new[] { "String" }, // "StringValue" contains "String"
573+
},
574+
new AdvancedFilterSetting
575+
{
576+
Key = "Data.Name",
577+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotContains,
578+
Values = new[] { "VALUE" }, // case insensitive, "StringValue" contains "VALUE"
579+
},
580+
// StringNotBeginsWith - should NOT match when value DOES begin with a filter value
581+
new AdvancedFilterSetting
582+
{
583+
Key = "Data.Name",
584+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotBeginsWith,
585+
Values = new[] { "String" }, // "StringValue" begins with "String"
586+
},
587+
new AdvancedFilterSetting
588+
{
589+
Key = "Data.Name",
590+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotBeginsWith,
591+
Values = new[] { "STRING" }, // case insensitive
592+
},
593+
// StringNotEndsWith - should NOT match when value DOES end with a filter value
594+
new AdvancedFilterSetting
595+
{
596+
Key = "Data.Name",
597+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotEndsWith,
598+
Values = new[] { "Value" }, // "StringValue" ends with "Value"
599+
},
600+
new AdvancedFilterSetting
601+
{
602+
Key = "Data.Name",
603+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotEndsWith,
604+
Values = new[] { "VALUE" }, // case insensitive
605+
},
606+
// NumberInRange - should NOT match when value is NOT within any range
607+
new AdvancedFilterSetting
608+
{
609+
Key = "Data.NumberValue",
610+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberInRange,
611+
Values = new object[] { new object[] { 5, 10 } }, // 1 is not within [5, 10]
612+
},
613+
new AdvancedFilterSetting
614+
{
615+
Key = "Data.NumberValue",
616+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberInRange,
617+
Values = new object[] { new object[] { 2, 5 }, new object[] { 10, 20 } }, // 1 is not within either range
618+
},
619+
// NumberNotInRange - should NOT match when value IS within a range
620+
new AdvancedFilterSetting
621+
{
622+
Key = "Data.NumberValue",
623+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberNotInRange,
624+
Values = new object[] { new object[] { 0, 2 } }, // 1 IS within [0, 2]
625+
},
626+
// IsNullOrUndefined - should NOT match when key exists and has a non-null value
627+
new AdvancedFilterSetting
628+
{
629+
Key = "Data.Name",
630+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.IsNullOrUndefined,
631+
},
632+
new AdvancedFilterSetting
633+
{
634+
Key = "Data.NumberValue",
635+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.IsNullOrUndefined,
636+
},
637+
// IsNotNull - should NOT match when key does NOT exist
638+
new AdvancedFilterSetting
639+
{
640+
Key = "Data.NonExistentKey",
641+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.IsNotNull,
642+
},
555643
};
556644
}
557645

src/AzureEventGridSimulator.Tests/UnitTests/Filtering/PositiveFilterTestCaseContainer.cs

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,31 @@ private static AdvancedFilterSetting[] GetPositiveIdFilterConfigurations()
8787
{
8888
Key = "Id",
8989
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
90-
Value = "EventId",
90+
Values = new[] { "EventId" },
91+
},
92+
new AdvancedFilterSetting
93+
{
94+
Key = "Id",
95+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
96+
Values = new[] { "Id" },
9197
},
9298
new AdvancedFilterSetting
9399
{
94100
Key = "Id",
95101
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
96-
Value = "Id",
102+
Values = new[] { "d" },
97103
},
98104
new AdvancedFilterSetting
99105
{
100106
Key = "Id",
101107
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
102-
Value = "d",
108+
Values = new[] { "D" },
103109
},
104110
new AdvancedFilterSetting
105111
{
106112
Key = "Id",
107113
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
108-
Value = "D",
114+
Values = new[] { "nomatch", "Id" }, // test multiple values - should match if any value matches
109115
},
110116
new AdvancedFilterSetting
111117
{
@@ -184,7 +190,7 @@ private static AdvancedFilterSetting[] GetPositiveTopicFilterConfigurations()
184190
{
185191
Key = "Topic",
186192
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
187-
Value = "Ic",
193+
Values = new[] { "Ic" },
188194
},
189195
new AdvancedFilterSetting
190196
{
@@ -227,7 +233,7 @@ private static AdvancedFilterSetting[] GetPositiveSubjectFilterConfigurations()
227233
{
228234
Key = "Subject",
229235
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
230-
Value = "Subject",
236+
Values = new[] { "Subject" },
231237
},
232238
new AdvancedFilterSetting
233239
{
@@ -270,7 +276,7 @@ private static AdvancedFilterSetting[] GetPositiveEventTypeFilterConfigurations(
270276
{
271277
Key = "EventType",
272278
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
273-
Value = "EVENT.TYPE",
279+
Values = new[] { "EVENT.TYPE" },
274280
},
275281
new AdvancedFilterSetting
276282
{
@@ -307,7 +313,7 @@ private static AdvancedFilterSetting[] GetPositiveDataVersionFilterConfiguration
307313
{
308314
Key = "DataVersion",
309315
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringEndsWith,
310-
Value = "0",
316+
Values = new[] { "0" },
311317
},
312318
new AdvancedFilterSetting
313319
{
@@ -508,6 +514,58 @@ private static AdvancedFilterSetting[] GetPositiveEventDataFilterConfigurations(
508514
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberIn,
509515
Values = new object[] { 1 },
510516
},
517+
// New operators: StringNotContains - should match when value does NOT contain any of the filter values
518+
new AdvancedFilterSetting
519+
{
520+
Key = "Data.Name",
521+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotContains,
522+
Values = new[] { "NotFound", "Missing" },
523+
},
524+
// StringNotBeginsWith - should match when value does NOT begin with any of the filter values
525+
new AdvancedFilterSetting
526+
{
527+
Key = "Data.Name",
528+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotBeginsWith,
529+
Values = new[] { "NotString", "Wrong" },
530+
},
531+
// StringNotEndsWith - should match when value does NOT end with any of the filter values
532+
new AdvancedFilterSetting
533+
{
534+
Key = "Data.Name",
535+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.StringNotEndsWith,
536+
Values = new[] { "NotValue", "Wrong" },
537+
},
538+
// NumberInRange - should match when value is within any of the ranges
539+
new AdvancedFilterSetting
540+
{
541+
Key = "Data.NumberValue",
542+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberInRange,
543+
Values = new object[] { new object[] { 0, 2 } }, // 1 is within [0, 2]
544+
},
545+
new AdvancedFilterSetting
546+
{
547+
Key = "Data.NumberValue",
548+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberInRange,
549+
Values = new object[] { new object[] { 0, 0.5 }, new object[] { 0.8, 1.5 } }, // 1 is within [0.8, 1.5]
550+
},
551+
// NumberNotInRange - should match when value is NOT within any of the ranges
552+
new AdvancedFilterSetting
553+
{
554+
Key = "Data.NumberValue",
555+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.NumberNotInRange,
556+
Values = new object[] { new object[] { 5, 10 }, new object[] { 20, 30 } }, // 1 is not within these ranges
557+
},
558+
// IsNotNull - should match when key exists and has a non-null value
559+
new AdvancedFilterSetting
560+
{
561+
Key = "Data.Name",
562+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.IsNotNull,
563+
},
564+
new AdvancedFilterSetting
565+
{
566+
Key = "Data.NumberValue",
567+
OperatorType = AdvancedFilterSetting.AdvancedFilterOperatorType.IsNotNull,
568+
},
511569
};
512570
}
513571
}

0 commit comments

Comments
 (0)