Skip to content

Commit cb5bf11

Browse files
committed
Move checks for blocked fields into parsers: this simplifies the API shape of parsers, making it easier to use them directly from external code
1 parent 5da2ad6 commit cb5bf11

File tree

12 files changed

+52
-90
lines changed

12 files changed

+52
-90
lines changed

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,15 @@ public class FilterParser : QueryExpressionParser
1515
{
1616
private readonly IResourceFactory _resourceFactory;
1717
private readonly IEnumerable<IFilterValueConverter> _filterValueConverters;
18-
private readonly Action<ResourceFieldAttribute, ResourceType, string>? _validateSingleFieldCallback;
1918
private ResourceType? _resourceTypeInScope;
2019

21-
public FilterParser(IResourceFactory resourceFactory, IEnumerable<IFilterValueConverter> filterValueConverters,
22-
Action<ResourceFieldAttribute, ResourceType, string>? validateSingleFieldCallback = null)
20+
public FilterParser(IResourceFactory resourceFactory, IEnumerable<IFilterValueConverter> filterValueConverters)
2321
{
2422
ArgumentGuard.NotNull(resourceFactory);
2523
ArgumentGuard.NotNull(filterValueConverters);
2624

2725
_resourceFactory = resourceFactory;
2826
_filterValueConverters = filterValueConverters;
29-
_validateSingleFieldCallback = validateSingleFieldCallback;
3027
}
3128

3229
public FilterExpression Parse(string source, ResourceType resourceTypeInScope)
@@ -519,29 +516,37 @@ protected override IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(st
519516
{
520517
if (chainRequirements == FieldChainRequirements.EndsInToMany)
521518
{
522-
return ChainResolver.ResolveToOneChainEndingInToMany(_resourceTypeInScope!, path, FieldChainInheritanceRequirement.Disabled,
523-
_validateSingleFieldCallback);
519+
return ChainResolver.ResolveToOneChainEndingInToMany(_resourceTypeInScope!, path, FieldChainInheritanceRequirement.Disabled, ValidateSingleField);
524520
}
525521

526522
if (chainRequirements == FieldChainRequirements.EndsInAttribute)
527523
{
528524
return ChainResolver.ResolveToOneChainEndingInAttribute(_resourceTypeInScope!, path, FieldChainInheritanceRequirement.Disabled,
529-
_validateSingleFieldCallback);
525+
ValidateSingleField);
530526
}
531527

532528
if (chainRequirements == FieldChainRequirements.EndsInToOne)
533529
{
534-
return ChainResolver.ResolveToOneChain(_resourceTypeInScope!, path, _validateSingleFieldCallback);
530+
return ChainResolver.ResolveToOneChain(_resourceTypeInScope!, path, ValidateSingleField);
535531
}
536532

537533
if (chainRequirements.HasFlag(FieldChainRequirements.EndsInAttribute) && chainRequirements.HasFlag(FieldChainRequirements.EndsInToOne))
538534
{
539-
return ChainResolver.ResolveToOneChainEndingInAttributeOrToOne(_resourceTypeInScope!, path, _validateSingleFieldCallback);
535+
return ChainResolver.ResolveToOneChainEndingInAttributeOrToOne(_resourceTypeInScope!, path, ValidateSingleField);
540536
}
541537

542538
throw new InvalidOperationException($"Unexpected combination of chain requirement flags '{chainRequirements}'.");
543539
}
544540

541+
protected override void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
542+
{
543+
if (field.IsFilterBlocked())
544+
{
545+
string kind = field is AttrAttribute ? "attribute" : "relationship";
546+
throw new QueryParseException($"Filtering on {kind} '{field.PublicName}' is not allowed.");
547+
}
548+
}
549+
545550
private TResult InScopeOfResourceType<TResult>(ResourceType resourceType, Func<TResult> action)
546551
{
547552
ResourceType? backupType = _resourceTypeInScope;

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,8 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing;
99
[PublicAPI]
1010
public class PaginationParser : QueryExpressionParser
1111
{
12-
private readonly Action<ResourceFieldAttribute, ResourceType, string>? _validateSingleFieldCallback;
1312
private ResourceType? _resourceTypeInScope;
1413

15-
public PaginationParser(Action<ResourceFieldAttribute, ResourceType, string>? validateSingleFieldCallback = null)
16-
{
17-
_validateSingleFieldCallback = validateSingleFieldCallback;
18-
}
19-
2014
public PaginationQueryStringValueExpression Parse(string source, ResourceType resourceTypeInScope)
2115
{
2216
ArgumentGuard.NotNull(resourceTypeInScope);
@@ -104,6 +98,6 @@ protected PaginationElementQueryStringValueExpression ParsePaginationElement()
10498

10599
protected override IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(string path, FieldChainRequirements chainRequirements)
106100
{
107-
return ChainResolver.ResolveToManyChain(_resourceTypeInScope!, path, _validateSingleFieldCallback);
101+
return ChainResolver.ResolveToManyChain(_resourceTypeInScope!, path);
108102
}
109103
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Immutable;
22
using System.Text;
33
using JetBrains.Annotations;
4+
using JsonApiDotNetCore.Configuration;
45
using JsonApiDotNetCore.Queries.Expressions;
56
using JsonApiDotNetCore.Resources.Annotations;
67

@@ -24,6 +25,10 @@ public abstract class QueryExpressionParser
2425
/// </summary>
2526
protected abstract IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(string path, FieldChainRequirements chainRequirements);
2627

28+
protected virtual void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
29+
{
30+
}
31+
2732
protected virtual void Tokenize(string source)
2833
{
2934
var tokenizer = new QueryTokenizer(source);

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,11 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing;
1010
public class QueryStringParameterScopeParser : QueryExpressionParser
1111
{
1212
private readonly FieldChainRequirements _chainRequirements;
13-
private readonly Action<ResourceFieldAttribute, ResourceType, string>? _validateSingleFieldCallback;
1413
private ResourceType? _resourceTypeInScope;
1514

16-
public QueryStringParameterScopeParser(FieldChainRequirements chainRequirements,
17-
Action<ResourceFieldAttribute, ResourceType, string>? validateSingleFieldCallback = null)
15+
public QueryStringParameterScopeParser(FieldChainRequirements chainRequirements)
1816
{
1917
_chainRequirements = chainRequirements;
20-
_validateSingleFieldCallback = validateSingleFieldCallback;
2118
}
2219

2320
public QueryStringParameterScopeExpression Parse(string source, ResourceType resourceTypeInScope)
@@ -63,12 +60,12 @@ protected override IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(st
6360
if (chainRequirements == FieldChainRequirements.EndsInToMany)
6461
{
6562
// The mismatch here (ends-in-to-many being interpreted as entire-chain-must-be-to-many) is intentional.
66-
return ChainResolver.ResolveToManyChain(_resourceTypeInScope!, path, _validateSingleFieldCallback);
63+
return ChainResolver.ResolveToManyChain(_resourceTypeInScope!, path);
6764
}
6865

6966
if (chainRequirements == FieldChainRequirements.IsRelationship)
7067
{
71-
return ChainResolver.ResolveRelationshipChain(_resourceTypeInScope!, path, _validateSingleFieldCallback);
68+
return ChainResolver.ResolveRelationshipChain(_resourceTypeInScope!, path);
7269
}
7370

7471
throw new InvalidOperationException($"Unexpected combination of chain requirement flags '{chainRequirements}'.");

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,8 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing;
99
[PublicAPI]
1010
public class SortParser : QueryExpressionParser
1111
{
12-
private readonly Action<ResourceFieldAttribute, ResourceType, string>? _validateSingleFieldCallback;
1312
private ResourceType? _resourceTypeInScope;
1413

15-
public SortParser(Action<ResourceFieldAttribute, ResourceType, string>? validateSingleFieldCallback = null)
16-
{
17-
_validateSingleFieldCallback = validateSingleFieldCallback;
18-
}
19-
2014
public SortExpression Parse(string source, ResourceType resourceTypeInScope)
2115
{
2216
ArgumentGuard.NotNull(resourceTypeInScope);
@@ -107,9 +101,17 @@ protected override IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(st
107101
if (chainRequirements == FieldChainRequirements.EndsInAttribute)
108102
{
109103
return ChainResolver.ResolveToOneChainEndingInAttribute(_resourceTypeInScope!, path, FieldChainInheritanceRequirement.RequireSingleMatch,
110-
_validateSingleFieldCallback);
104+
ValidateSingleField);
111105
}
112106

113107
throw new InvalidOperationException($"Unexpected combination of chain requirement flags '{chainRequirements}'.");
114108
}
109+
110+
protected override void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
111+
{
112+
if (field is AttrAttribute attribute && !attribute.Capabilities.HasFlag(AttrCapabilities.AllowSort))
113+
{
114+
throw new QueryParseException($"Sorting on attribute '{attribute.PublicName}' is not allowed.");
115+
}
116+
}
115117
}

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,8 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing;
99
[PublicAPI]
1010
public class SparseFieldSetParser : QueryExpressionParser
1111
{
12-
private readonly Action<ResourceFieldAttribute, ResourceType, string>? _validateSingleFieldCallback;
1312
private ResourceType? _resourceType;
1413

15-
public SparseFieldSetParser(Action<ResourceFieldAttribute, ResourceType, string>? validateSingleFieldCallback = null)
16-
{
17-
_validateSingleFieldCallback = validateSingleFieldCallback;
18-
}
19-
2014
public SparseFieldSetExpression? Parse(string source, ResourceType resourceType)
2115
{
2216
ArgumentGuard.NotNull(resourceType);
@@ -55,8 +49,17 @@ protected override IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(st
5549
{
5650
ResourceFieldAttribute field = ChainResolver.GetField(path, _resourceType!, path);
5751

58-
_validateSingleFieldCallback?.Invoke(field, _resourceType!, path);
52+
ValidateSingleField(field, _resourceType!, path);
5953

6054
return ImmutableArray.Create(field);
6155
}
56+
57+
protected override void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
58+
{
59+
if (field.IsViewBlocked())
60+
{
61+
string kind = field is AttrAttribute ? "attribute" : "relationship";
62+
throw new QueryParseException($"Retrieving the {kind} '{field.PublicName}' is not allowed.");
63+
}
64+
}
6265
}

src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using JsonApiDotNetCore.Queries.Expressions;
99
using JsonApiDotNetCore.Queries.Internal.Parsing;
1010
using JsonApiDotNetCore.Resources;
11-
using JsonApiDotNetCore.Resources.Annotations;
1211
using Microsoft.Extensions.Primitives;
1312

1413
namespace JsonApiDotNetCore.QueryStrings.Internal;
@@ -24,8 +23,6 @@ public class FilterQueryStringParameterReader : QueryStringParameterReader, IFil
2423
private readonly ImmutableArray<FilterExpression>.Builder _filtersInGlobalScope = ImmutableArray.CreateBuilder<FilterExpression>();
2524
private readonly Dictionary<ResourceFieldChainExpression, ImmutableArray<FilterExpression>.Builder> _filtersPerScope = new();
2625

27-
private string? _lastParameterName;
28-
2926
public bool AllowEmptyValue => false;
3027

3128
public FilterQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph, IResourceFactory resourceFactory, IJsonApiOptions options,
@@ -37,18 +34,7 @@ public FilterQueryStringParameterReader(IJsonApiRequest request, IResourceGraph
3734

3835
_options = options;
3936
_scopeParser = new QueryStringParameterScopeParser(FieldChainRequirements.EndsInToMany);
40-
_filterParser = new FilterParser(resourceFactory, filterValueConverters, ValidateSingleField);
41-
}
42-
43-
protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
44-
{
45-
if (field.IsFilterBlocked())
46-
{
47-
string kind = field is AttrAttribute ? "attribute" : "relationship";
48-
49-
throw new InvalidQueryStringParameterException(_lastParameterName!, $"Filtering on the requested {kind} is not allowed.",
50-
$"Filtering on {kind} '{field.PublicName}' is not allowed.");
51-
}
37+
_filterParser = new FilterParser(resourceFactory, filterValueConverters);
5238
}
5339

5440
/// <inheritdoc />
@@ -71,8 +57,6 @@ public virtual bool CanRead(string parameterName)
7157
/// <inheritdoc />
7258
public virtual void Read(string parameterName, StringValues parameterValue)
7359
{
74-
_lastParameterName = parameterName;
75-
7660
foreach (string value in parameterValue.SelectMany(ExtractParameterValue))
7761
{
7862
ReadSingleValue(parameterName, value);
@@ -116,7 +100,7 @@ private void ReadSingleValue(string parameterName, string parameterValue)
116100
}
117101
catch (QueryParseException exception)
118102
{
119-
throw new InvalidQueryStringParameterException(_lastParameterName!, "The specified filter is invalid.", exception.Message, exception);
103+
throw new InvalidQueryStringParameterException(parameterName, "The specified filter is invalid.", exception.Message, exception);
120104
}
121105
}
122106

src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using JsonApiDotNetCore.Queries;
77
using JsonApiDotNetCore.Queries.Expressions;
88
using JsonApiDotNetCore.Queries.Internal.Parsing;
9-
using JsonApiDotNetCore.Resources.Annotations;
109
using Microsoft.Extensions.Primitives;
1110

1211
namespace JsonApiDotNetCore.QueryStrings.Internal;
@@ -17,24 +16,14 @@ public class SortQueryStringParameterReader : QueryStringParameterReader, ISortQ
1716
private readonly QueryStringParameterScopeParser _scopeParser;
1817
private readonly SortParser _sortParser;
1918
private readonly List<ExpressionInScope> _constraints = new();
20-
private string? _lastParameterName;
2119

2220
public bool AllowEmptyValue => false;
2321

2422
public SortQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph)
2523
: base(request, resourceGraph)
2624
{
2725
_scopeParser = new QueryStringParameterScopeParser(FieldChainRequirements.EndsInToMany);
28-
_sortParser = new SortParser(ValidateSingleField);
29-
}
30-
31-
protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
32-
{
33-
if (field is AttrAttribute attribute && !attribute.Capabilities.HasFlag(AttrCapabilities.AllowSort))
34-
{
35-
throw new InvalidQueryStringParameterException(_lastParameterName!, "Sorting on the requested attribute is not allowed.",
36-
$"Sorting on attribute '{attribute.PublicName}' is not allowed.");
37-
}
26+
_sortParser = new SortParser();
3827
}
3928

4029
/// <inheritdoc />
@@ -57,8 +46,6 @@ public virtual bool CanRead(string parameterName)
5746
/// <inheritdoc />
5847
public virtual void Read(string parameterName, StringValues parameterValue)
5948
{
60-
_lastParameterName = parameterName;
61-
6249
try
6350
{
6451
ResourceFieldChainExpression? scope = GetScope(parameterName);

src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,14 @@ public class SparseFieldSetQueryStringParameterReader : QueryStringParameterRead
2222
private readonly ImmutableDictionary<ResourceType, SparseFieldSetExpression>.Builder _sparseFieldTableBuilder =
2323
ImmutableDictionary.CreateBuilder<ResourceType, SparseFieldSetExpression>();
2424

25-
private string? _lastParameterName;
26-
2725
/// <inheritdoc />
2826
bool IQueryStringParameterReader.AllowEmptyValue => true;
2927

3028
public SparseFieldSetQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph)
3129
: base(request, resourceGraph)
3230
{
3331
_sparseFieldTypeParser = new SparseFieldTypeParser(resourceGraph);
34-
_sparseFieldSetParser = new SparseFieldSetParser(ValidateSingleField);
35-
}
36-
37-
protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path)
38-
{
39-
if (field.IsViewBlocked())
40-
{
41-
string kind = field is AttrAttribute ? "attribute" : "relationship";
42-
43-
throw new InvalidQueryStringParameterException(_lastParameterName!, $"Retrieving the requested {kind} is not allowed.",
44-
$"Retrieving the {kind} '{field.PublicName}' is not allowed.");
45-
}
32+
_sparseFieldSetParser = new SparseFieldSetParser();
4633
}
4734

4835
/// <inheritdoc />
@@ -64,8 +51,6 @@ public virtual bool CanRead(string parameterName)
6451
/// <inheritdoc />
6552
public virtual void Read(string parameterName, StringValues parameterValue)
6653
{
67-
_lastParameterName = parameterName;
68-
6954
try
7055
{
7156
ResourceType targetResourceType = GetSparseFieldType(parameterName);

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public async Task Cannot_filter_on_attribute_with_blocked_capability()
8484

8585
ErrorObject error = responseDocument.Errors[0];
8686
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
87-
error.Title.Should().Be("Filtering on the requested attribute is not allowed.");
87+
error.Title.Should().Be("The specified filter is invalid.");
8888
error.Detail.Should().Be("Filtering on attribute 'dateOfBirth' is not allowed.");
8989
error.Source.ShouldNotBeNull();
9090
error.Source.Parameter.Should().Be("filter");
@@ -106,7 +106,7 @@ public async Task Cannot_filter_on_ToMany_relationship_with_blocked_capability()
106106

107107
ErrorObject error = responseDocument.Errors[0];
108108
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
109-
error.Title.Should().Be("Filtering on the requested relationship is not allowed.");
109+
error.Title.Should().Be("The specified filter is invalid.");
110110
error.Detail.Should().Be("Filtering on relationship 'appointments' is not allowed.");
111111
error.Source.ShouldNotBeNull();
112112
error.Source.Parameter.Should().Be("filter");

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Sorting/SortTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ public async Task Cannot_sort_on_attribute_with_blocked_capability()
508508

509509
ErrorObject error = responseDocument.Errors[0];
510510
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
511-
error.Title.Should().Be("Sorting on the requested attribute is not allowed.");
511+
error.Title.Should().Be("The specified sort is invalid.");
512512
error.Detail.Should().Be("Sorting on attribute 'dateOfBirth' is not allowed.");
513513
error.Source.ShouldNotBeNull();
514514
error.Source.Parameter.Should().Be("sort");

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ public async Task Cannot_select_attribute_with_blocked_capability()
736736

737737
ErrorObject error = responseDocument.Errors[0];
738738
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
739-
error.Title.Should().Be("Retrieving the requested attribute is not allowed.");
739+
error.Title.Should().Be("The specified fieldset is invalid.");
740740
error.Detail.Should().Be("Retrieving the attribute 'password' is not allowed.");
741741
error.Source.ShouldNotBeNull();
742742
error.Source.Parameter.Should().Be("fields[webAccounts]");
@@ -760,7 +760,7 @@ public async Task Cannot_select_ToOne_relationship_with_blocked_capability()
760760

761761
ErrorObject error = responseDocument.Errors[0];
762762
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
763-
error.Title.Should().Be("Retrieving the requested relationship is not allowed.");
763+
error.Title.Should().Be("The specified fieldset is invalid.");
764764
error.Detail.Should().Be("Retrieving the relationship 'person' is not allowed.");
765765
error.Source.ShouldNotBeNull();
766766
error.Source.Parameter.Should().Be("fields[webAccounts]");
@@ -784,7 +784,7 @@ public async Task Cannot_select_ToMany_relationship_with_blocked_capability()
784784

785785
ErrorObject error = responseDocument.Errors[0];
786786
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
787-
error.Title.Should().Be("Retrieving the requested relationship is not allowed.");
787+
error.Title.Should().Be("The specified fieldset is invalid.");
788788
error.Detail.Should().Be("Retrieving the relationship 'appointments' is not allowed.");
789789
error.Source.ShouldNotBeNull();
790790
error.Source.Parameter.Should().Be("fields[calendars]");

0 commit comments

Comments
 (0)