Skip to content

Commit 971bf33

Browse files
author
Bart Koelman
committed
Changed the expression tokenizer to recognize dots in field chains
1 parent da7024c commit 971bf33

File tree

6 files changed

+36
-8
lines changed

6 files changed

+36
-8
lines changed

src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs

+32-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Immutable;
2+
using System.Text;
23
using JetBrains.Annotations;
34
using JsonApiDotNetCore.Queries.Expressions;
45
using JsonApiDotNetCore.Resources.Annotations;
@@ -31,17 +32,42 @@ protected virtual void Tokenize(string source)
3132

3233
protected ResourceFieldChainExpression ParseFieldChain(FieldChainRequirements chainRequirements, string? alternativeErrorMessage)
3334
{
34-
if (TokenStack.TryPop(out Token? token) && token.Kind == TokenKind.Text)
35+
var pathBuilder = new StringBuilder();
36+
EatFieldChain(pathBuilder, alternativeErrorMessage);
37+
38+
IImmutableList<ResourceFieldAttribute> chain = OnResolveFieldChain(pathBuilder.ToString(), chainRequirements);
39+
40+
if (chain.Any())
3541
{
36-
IImmutableList<ResourceFieldAttribute> chain = OnResolveFieldChain(token.Value!, chainRequirements);
42+
return new ResourceFieldChainExpression(chain);
43+
}
44+
45+
throw new QueryParseException(alternativeErrorMessage ?? "Field name expected.");
46+
}
3747

38-
if (chain.Any())
48+
private void EatFieldChain(StringBuilder pathBuilder, string? alternativeErrorMessage)
49+
{
50+
while (true)
51+
{
52+
if (TokenStack.TryPop(out Token? token) && token.Kind == TokenKind.Text)
53+
{
54+
pathBuilder.Append(token.Value);
55+
56+
if (TokenStack.TryPeek(out Token? nextToken) && nextToken.Kind == TokenKind.Period)
57+
{
58+
EatSingleCharacterToken(TokenKind.Period);
59+
pathBuilder.Append('.');
60+
}
61+
else
62+
{
63+
return;
64+
}
65+
}
66+
else
3967
{
40-
return new ResourceFieldChainExpression(chain);
68+
throw new QueryParseException(alternativeErrorMessage ?? "Field name expected.");
4169
}
4270
}
43-
44-
throw new QueryParseException(alternativeErrorMessage ?? "Field name expected.");
4571
}
4672

4773
protected CountExpression? TryParseCount()

src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public sealed class QueryTokenizer
1414
[')'] = TokenKind.CloseParen,
1515
['['] = TokenKind.OpenBracket,
1616
[']'] = TokenKind.CloseBracket,
17+
['.'] = TokenKind.Period,
1718
[','] = TokenKind.Comma,
1819
[':'] = TokenKind.Colon,
1920
['-'] = TokenKind.Minus

src/JsonApiDotNetCore/Queries/Internal/Parsing/TokenKind.cs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public enum TokenKind
66
CloseParen,
77
OpenBracket,
88
CloseBracket,
9+
Period,
910
Comma,
1011
Colon,
1112
Minus,

test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/IncludeParseTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, b
5454
[InlineData("includes", " ", "Unexpected whitespace.")]
5555
[InlineData("includes", ",", "Relationship name expected.")]
5656
[InlineData("includes", "posts,", "Relationship name expected.")]
57+
[InlineData("includes", "posts.", "Relationship name expected.")]
5758
[InlineData("includes", "posts[", ", expected.")]
5859
[InlineData("includes", "title", "Relationship 'title' does not exist on resource type 'blogs'.")]
5960
[InlineData("includes", "posts.comments.publishTime,",

test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/LegacyFilterParseTests.cs

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public LegacyFilterParseTests()
3131
[InlineData("filter", "some", "Expected field name between brackets in filter parameter name.")]
3232
[InlineData("filter[", "some", "Expected field name between brackets in filter parameter name.")]
3333
[InlineData("filter[]", "some", "Expected field name between brackets in filter parameter name.")]
34-
[InlineData("filter[.]", "some", "Relationship '' in '.' does not exist on resource type 'blogPosts'.")]
3534
[InlineData("filter[some]", "other", "Field 'some' does not exist on resource type 'blogPosts'.")]
3635
[InlineData("filter[author]", "some", "Attribute 'author' does not exist on resource type 'blogPosts'.")]
3736
[InlineData("filter[author.posts]", "some",

test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, b
5858
[InlineData("fields[]", "", "Resource type expected.")]
5959
[InlineData("fields[ ]", "", "Unexpected whitespace.")]
6060
[InlineData("fields[owner]", "", "Resource type 'owner' does not exist.")]
61-
[InlineData("fields[owner.posts]", "id", "Resource type 'owner.posts' does not exist.")]
61+
[InlineData("fields[owner.posts]", "id", "Resource type 'owner' does not exist.")]
6262
[InlineData("fields[blogPosts]", " ", "Unexpected whitespace.")]
6363
[InlineData("fields[blogPosts]", "some", "Field 'some' does not exist on resource type 'blogPosts'.")]
6464
[InlineData("fields[blogPosts]", "id,owner.name", "Field 'owner.name' does not exist on resource type 'blogPosts'.")]

0 commit comments

Comments
 (0)