Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public async Task decimal_value_uses_invariant_culture()
}

[Fact]
public async Task list_value_with_comma_is_split_into_items()
public async Task list_value_with_comma_is_one_item()
{
// Arrange
var testingServiceScope = new TestingServiceScope();
Expand All @@ -106,8 +106,8 @@ public async Task list_value_with_comma_is_split_into_items()
var notInPeople = await queryablePeople.ApplyQueryKitFilter(notInInput).ToListAsync();

// Assert
inPeople.Select(x => x.Id).Should().Equal(fakePersonTwo.Id);
notInPeople.Select(x => x.Id).Should().Equal(fakePersonOne.Id);
inPeople.Select(x => x.Id).Should().Equal(fakePersonOne.Id);
notInPeople.Select(x => x.Id).Should().Equal(fakePersonTwo.Id);
}

[Theory]
Expand Down
10 changes: 5 additions & 5 deletions QueryKit.UnitTests/FilterParsingRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ public void decimal_value_uses_invariant_culture_in_memory()
}

[Theory]
[InlineData("Title ^^ [\"Warm, with syrup\", \"a\\b\"]", new[] { "Warm", "with syrup", "a\\b" })]
[InlineData("Title !^^ [\"Warm, with syrup\", \"a\\b\"]", new[] { "Warm, with syrup" })]
[InlineData("Title ^^* [\"WARM, WITH SYRUP\"]", new[] { "Warm", "with syrup" })]
[InlineData("Title ^^ [\"\"\"Warm, with syrup\"\"\", \"Warm\"]", new[] { "Warm", "with syrup" })]
[InlineData("Title ^^ [\"Warm, with syrup\", \"a\\b\"]", new[] { "Warm, with syrup", "a\\b" })]
[InlineData("Title !^^ [\"Warm, with syrup\", \"a\\b\"]", new[] { "Warm", "with syrup" })]
[InlineData("Title ^^* [\"WARM, WITH SYRUP\"]", new[] { "Warm, with syrup" })]
[InlineData("Title ^^ [\"\"\"Warm, with syrup\"\"\", \"Warm\"]", new[] { "Warm, with syrup", "Warm" })]
[InlineData("Title ^^ [\" Warm \", \"with syrup \"]", new[] { "Warm", "with syrup" })]
public void list_value_with_comma_is_split_into_items(string input, string[] expectedTitles)
public void list_value_with_comma_is_one_item(string input, string[] expectedTitles)
{
var people = new[]
{
Expand Down
37 changes: 34 additions & 3 deletions QueryKit/FilterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Configuration;
using Exceptions;
using Operators;
Expand Down Expand Up @@ -353,9 +354,39 @@ from closingQuotes in Parse.Char('"').Repeat(count).Text()
from openingBracket in Parse.Char('[')
from content in SquareBracketValuesParser
from closingBracket in Parse.Char(']')
select new RightSideValue("[" + string.Join(",", content.Select(x => x.Value)) + "]", false,
select new RightSideValue("[" + string.Join(",", content.Select(x => EscapeListItem(x.Value))) + "]", false,
content.Any(x => x.IsDotOnlyNumber) ? "" : null);

// List items are joined with ',' so quoted items that contain ',' or '\' are escaped and split back with SplitListItems, which trims each item
private static string EscapeListItem(string item)
=> item.Replace(@"\", @"\\").Replace(",", @"\,");

private static List<string> SplitListItems(string list)
{
var items = new List<string>();
var current = new StringBuilder();
var content = list.Substring(1, list.Length - 2);
for (var i = 0; i < content.Length; i++)
{
if (content[i] == '\\' && i + 1 < content.Length)
{
current.Append(content[++i]);
}
else if (content[i] == ',')
{
items.Add(current.ToString().Trim());
current.Clear();
}
else
{
current.Append(content[i]);
}
}
items.Add(current.ToString().Trim());

return items;
}

private static readonly Parser<RightSideValue> RightSideValueChoiceParser =
Parse.String("null").Text().Select(v => new RightSideValue(v, false))
.Or(GuidFormatParser.Select(v => new RightSideValue(v, false)))
Expand Down Expand Up @@ -612,7 +643,7 @@ private static Expression CreateRightExprFromType(Type leftExprType, string righ
{
targetType = typeof(string);
}
var values = right.Trim('[', ']').Split(',').Select(x => x.Trim()).ToList();
var values = SplitListItems(right);
var elementType = targetType.IsArray ? targetType.GetElementType()! : targetType;

var expressions = values.Select(x =>
Expand Down Expand Up @@ -724,7 +755,7 @@ private static Expression CreateRightExprFromType(Type leftExprType, string righ

if (right.StartsWith("[") && right.EndsWith("]"))
{
var values = right.Trim('[', ']').Split(',').Select(x => x.Trim()).ToList();
var values = SplitListItems(right);
var elementType = targetType.IsArray ? targetType.GetElementType() : targetType;

var expressions = values.Select<string, Expression>(x =>
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ Here's an example for the `in` operator:
var input = """(Age ^^ [20, 30, 40]) && (BirthMonth ^^* ["January", "February", "March"]) || (Id ^^ ["6d623e92-d2cf-4496-a2df-f49fa77328ee"])""";
```

A quoted list item can hold a comma. For example, `Title ^^ ["Warm, with syrup", "Cold"]` has two items: `Warm, with syrup` and `Cold`.

### Property List Grouping

Property list grouping allows you to apply a single comparison operation across multiple properties, making it easy to search for a value in any of several fields without writing repetitive conditions.
Expand Down
Loading