Skip to content

Commit 99f106f

Browse files
authored
Merge pull request #1286 from json-api-dotnet/query-string-extensibility
Extensible query string functions
2 parents fe64052 + 38a4a83 commit 99f106f

File tree

207 files changed

+8004
-2628
lines changed

Some content is hidden

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

207 files changed

+8004
-2628
lines changed

benchmarks/QueryString/QueryStringParserBenchmarks.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
using JsonApiDotNetCore;
55
using JsonApiDotNetCore.Configuration;
66
using JsonApiDotNetCore.Middleware;
7+
using JsonApiDotNetCore.Queries.Parsing;
78
using JsonApiDotNetCore.QueryStrings;
8-
using JsonApiDotNetCore.QueryStrings.Internal;
99
using JsonApiDotNetCore.Resources;
1010
using Microsoft.Extensions.Logging.Abstractions;
1111

@@ -37,11 +37,23 @@ public QueryStringParserBenchmarks()
3737

3838
var resourceFactory = new ResourceFactory(new ServiceContainer());
3939

40-
var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
41-
var filterReader = new FilterQueryStringParameterReader(request, resourceGraph, resourceFactory, options);
42-
var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
43-
var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(request, resourceGraph);
44-
var paginationReader = new PaginationQueryStringParameterReader(request, resourceGraph, options);
40+
var includeParser = new IncludeParser(options);
41+
var includeReader = new IncludeQueryStringParameterReader(includeParser, request, resourceGraph);
42+
43+
var filterScopeParser = new QueryStringParameterScopeParser();
44+
var filterValueParser = new FilterParser(resourceFactory);
45+
var filterReader = new FilterQueryStringParameterReader(filterScopeParser, filterValueParser, request, resourceGraph, options);
46+
47+
var sortScopeParser = new QueryStringParameterScopeParser();
48+
var sortValueParser = new SortParser();
49+
var sortReader = new SortQueryStringParameterReader(sortScopeParser, sortValueParser, request, resourceGraph);
50+
51+
var sparseFieldSetScopeParser = new SparseFieldTypeParser(resourceGraph);
52+
var sparseFieldSetValueParser = new SparseFieldSetParser();
53+
var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(sparseFieldSetScopeParser, sparseFieldSetValueParser, request, resourceGraph);
54+
55+
var paginationParser = new PaginationParser();
56+
var paginationReader = new PaginationQueryStringParameterReader(paginationParser, request, resourceGraph, options);
4557

4658
IQueryStringParameterReader[] readers = ArrayFactory.Create<IQueryStringParameterReader>(includeReader, filterReader, sortReader,
4759
sparseFieldSetReader, paginationReader);

benchmarks/Serialization/OperationsSerializationBenchmarks.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using JsonApiDotNetCore.Configuration;
44
using JsonApiDotNetCore.Middleware;
55
using JsonApiDotNetCore.Queries;
6-
using JsonApiDotNetCore.Queries.Internal;
76
using JsonApiDotNetCore.Resources;
87
using JsonApiDotNetCore.Serialization.Objects;
98

benchmarks/Serialization/ResourceSerializationBenchmarks.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using JsonApiDotNetCore.Middleware;
77
using JsonApiDotNetCore.Queries;
88
using JsonApiDotNetCore.Queries.Expressions;
9-
using JsonApiDotNetCore.Queries.Internal;
109
using JsonApiDotNetCore.Resources.Annotations;
1110
using JsonApiDotNetCore.Serialization.Objects;
1211

benchmarks/Serialization/SerializationBenchmarkBase.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using JsonApiDotNetCore.Configuration;
66
using JsonApiDotNetCore.Middleware;
77
using JsonApiDotNetCore.Queries;
8-
using JsonApiDotNetCore.Queries.Internal;
98
using JsonApiDotNetCore.Resources;
109
using JsonApiDotNetCore.Resources.Annotations;
1110
using JsonApiDotNetCore.Serialization.Response;

benchmarks/Tools/NeverResourceDefinitionAccessor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Middleware;
44
using JsonApiDotNetCore.Queries.Expressions;
5+
using JsonApiDotNetCore.Queries.QueryableBuilding;
56
using JsonApiDotNetCore.Resources;
67
using JsonApiDotNetCore.Resources.Annotations;
78

@@ -13,6 +14,7 @@ namespace Benchmarks.Tools;
1314
internal sealed class NeverResourceDefinitionAccessor : IResourceDefinitionAccessor
1415
{
1516
bool IResourceDefinitionAccessor.IsReadOnlyRequest => throw new NotImplementedException();
17+
public IQueryableBuilder QueryableBuilder => throw new NotImplementedException();
1618

1719
public IImmutableSet<IncludeElementExpression> OnApplyIncludes(ResourceType resourceType, IImmutableSet<IncludeElementExpression> existingIncludes)
1820
{

src/Examples/NoEntityFrameworkExample/QueryLayerToLinqConverter.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
using System.Collections;
22
using System.Linq.Expressions;
33
using JsonApiDotNetCore.Queries;
4-
using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
4+
using JsonApiDotNetCore.Queries.QueryableBuilding;
55
using JsonApiDotNetCore.Resources;
66
using Microsoft.EntityFrameworkCore.Metadata;
77

88
namespace NoEntityFrameworkExample;
99

1010
internal sealed class QueryLayerToLinqConverter
1111
{
12-
private readonly IResourceFactory _resourceFactory;
1312
private readonly IModel _model;
13+
private readonly IQueryableBuilder _queryableBuilder;
1414

15-
public QueryLayerToLinqConverter(IResourceFactory resourceFactory, IModel model)
15+
public QueryLayerToLinqConverter(IModel model, IQueryableBuilder queryableBuilder)
1616
{
17-
_resourceFactory = resourceFactory;
1817
_model = model;
18+
_queryableBuilder = queryableBuilder;
1919
}
2020

2121
public IEnumerable<TResource> ApplyQueryLayer<TResource>(QueryLayer queryLayer, IEnumerable<TResource> resources)
@@ -26,10 +26,9 @@ public IEnumerable<TResource> ApplyQueryLayer<TResource>(QueryLayer queryLayer,
2626
converter.ConvertIncludesToSelections();
2727

2828
// Convert QueryLayer into LINQ expression.
29-
Expression source = ((IEnumerable)resources).AsQueryable().Expression;
30-
var nameFactory = new LambdaParameterNameFactory();
31-
var queryableBuilder = new QueryableBuilder(source, queryLayer.ResourceType.ClrType, typeof(Enumerable), nameFactory, _resourceFactory, _model);
32-
Expression expression = queryableBuilder.ApplyQuery(queryLayer);
29+
IQueryable source = ((IEnumerable)resources).AsQueryable();
30+
var context = QueryableBuilderContext.CreateRoot(source, typeof(Enumerable), _model, null);
31+
Expression expression = _queryableBuilder.ApplyQuery(queryLayer, context);
3332

3433
// Insert null checks to prevent a NullReferenceException during execution of expressions such as:
3534
// 'todoItems => todoItems.Where(todoItem => todoItem.Assignee.Id == 1)' when a TodoItem doesn't have an assignee.

src/Examples/NoEntityFrameworkExample/Repositories/InMemoryResourceRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using JsonApiDotNetCore.Configuration;
22
using JsonApiDotNetCore.Queries;
33
using JsonApiDotNetCore.Queries.Expressions;
4-
using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
4+
using JsonApiDotNetCore.Queries.QueryableBuilding;
55
using JsonApiDotNetCore.Repositories;
66
using JsonApiDotNetCore.Resources;
77
using NoEntityFrameworkExample.Data;
@@ -25,12 +25,12 @@ public abstract class InMemoryResourceRepository<TResource, TId> : IResourceRead
2525
private readonly ResourceType _resourceType;
2626
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter;
2727

28-
protected InMemoryResourceRepository(IResourceGraph resourceGraph, IResourceFactory resourceFactory)
28+
protected InMemoryResourceRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
2929
{
3030
_resourceType = resourceGraph.GetResourceType<TResource>();
3131

3232
var model = new InMemoryModel(resourceGraph);
33-
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(resourceFactory, model);
33+
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(model, queryableBuilder);
3434
}
3535

3636
/// <inheritdoc />

src/Examples/NoEntityFrameworkExample/Repositories/PersonRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
3-
using JsonApiDotNetCore.Resources;
3+
using JsonApiDotNetCore.Queries.QueryableBuilding;
44
using NoEntityFrameworkExample.Data;
55
using NoEntityFrameworkExample.Models;
66

@@ -9,8 +9,8 @@ namespace NoEntityFrameworkExample.Repositories;
99
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
1010
public sealed class PersonRepository : InMemoryResourceRepository<Person, long>
1111
{
12-
public PersonRepository(IResourceGraph resourceGraph, IResourceFactory resourceFactory)
13-
: base(resourceGraph, resourceFactory)
12+
public PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
13+
: base(resourceGraph, queryableBuilder)
1414
{
1515
}
1616

src/Examples/NoEntityFrameworkExample/Repositories/TagRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
3-
using JsonApiDotNetCore.Resources;
3+
using JsonApiDotNetCore.Queries.QueryableBuilding;
44
using NoEntityFrameworkExample.Data;
55
using NoEntityFrameworkExample.Models;
66

@@ -9,8 +9,8 @@ namespace NoEntityFrameworkExample.Repositories;
99
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
1010
public sealed class TagRepository : InMemoryResourceRepository<Tag, long>
1111
{
12-
public TagRepository(IResourceGraph resourceGraph, IResourceFactory resourceFactory)
13-
: base(resourceGraph, resourceFactory)
12+
public TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
13+
: base(resourceGraph, queryableBuilder)
1414
{
1515
}
1616

src/Examples/NoEntityFrameworkExample/Repositories/TodoItemRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
3-
using JsonApiDotNetCore.Resources;
3+
using JsonApiDotNetCore.Queries.QueryableBuilding;
44
using NoEntityFrameworkExample.Data;
55
using NoEntityFrameworkExample.Models;
66

@@ -9,8 +9,8 @@ namespace NoEntityFrameworkExample.Repositories;
99
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
1010
public sealed class TodoItemRepository : InMemoryResourceRepository<TodoItem, long>
1111
{
12-
public TodoItemRepository(IResourceGraph resourceGraph, IResourceFactory resourceFactory)
13-
: base(resourceGraph, resourceFactory)
12+
public TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
13+
: base(resourceGraph, queryableBuilder)
1414
{
1515
}
1616

src/Examples/NoEntityFrameworkExample/Services/InMemoryResourceService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using JsonApiDotNetCore.Errors;
44
using JsonApiDotNetCore.Queries;
55
using JsonApiDotNetCore.Queries.Expressions;
6-
using JsonApiDotNetCore.Queries.Internal.QueryableBuilding;
6+
using JsonApiDotNetCore.Queries.QueryableBuilding;
77
using JsonApiDotNetCore.Resources;
88
using JsonApiDotNetCore.Resources.Annotations;
99
using JsonApiDotNetCore.Services;
@@ -42,7 +42,7 @@ public abstract class InMemoryResourceService<TResource, TId> : IResourceQuerySe
4242
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter;
4343

4444
protected InMemoryResourceService(IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer,
45-
IResourceFactory resourceFactory, IPaginationContext paginationContext, IEnumerable<IQueryConstraintProvider> constraintProviders,
45+
IPaginationContext paginationContext, IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder,
4646
ILoggerFactory loggerFactory)
4747
{
4848
_options = options;
@@ -54,7 +54,7 @@ protected InMemoryResourceService(IJsonApiOptions options, IResourceGraph resour
5454
_resourceType = resourceGraph.GetResourceType<TResource>();
5555

5656
var model = new InMemoryModel(resourceGraph);
57-
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(resourceFactory, model);
57+
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(model, queryableBuilder);
5858
}
5959

6060
/// <inheritdoc />

src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Queries;
4+
using JsonApiDotNetCore.Queries.QueryableBuilding;
45
using JsonApiDotNetCore.Resources;
56
using NoEntityFrameworkExample.Data;
67
using NoEntityFrameworkExample.Models;
@@ -10,9 +11,9 @@ namespace NoEntityFrameworkExample.Services;
1011
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
1112
public sealed class TodoItemService : InMemoryResourceService<TodoItem, long>
1213
{
13-
public TodoItemService(IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IResourceFactory resourceFactory,
14-
IPaginationContext paginationContext, IEnumerable<IQueryConstraintProvider> constraintProviders, ILoggerFactory loggerFactory)
15-
: base(options, resourceGraph, queryLayerComposer, resourceFactory, paginationContext, constraintProviders, loggerFactory)
14+
public TodoItemService(IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext,
15+
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, ILoggerFactory loggerFactory)
16+
: base(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder, loggerFactory)
1617
{
1718
}
1819

src/JsonApiDotNetCore.Annotations/Resources/Identifiable.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.ComponentModel.DataAnnotations.Schema;
2-
using JsonApiDotNetCore.Resources.Internal;
32

43
namespace JsonApiDotNetCore.Resources;
54

src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs renamed to src/JsonApiDotNetCore.Annotations/Resources/RuntimeTypeConverter.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,31 @@
33

44
#pragma warning disable AV1008 // Class should not be static
55

6-
namespace JsonApiDotNetCore.Resources.Internal;
6+
namespace JsonApiDotNetCore.Resources;
77

8+
/// <summary>
9+
/// Provides utilities regarding runtime types.
10+
/// </summary>
811
[PublicAPI]
912
public static class RuntimeTypeConverter
1013
{
1114
private const string ParseQueryStringsUsingCurrentCultureSwitchName = "JsonApiDotNetCore.ParseQueryStringsUsingCurrentCulture";
1215

16+
/// <summary>
17+
/// Converts the specified value to the specified type.
18+
/// </summary>
19+
/// <param name="value">
20+
/// The value to convert from.
21+
/// </param>
22+
/// <param name="type">
23+
/// The type to convert to.
24+
/// </param>
25+
/// <returns>
26+
/// The converted type, or <c>null</c> if <paramref name="value" /> is <c>null</c> and <paramref name="type" /> is a nullable type.
27+
/// </returns>
28+
/// <exception cref="FormatException">
29+
/// <paramref name="value" /> is not compatible with <paramref name="type" />.
30+
/// </exception>
1331
public static object? ConvertType(object? value, Type type)
1432
{
1533
ArgumentGuard.NotNull(type);
@@ -114,11 +132,20 @@ public static class RuntimeTypeConverter
114132
}
115133
}
116134

135+
/// <summary>
136+
/// Indicates whether the specified type is a nullable value type or a reference type.
137+
/// </summary>
117138
public static bool CanContainNull(Type type)
118139
{
119140
return !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
120141
}
121142

143+
/// <summary>
144+
/// Gets the default value for the specified type.
145+
/// </summary>
146+
/// <returns>
147+
/// The default value, or <c>null</c> for nullable value types and reference types.
148+
/// </returns>
122149
public static object? GetDefaultValue(Type type)
123150
{
124151
return type.IsValueType ? Activator.CreateInstance(type) : null;

src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Errors;
44
using JsonApiDotNetCore.Middleware;
5-
using JsonApiDotNetCore.Queries.Internal;
5+
using JsonApiDotNetCore.Queries;
66
using JsonApiDotNetCore.Resources;
77
using JsonApiDotNetCore.Serialization.Objects;
88

src/JsonApiDotNetCore/CollectionExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ public static int FindIndex<T>(this IReadOnlyList<T> source, Predicate<T> match)
3232
return -1;
3333
}
3434

35+
public static IEnumerable<T> ToEnumerable<T>(this LinkedListNode<T>? startNode)
36+
{
37+
LinkedListNode<T>? current = startNode;
38+
39+
while (current != null)
40+
{
41+
yield return current.Value;
42+
43+
current = current.Next;
44+
}
45+
}
46+
3547
public static bool DictionaryEqual<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue>? first, IReadOnlyDictionary<TKey, TValue>? second,
3648
IEqualityComparer<TValue>? valueComparer = null)
3749
{

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
using JsonApiDotNetCore.AtomicOperations.Processors;
33
using JsonApiDotNetCore.Middleware;
44
using JsonApiDotNetCore.Queries;
5-
using JsonApiDotNetCore.Queries.Internal;
5+
using JsonApiDotNetCore.Queries.Parsing;
6+
using JsonApiDotNetCore.Queries.QueryableBuilding;
67
using JsonApiDotNetCore.QueryStrings;
7-
using JsonApiDotNetCore.QueryStrings.Internal;
88
using JsonApiDotNetCore.Repositories;
99
using JsonApiDotNetCore.Resources;
1010
using JsonApiDotNetCore.Serialization.JsonConverters;
@@ -193,6 +193,13 @@ private void AddRepositoryLayer()
193193
RegisterImplementationForInterfaces(ServiceDiscoveryFacade.RepositoryUnboundInterfaces, typeof(EntityFrameworkCoreRepository<,>));
194194

195195
_services.AddScoped<IResourceRepositoryAccessor, ResourceRepositoryAccessor>();
196+
197+
_services.TryAddTransient<IQueryableBuilder, QueryableBuilder>();
198+
_services.TryAddTransient<IIncludeClauseBuilder, IncludeClauseBuilder>();
199+
_services.TryAddTransient<IOrderClauseBuilder, OrderClauseBuilder>();
200+
_services.TryAddTransient<ISelectClauseBuilder, SelectClauseBuilder>();
201+
_services.TryAddTransient<ISkipTakeClauseBuilder, SkipTakeClauseBuilder>();
202+
_services.TryAddTransient<IWhereClauseBuilder, WhereClauseBuilder>();
196203
}
197204

198205
private void AddServiceLayer()
@@ -210,6 +217,14 @@ private void RegisterImplementationForInterfaces(HashSet<Type> unboundInterfaces
210217

211218
private void AddQueryStringLayer()
212219
{
220+
_services.TryAddTransient<IQueryStringParameterScopeParser, QueryStringParameterScopeParser>();
221+
_services.TryAddTransient<IIncludeParser, IncludeParser>();
222+
_services.TryAddTransient<IFilterParser, FilterParser>();
223+
_services.TryAddTransient<ISortParser, SortParser>();
224+
_services.TryAddTransient<ISparseFieldTypeParser, SparseFieldTypeParser>();
225+
_services.TryAddTransient<ISparseFieldSetParser, SparseFieldSetParser>();
226+
_services.TryAddTransient<IPaginationParser, PaginationParser>();
227+
213228
_services.AddScoped<IIncludeQueryStringParameterReader, IncludeQueryStringParameterReader>();
214229
_services.AddScoped<IFilterQueryStringParameterReader, FilterQueryStringParameterReader>();
215230
_services.AddScoped<ISortQueryStringParameterReader, SortQueryStringParameterReader>();

src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs renamed to src/JsonApiDotNetCore/Queries/EvaluatedIncludeCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using JsonApiDotNetCore.Queries.Expressions;
22

3-
namespace JsonApiDotNetCore.Queries.Internal;
3+
namespace JsonApiDotNetCore.Queries;
44

55
/// <inheritdoc />
66
internal sealed class EvaluatedIncludeCache : IEvaluatedIncludeCache

0 commit comments

Comments
 (0)