From e04d677a806680ced0af6158ea5ccad90bf626f2 Mon Sep 17 00:00:00 2001 From: Gerard Date: Fri, 22 Aug 2025 23:30:01 +0200 Subject: [PATCH 1/4] Treat empty _in arrays as false, ie. --- src/BccCode.Linq/Server/Filter.cs | 15 ++++++-- tests/BccCode.Linq.Tests/FilterTests.cs | 5 ++- .../Server/FilterToLambdaParserTests.cs | 37 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/BccCode.Linq/Server/Filter.cs b/src/BccCode.Linq/Server/Filter.cs index eb17eb7..09b8b86 100644 --- a/src/BccCode.Linq/Server/Filter.cs +++ b/src/BccCode.Linq/Server/Filter.cs @@ -112,10 +112,12 @@ private void _parse(string json) else if (new[] { "_in", "_nin" }.Contains(key)) { var array = JsonConvert.DeserializeObject(value.ToString() ?? string.Empty); - if (array is null || array.Length == 0) + // Treat empty arrays as a valid value (resulting in a predicate that's always false for _in + // and always true for _nin). Only null is considered invalid. + if (array is null) { throw new ArgumentException( - $"JSON filter rule is invalid. Array under {key} is null or empty."); + $"JSON filter rule is invalid. Array under {key} is null."); } deserializedJson[key] = array; @@ -135,12 +137,19 @@ private void _parse(string json) ? typeof(ValueTuple<,>).MakeGenericType(propertyType, propertyType) : typeof(Tuple<,>).MakeGenericType(propertyType, propertyType); - deserializedJson[key] = Activator.CreateInstance(tupleType, + var tupleInstance = Activator.CreateInstance(tupleType, OperandToExpressionResolver.ConvertValue(propertyInfo?.PropertyType ?? GetFilterType(), array[0].ToString()), OperandToExpressionResolver.ConvertValue(propertyInfo?.PropertyType ?? GetFilterType(), array[1].ToString()) ); + + if (tupleInstance == null) + { + throw new InvalidOperationException($"Failed to create tuple instance for key {key}"); + } + + deserializedJson[key] = tupleInstance; } else if (key.StartsWith("_")) { diff --git a/tests/BccCode.Linq.Tests/FilterTests.cs b/tests/BccCode.Linq.Tests/FilterTests.cs index b727ad8..a957fb3 100644 --- a/tests/BccCode.Linq.Tests/FilterTests.cs +++ b/tests/BccCode.Linq.Tests/FilterTests.cs @@ -77,11 +77,12 @@ public void should_cast_value_to_decimal_list() } [Fact] - public void should_throw_exception_on_empty_list() + public void should_allow_empty_list_in_in_operator() { var json = @"{ ""StringArrayProp"": { ""_in"": [] } }"; - Assert.Throws(() => new Filter(json)); + var ex = Record.Exception(() => new Filter(json)); + Assert.Null(ex); } [Fact] diff --git a/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs b/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs index fcb209f..d0caf32 100644 --- a/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs +++ b/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs @@ -111,4 +111,41 @@ public void should_get_correct_result_when_filtering_nested_object() Assert.Equal(expected.Count, result.Count); } + + [Fact] + public void should_match_using_in_operator() + { + var jsonRule = "{\n \"country\": { \n \"_in\": [\"Poland\", \"Greece\"]\n }\n}"; + + var expected = PeopleList.Where(person => new[] { "Poland", "Greece" }.Contains(person.Country)).ToList(); + var f = new Filter(jsonRule); + var exp = FilterToLambdaParser.Parse(f); + var result = PeopleList.Where(exp.Compile()).ToList(); + + Assert.Equal(expected.Count, result.Count); + } + + [Fact] + public void empty_in_array_should_match_no_items() + { + var jsonRule = "{\n \"country\": { \n \"_in\": []\n }\n}"; + + var f = new Filter(jsonRule); + var exp = FilterToLambdaParser.Parse(f); + var result = PeopleList.Where(exp.Compile()).ToList(); + + Assert.Empty(result); + } + + [Fact] + public void empty_nin_array_should_match_all_items() + { + var jsonRule = "{\n \"country\": { \n \"_nin\": []\n }\n}"; + + var f = new Filter(jsonRule); + var exp = FilterToLambdaParser.Parse(f); + var result = PeopleList.Where(exp.Compile()).ToList(); + + Assert.Equal(PeopleList.Count, result.Count); + } } From c082874ce30492c59700a06bfbf47ada1a128577 Mon Sep 17 00:00:00 2001 From: Gerard Date: Fri, 22 Aug 2025 23:35:10 +0200 Subject: [PATCH 2/4] Remove tuple fix --- src/BccCode.Linq/Server/Filter.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/BccCode.Linq/Server/Filter.cs b/src/BccCode.Linq/Server/Filter.cs index 09b8b86..3c00af7 100644 --- a/src/BccCode.Linq/Server/Filter.cs +++ b/src/BccCode.Linq/Server/Filter.cs @@ -137,19 +137,12 @@ private void _parse(string json) ? typeof(ValueTuple<,>).MakeGenericType(propertyType, propertyType) : typeof(Tuple<,>).MakeGenericType(propertyType, propertyType); - var tupleInstance = Activator.CreateInstance(tupleType, + deserializedJson[key] = Activator.CreateInstance(tupleType, OperandToExpressionResolver.ConvertValue(propertyInfo?.PropertyType ?? GetFilterType(), array[0].ToString()), OperandToExpressionResolver.ConvertValue(propertyInfo?.PropertyType ?? GetFilterType(), array[1].ToString()) ); - - if (tupleInstance == null) - { - throw new InvalidOperationException($"Failed to create tuple instance for key {key}"); - } - - deserializedJson[key] = tupleInstance; } else if (key.StartsWith("_")) { From a647c863abfff5d37ec247e27ebf01a0d4e6db40 Mon Sep 17 00:00:00 2001 From: Gerard Date: Fri, 22 Aug 2025 23:39:41 +0200 Subject: [PATCH 3/4] Cleaner --- .../Server/FilterToLambdaParserTests.cs | 55 +++++-------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs b/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs index d0caf32..25708bc 100644 --- a/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs +++ b/tests/BccCode.Linq.Tests/Server/FilterToLambdaParserTests.cs @@ -44,45 +44,16 @@ public void should_get_correct_result_when_filter_has_one_logical_filter() [Fact] public void should_get_correct_result_when_filter_has_two_logical_filter() { - var jsonRule = "{\r\n" + - " \"_or\": [\r\n" + - " {\r\n" + - " \"age\": {\r\n" + - " \"_gte\": 20" + - "\r\n" + - " }\r\n" + - " },\r\n" + - " {\r\n" + - " \"country\": {" + - "\r\n" + - " \"_eq\": \"P" + - "oland\",\r\n" + - " \"_in\": [\r" + - "\n" + - " \"Greece\"" + - ",\r\n" + - " \"Norway\"" + - "\r\n" + - " ]\r\n" + - " }\r\n" + - " }\r\n" + - " ],\r\n" + - " \"_and\": [\r\n" + - " {\r\n" + - " \"age\": {\r\n" + - " \"_between\"" + - ": [20, 30]\r\n" + - " }\r\n" + - " },\r\n" + - " {\r\n" + - " \"name\": {\r" + - "\n" + - " \"_starts_wi" + - "th\": \"test\"\r\n" + - " }\r\n" + - " }\r\n" + - " ]\r\n" + - "}"; + var jsonRule = @"{ + ""_or"": [ + { ""age"": { ""_gte"": 20 } }, + { ""country"": { ""_eq"": ""Poland"", ""_in"": [ ""Greece"", ""Norway"" ] } } + ], + ""_and"": [ + { ""age"": { ""_between"": [20, 30] } }, + { ""name"": { ""_starts_with"": ""test"" } } + ] + }"; var expected = PeopleList.Where(person => // or @@ -115,7 +86,7 @@ public void should_get_correct_result_when_filtering_nested_object() [Fact] public void should_match_using_in_operator() { - var jsonRule = "{\n \"country\": { \n \"_in\": [\"Poland\", \"Greece\"]\n }\n}"; + var jsonRule = "{ \"country\": { \"_in\": [\"Poland\", \"Greece\"] } }"; var expected = PeopleList.Where(person => new[] { "Poland", "Greece" }.Contains(person.Country)).ToList(); var f = new Filter(jsonRule); @@ -128,7 +99,7 @@ public void should_match_using_in_operator() [Fact] public void empty_in_array_should_match_no_items() { - var jsonRule = "{\n \"country\": { \n \"_in\": []\n }\n}"; + var jsonRule = "{ \"country\": { \"_in\": [] } }"; var f = new Filter(jsonRule); var exp = FilterToLambdaParser.Parse(f); @@ -140,7 +111,7 @@ public void empty_in_array_should_match_no_items() [Fact] public void empty_nin_array_should_match_all_items() { - var jsonRule = "{\n \"country\": { \n \"_nin\": []\n }\n}"; + var jsonRule = "{ \"country\": { \"_nin\": [] } }"; var f = new Filter(jsonRule); var exp = FilterToLambdaParser.Parse(f); From aff83e4161dacdb1abe5ab5444c0ada456d2c692 Mon Sep 17 00:00:00 2001 From: Gerard Date: Fri, 22 Aug 2025 23:40:56 +0200 Subject: [PATCH 4/4] Remove comments --- src/BccCode.Linq/Server/Filter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/BccCode.Linq/Server/Filter.cs b/src/BccCode.Linq/Server/Filter.cs index 3c00af7..f49bffe 100644 --- a/src/BccCode.Linq/Server/Filter.cs +++ b/src/BccCode.Linq/Server/Filter.cs @@ -112,8 +112,6 @@ private void _parse(string json) else if (new[] { "_in", "_nin" }.Contains(key)) { var array = JsonConvert.DeserializeObject(value.ToString() ?? string.Empty); - // Treat empty arrays as a valid value (resulting in a predicate that's always false for _in - // and always true for _nin). Only null is considered invalid. if (array is null) { throw new ArgumentException(