Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composable filters and deeply nested queries #792

Merged
merged 14 commits into from
Aug 17, 2020
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<!-- Test Project Dependencies -->
<PropertyGroup>
<XUnitVersion>2.4.1</XUnitVersion>
<FluentAssertionsVersion>5.10.3</FluentAssertionsVersion>
<BogusVersion>29.0.1</BogusVersion>
<MoqVersion>4.13.1</MoqVersion>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions benchmarks/BenchmarkResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Models.Annotation;

namespace Benchmarks
{
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/DependencyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.Services.Contract;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;

Expand Down
22 changes: 11 additions & 11 deletions benchmarks/LinkBuilder/LinkBuilderGetNamespaceFromPathBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ namespace Benchmarks.LinkBuilder
public class LinkBuilderGetNamespaceFromPathBenchmarks
{
private const string RequestPath = "/api/some-really-long-namespace-path/resources/current/articles/?some";
private const string EntityName = "articles";
private const string ResourceName = "articles";
private const char PathDelimiter = '/';

[Benchmark]
public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, EntityName);
public void UsingStringSplit() => GetNamespaceFromPathUsingStringSplit(RequestPath, ResourceName);

[Benchmark]
public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, EntityName);
public void UsingReadOnlySpan() => GetNamespaceFromPathUsingReadOnlySpan(RequestPath, ResourceName);

public static string GetNamespaceFromPathUsingStringSplit(string path, string entityName)
public static string GetNamespaceFromPathUsingStringSplit(string path, string resourceName)
{
StringBuilder namespaceBuilder = new StringBuilder(path.Length);
string[] segments = path.Split('/');

for (int index = 1; index < segments.Length; index++)
{
if (segments[index] == entityName)
if (segments[index] == resourceName)
{
break;
}
Expand All @@ -36,22 +36,22 @@ public static string GetNamespaceFromPathUsingStringSplit(string path, string en
return namespaceBuilder.ToString();
}

public static string GetNamespaceFromPathUsingReadOnlySpan(string path, string entityName)
public static string GetNamespaceFromPathUsingReadOnlySpan(string path, string resourceName)
{
ReadOnlySpan<char> entityNameSpan = entityName.AsSpan();
ReadOnlySpan<char> resourceNameSpan = resourceName.AsSpan();
ReadOnlySpan<char> pathSpan = path.AsSpan();

for (int index = 0; index < pathSpan.Length; index++)
{
if (pathSpan[index].Equals(PathDelimiter))
{
if (pathSpan.Length > index + entityNameSpan.Length)
if (pathSpan.Length > index + resourceNameSpan.Length)
{
ReadOnlySpan<char> possiblePathSegment = pathSpan.Slice(index + 1, entityNameSpan.Length);
ReadOnlySpan<char> possiblePathSegment = pathSpan.Slice(index + 1, resourceNameSpan.Length);

if (entityNameSpan.SequenceEqual(possiblePathSegment))
if (resourceNameSpan.SequenceEqual(possiblePathSegment))
{
int lastCharacterIndex = index + 1 + entityNameSpan.Length;
int lastCharacterIndex = index + 1 + resourceNameSpan.Length;

bool isAtEnd = lastCharacterIndex == pathSpan.Length;
bool hasDelimiterAfterSegment = pathSpan.Length >= lastCharacterIndex + 1 && pathSpan[lastCharacterIndex].Equals(PathDelimiter);
Expand Down
80 changes: 42 additions & 38 deletions benchmarks/Query/QueryParserBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Managers;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.QueryParameterServices.Common;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCore.Internal.QueryStrings;
using JsonApiDotNetCore.QueryStrings;
using JsonApiDotNetCore.RequestServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging.Abstractions;
Expand All @@ -17,56 +18,59 @@ namespace Benchmarks.Query
public class QueryParserBenchmarks
{
private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor();
private readonly QueryParameterParser _queryParameterParserForSort;
private readonly QueryParameterParser _queryParameterParserForAll;
private readonly QueryStringReader _queryStringReaderForSort;
private readonly QueryStringReader _queryStringReaderForAll;

public QueryParserBenchmarks()
{
IJsonApiOptions options = new JsonApiOptions();
IJsonApiOptions options = new JsonApiOptions
{
EnableLegacyFilterNotation = true
};

IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);

var currentRequest = new CurrentRequest();
currentRequest.SetRequestResource(resourceGraph.GetResourceContext(typeof(BenchmarkResource)));

IResourceDefinitionProvider resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
var currentRequest = new CurrentRequest
{
PrimaryResource = resourceGraph.GetResourceContext(typeof(BenchmarkResource))
};

_queryParameterParserForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, currentRequest, resourceDefinitionProvider, options, _queryStringAccessor);
_queryParameterParserForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, currentRequest, resourceDefinitionProvider, options, _queryStringAccessor);
_queryStringReaderForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, currentRequest, options, _queryStringAccessor);
_queryStringReaderForAll = CreateQueryParameterDiscoveryForAll(resourceGraph, currentRequest, options, _queryStringAccessor);
}

private static QueryParameterParser CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph,
CurrentRequest currentRequest, IResourceDefinitionProvider resourceDefinitionProvider,
private static QueryStringReader CreateQueryParameterDiscoveryForSort(IResourceGraph resourceGraph,
CurrentRequest currentRequest,
IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
{
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);

var queryServices = new List<IQueryParameterService>
var sortReader = new SortQueryStringParameterReader(currentRequest, resourceGraph);
var readers = new List<IQueryStringParameterReader>
{
sortService
sortReader
};

return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
}

private static QueryParameterParser CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
CurrentRequest currentRequest, IResourceDefinitionProvider resourceDefinitionProvider,
IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGraph resourceGraph,
CurrentRequest currentRequest, IJsonApiOptions options, FakeRequestQueryStringAccessor queryStringAccessor)
{
IIncludeService includeService = new IncludeService(resourceGraph, currentRequest);
IFilterService filterService = new FilterService(resourceDefinitionProvider, resourceGraph, currentRequest);
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);
ISparseFieldsService sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);
IPageService pageService = new PageService(options, resourceGraph, currentRequest);
IDefaultsService defaultsService = new DefaultsService(options);
INullsService nullsService = new NullsService(options);

var queryServices = new List<IQueryParameterService>
var resourceFactory = new ResourceFactory(new ServiceContainer());

var filterReader = new FilterQueryStringParameterReader(currentRequest, resourceGraph, resourceFactory, options);
var sortReader = new SortQueryStringParameterReader(currentRequest, resourceGraph);
var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(currentRequest, resourceGraph);
var paginationReader = new PaginationQueryStringParameterReader(currentRequest, resourceGraph, options);
var defaultsReader = new DefaultsQueryStringParameterReader(options);
var nullsReader = new NullsQueryStringParameterReader(options);

var readers = new List<IQueryStringParameterReader>
{
includeService, filterService, sortService, sparseFieldsService, pageService, defaultsService,
nullsService
filterReader, sortReader, sparseFieldSetReader, paginationReader, defaultsReader, nullsReader
};

return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
}

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

_queryStringAccessor.SetQueryString(queryString);
_queryParameterParserForSort.Parse(null);
_queryStringReaderForSort.ReadAll(null);
}

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

_queryStringAccessor.SetQueryString(queryString);
_queryParameterParserForSort.Parse(null);
_queryStringReaderForSort.ReadAll(null);
}

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

_queryStringAccessor.SetQueryString(queryString);
_queryParameterParserForAll.Parse(null);
_queryStringReaderForAll.ReadAll(null);
});

private void Run(int iterations, Action action) {
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public JsonApiDeserializerBenchmarks()
var options = new JsonApiOptions();
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
var targetedFields = new TargetedFields();
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new DefaultResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, new ResourceFactory(new ServiceContainer()), targetedFields, new HttpContextAccessor());
}

[Benchmark]
Expand Down
22 changes: 14 additions & 8 deletions benchmarks/Serialization/JsonApiSerializerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
using BenchmarkDotNet.Attributes;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Managers;
using JsonApiDotNetCore.Query;
using JsonApiDotNetCore.Internal.QueryStrings;
using JsonApiDotNetCore.Queries;
using JsonApiDotNetCore.RequestServices;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Serialization.Server;
using JsonApiDotNetCore.Serialization.Server.Builders;
Expand All @@ -14,7 +15,7 @@ namespace Benchmarks.Serialization
[MarkdownExporter]
public class JsonApiSerializerBenchmarks
{
private static readonly BenchmarkResource Content = new BenchmarkResource
private static readonly BenchmarkResource _content = new BenchmarkResource
{
Id = 123,
Name = Guid.NewGuid().ToString()
Expand All @@ -40,14 +41,19 @@ public JsonApiSerializerBenchmarks()

private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)
{
var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);
var currentRequest = new CurrentRequest();
var sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);

return new FieldsToSerialize(resourceGraph, sparseFieldsService, resourceDefinitionProvider);

var constraintProviders = new IQueryConstraintProvider[]
{
new SparseFieldSetQueryStringParameterReader(currentRequest, resourceGraph)
};

var resourceDefinitionProvider = DependencyFactory.CreateResourceDefinitionProvider(resourceGraph);

return new FieldsToSerialize(resourceGraph, constraintProviders, resourceDefinitionProvider);
}

[Benchmark]
public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content);
public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(_content);
}
}
6 changes: 3 additions & 3 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ This section documents the package API and is generated from the XML source comm

## Common APIs

- [`JsonApiOptions`](JsonApiDotNetCore.Configuration.JsonApiOptions.html)
- [`IResourceGraph`](JsonApiDotNetCore.Internal.Contracts.IResourceGraph.html)
- [`ResourceDefinition<T>`](JsonApiDotNetCore.Models.ResourceDefinition-1.html)
- [`JsonApiOptions`](JsonApiDotNetCore.Configuration.JsonApiOptions.yml)
- [`IResourceGraph`](JsonApiDotNetCore.Internal.Contracts.IResourceGraph.yml)
- [`ResourceDefinition<TResource>`](JsonApiDotNetCore.Models.ResourceDefinition-1.yml)
1 change: 1 addition & 0 deletions docs/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"getting-started/**/toc.yml",
"usage/**.md",
"request-examples/**.md",
"internals/**.md",
"toc.yml",
"*.md"
]
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Eliminate CRUD boilerplate and provide the following features across your resour
- Filtering
- Sorting
- Pagination
- Sparse field selection
- Sparse fieldset selection
- Relationship inclusion and navigation

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

### 2. Extensibility

Expand Down
3 changes: 3 additions & 0 deletions docs/internals/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Internals

The section contains overviews for the inner workings of the JsonApiDotNetCore library.
Loading