Skip to content

Commit 72ce454

Browse files
author
Bart Koelman
authored
Merge pull request #792 from bart-degreed/complex-queries
Composable filters and deeply nested queries
2 parents e0270bd + 1a211fa commit 72ce454

File tree

453 files changed

+18262
-10378
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

453 files changed

+18262
-10378
lines changed

Directory.Build.props

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<!-- Test Project Dependencies -->
1616
<PropertyGroup>
1717
<XUnitVersion>2.4.1</XUnitVersion>
18+
<FluentAssertionsVersion>5.10.3</FluentAssertionsVersion>
1819
<BogusVersion>29.0.1</BogusVersion>
1920
<MoqVersion>4.13.1</MoqVersion>
2021
</PropertyGroup>

benchmarks/BenchmarkResource.cs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using JsonApiDotNetCore.Models;
2+
using JsonApiDotNetCore.Models.Annotation;
23

34
namespace Benchmarks
45
{

benchmarks/DependencyFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using JsonApiDotNetCore.Configuration;
44
using JsonApiDotNetCore.Internal.Contracts;
55
using JsonApiDotNetCore.Models;
6-
using JsonApiDotNetCore.Query;
6+
using JsonApiDotNetCore.Services.Contract;
77
using Microsoft.Extensions.Logging.Abstractions;
88
using Moq;
99

benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs

+11-11
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ namespace Benchmarks.LinkBuilder
88
public class LinkBuilderGetNamespaceFromPathBenchmarks
99
{
1010
private const string RequestPath = "/api/some-really-long-namespace-path/resources/current/articles/?some";
11-
private const string EntityName = "articles";
11+
private const string ResourceName = "articles";
1212
private const char PathDelimiter = '/';
1313

1414
[Benchmark]
15-
public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, EntityName);
15+
public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, ResourceName);
1616

1717
[Benchmark]
18-
public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, EntityName);
18+
public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, ResourceName);
1919

20-
public static string GetNamespaceFromPathUsingStringSplit(string path, string entityName)
20+
public static string GetNamespaceFromPathUsingStringSplit(string path, string resourceName)
2121
{
2222
StringBuilder namespaceBuilder = new StringBuilder(path.Length);
2323
string[] segments = path.Split('/');
2424

2525
for (int index = 1; index < segments.Length; index++)
2626
{
27-
if (segments[index] == entityName)
27+
if (segments[index] == resourceName)
2828
{
2929
break;
3030
}
@@ -36,22 +36,22 @@ public static string GetNamespaceFromPathUsingStringSplit(string path, string en
3636
return namespaceBuilder.ToString();
3737
}
3838

39-
public static string GetNamespaceFromPathUsingReadOnlySpan(string path, string entityName)
39+
public static string GetNamespaceFromPathUsingReadOnlySpan(string path, string resourceName)
4040
{
41-
ReadOnlySpan<char> entityNameSpan = entityName.AsSpan();
41+
ReadOnlySpan<char> resourceNameSpan = resourceName.AsSpan();
4242
ReadOnlySpan<char> pathSpan = path.AsSpan();
4343

4444
for (int index = 0; index < pathSpan.Length; index++)
4545
{
4646
if (pathSpan[index].Equals(PathDelimiter))
4747
{
48-
if (pathSpan.Length > index + entityNameSpan.Length)
48+
if (pathSpan.Length > index + resourceNameSpan.Length)
4949
{
50-
ReadOnlySpan<char> possiblePathSegment = pathSpan.Slice(index + 1, entityNameSpan.Length);
50+
ReadOnlySpan<char> possiblePathSegment = pathSpan.Slice(index + 1, resourceNameSpan.Length);
5151

52-
if (entityNameSpan.SequenceEqual(possiblePathSegment))
52+
if (resourceNameSpan.SequenceEqual(possiblePathSegment))
5353
{
54-
int lastCharacterIndex = index + 1 + entityNameSpan.Length;
54+
int lastCharacterIndex = index + 1 + resourceNameSpan.Length;
5555

5656
bool isAtEnd = lastCharacterIndex == pathSpan.Length;
5757
bool hasDelimiterAfterSegment = pathSpan.Length >= lastCharacterIndex + 1 && pathSpan[lastCharacterIndex].Equals(PathDelimiter);

benchmarks/Query/QueryParserBenchmarks.cs

+42-38
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.ComponentModel.Design;
34
using BenchmarkDotNet.Attributes;
45
using JsonApiDotNetCore.Configuration;
6+
using JsonApiDotNetCore.Internal;
57
using JsonApiDotNetCore.Internal.Contracts;
6-
using JsonApiDotNetCore.Managers;
7-
using JsonApiDotNetCore.Query;
8-
using JsonApiDotNetCore.QueryParameterServices.Common;
9-
using JsonApiDotNetCore.Services;
8+
using JsonApiDotNetCore.Internal.QueryStrings;
9+
using JsonApiDotNetCore.QueryStrings;
10+
using JsonApiDotNetCore.RequestServices;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.WebUtilities;
1213
using Microsoft.Extensions.Logging.Abstractions;
@@ -17,56 +18,59 @@ namespace Benchmarks.Query
1718
public class QueryParserBenchmarks
1819
{
1920
private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor();
20-
private readonly QueryParameterParser _queryParameterParserForSort;
21-
private readonly QueryParameterParser _queryParameterParserForAll;
21+
private readonly QueryStringReader _queryStringReaderForSort;
22+
private readonly QueryStringReader _queryStringReaderForAll;
2223

2324
public QueryParserBenchmarks()
2425
{
25-
IJsonApiOptions options = new JsonApiOptions();
26+
IJsonApiOptions options = new JsonApiOptions
27+
{
28+
EnableLegacyFilterNotation = true
29+
};
30+
2631
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
27-
28-
var currentRequest = new CurrentRequest();
29-
currentRequest.SetRequestResource(resourceGraph.GetResourceContext(typeof(BenchmarkResource)));
3032

31-
IResourceDefinitionProvider resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
33+
var currentRequest = new CurrentRequest
34+
{
35+
PrimaryResource = resourceGraph.GetResourceContext(typeof(BenchmarkResource))
36+
};
3237

33-
_queryParameterParserForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, currentRequest, resourceDefinitionProvider, options, _queryStringAccessor);
34-
_queryParameterParserForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, currentRequest, resourceDefinitionProvider, options, _queryStringAccessor);
38+
_queryStringReaderForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, currentRequest, options, _queryStringAccessor);
39+
_queryStringReaderForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, currentRequest, options, _queryStringAccessor);
3540
}
3641

37-
private static QueryParameterParser CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph,
38-
CurrentRequest currentRequest, IResourceDefinitionProvider resourceDefinitionProvider,
42+
private static QueryStringReader CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph,
43+
CurrentRequest currentRequest,
3944
IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
4045
{
41-
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);
42-
43-
var queryServices = new List<IQueryParameterService>
46+
var sortReader = new SortQueryStringParameterReader(currentRequest, resourceGraph);
47+
48+
var readers = new List<IQueryStringParameterReader>
4449
{
45-
sortService
50+
sortReader
4651
};
4752

48-
return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
53+
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
4954
}
5055

51-
private static QueryParameterParser CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
52-
CurrentRequest currentRequest, IResourceDefinitionProvider resourceDefinitionProvider,
53-
IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
56+
private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
57+
CurrentRequest currentRequest, IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
5458
{
55-
IIncludeService includeService = new IncludeService(resourceGraph, currentRequest);
56-
IFilterService filterService = new FilterService(resourceDefinitionProvider, resourceGraph, currentRequest);
57-
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);
58-
ISparseFieldsService sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);
59-
IPageService pageService = new PageService(options, resourceGraph, currentRequest);
60-
IDefaultsService defaultsService = new DefaultsService(options);
61-
INullsService nullsService = new NullsService(options);
62-
63-
var queryServices = new List<IQueryParameterService>
59+
var resourceFactory = new ResourceFactory(new ServiceContainer());
60+
61+
var filterReader = new FilterQueryStringParameterReader(currentRequest, resourceGraph, resourceFactory, options);
62+
var sortReader = new SortQueryStringParameterReader(currentRequest, resourceGraph);
63+
var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(currentRequest, resourceGraph);
64+
var paginationReader = new PaginationQueryStringParameterReader(currentRequest, resourceGraph, options);
65+
var defaultsReader = new DefaultsQueryStringParameterReader(options);
66+
var nullsReader = new NullsQueryStringParameterReader(options);
67+
68+
var readers = new List<IQueryStringParameterReader>
6469
{
65-
includeService, filterService, sortService, sparseFieldsService, pageService, defaultsService,
66-
nullsService
70+
filterReader, sortReader, sparseFieldSetReader, paginationReader, defaultsReader, nullsReader
6771
};
6872

69-
return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
73+
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
7074
}
7175

7276
[Benchmark]
@@ -75,7 +79,7 @@ public void AscendingSort()
7579
var queryString = $"?sort={BenchmarkResourcePublicNames.NameAttr}";
7680

7781
_queryStringAccessor.SetQueryString(queryString);
78-
_queryParameterParserForSort.Parse(null);
82+
_queryStringReaderForSort.ReadAll(null);
7983
}
8084

8185
[Benchmark]
@@ -84,7 +88,7 @@ public void DescendingSort()
8488
var queryString = $"?sort=-{BenchmarkResourcePublicNames.NameAttr}";
8589

8690
_queryStringAccessor.SetQueryString(queryString);
87-
_queryParameterParserForSort.Parse(null);
91+
_queryStringReaderForSort.ReadAll(null);
8892
}
8993

9094
[Benchmark]
@@ -93,7 +97,7 @@ public void ComplexQuery() => Run(100, () =>
9397
var queryString = $"?filter[{BenchmarkResourcePublicNames.NameAttr}]=abc,eq:abc&sort=-{BenchmarkResourcePublicNames.NameAttr}&include=child&page[size]=1&fields={BenchmarkResourcePublicNames.NameAttr}";
9498

9599
_queryStringAccessor.SetQueryString(queryString);
96-
_queryParameterParserForAll.Parse(null);
100+
_queryStringReaderForAll.ReadAll(null);
97101
});
98102

99103
private void Run(int iterations, Action action) {

benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public JsonApiDeserializerBenchmarks()
3939
var options = new JsonApiOptions();
4040
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
4141
var targetedFields = new TargetedFields();
42-
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
42+
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new ResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
4343
}
4444

4545
[Benchmark]

benchmarks/Serialization/JsonApiSerializerBenchmarks.cs

+14-8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
using BenchmarkDotNet.Attributes;
33
using JsonApiDotNetCore.Configuration;
44
using JsonApiDotNetCore.Internal.Contracts;
5-
using JsonApiDotNetCore.Managers;
6-
using JsonApiDotNetCore.Query;
5+
using JsonApiDotNetCore.Internal.QueryStrings;
6+
using JsonApiDotNetCore.Queries;
7+
using JsonApiDotNetCore.RequestServices;
78
using JsonApiDotNetCore.Serialization;
89
using JsonApiDotNetCore.Serialization.Server;
910
using JsonApiDotNetCore.Serialization.Server.Builders;
@@ -14,7 +15,7 @@ namespace Benchmarks.Serialization
1415
[MarkdownExporter]
1516
public class JsonApiSerializerBenchmarks
1617
{
17-
private static readonly BenchmarkResource Content = new BenchmarkResource
18+
private static readonly BenchmarkResource _content = new BenchmarkResource
1819
{
1920
Id = 123,
2021
Name = Guid.NewGuid().ToString()
@@ -40,14 +41,19 @@ public JsonApiSerializerBenchmarks()
4041

4142
private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)
4243
{
43-
var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
4444
var currentRequest = new CurrentRequest();
45-
var sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);
46-
47-
return new FieldsToSerialize(resourceGraph, sparseFieldsService, resourceDefinitionProvider);
45+
46+
var constraintProviders = new IQueryConstraintProvider[]
47+
{
48+
new SparseFieldSetQueryStringParameterReader(currentRequest, resourceGraph)
49+
};
50+
51+
var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
52+
53+
return new FieldsToSerialize(resourceGraph, constraintProviders, resourceDefinitionProvider);
4854
}
4955

5056
[Benchmark]
51-
public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content);
57+
public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(_content);
5258
}
5359
}

docs/api/index.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ This section documents the package API and is generated from the XML source comm
44

55
## Common APIs
66

7-
- [`JsonApiOptions`](JsonApiDotNetCore.Configuration.JsonApiOptions.html)
8-
- [`IResourceGraph`](JsonApiDotNetCore.Internal.Contracts.IResourceGraph.html)
9-
- [`ResourceDefinition<T>`](JsonApiDotNetCore.Models.ResourceDefinition-1.html)
7+
- [`JsonApiOptions`](JsonApiDotNetCore.Configuration.JsonApiOptions.yml)
8+
- [`IResourceGraph`](JsonApiDotNetCore.Internal.Contracts.IResourceGraph.yml)
9+
- [`ResourceDefinition<TResource>`](JsonApiDotNetCore.Models.ResourceDefinition-1.yml)

docs/docfx.json

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"getting-started/**/toc.yml",
2626
"usage/**.md",
2727
"request-examples/**.md",
28+
"internals/**.md",
2829
"toc.yml",
2930
"*.md"
3031
]

docs/index.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ Eliminate CRUD boilerplate and provide the following features across your resour
1414
- Filtering
1515
- Sorting
1616
- Pagination
17-
- Sparse field selection
17+
- Sparse fieldset selection
1818
- Relationship inclusion and navigation
1919

20-
Checkout the [example requests](request-examples) to see the kind of features you will get out of the box.
20+
Checkout the [example requests](request-examples/index.md) to see the kind of features you will get out of the box.
2121

2222
### 2. Extensibility
2323

docs/internals/index.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Internals
2+
3+
The section contains overviews for the inner workings of the JsonApiDotNetCore library.

0 commit comments

Comments
 (0)