Skip to content

Refactorings #1038

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

Merged
merged 29 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c97d6c4
Renamed CollectionNotEmptyExpression to HasExpression
Jul 30, 2021
5e54e4e
Renamed EqualsAnyOfExpression to AnyExpression
Jul 30, 2021
b408bad
Changed SparseFieldSetExpression.Fields type from IReadOnlyCollection…
Aug 2, 2021
48f8954
Changed SparseFieldTableExpression.Table type from IReadOnlyDictionar…
Aug 2, 2021
ae4a57b
Changed SortExpression.Elements type from IReadOnlyDictionary to IImm…
Aug 2, 2021
d0f7cc1
Changed ResourceFieldChainExpression.Fields type from IReadOnlyCollec…
Aug 2, 2021
8dddf5c
Resharper quickfix: Use negated pattern
Aug 2, 2021
159b99f
Changed PaginationQueryStringValueExpression.Elements type from IRead…
Aug 3, 2021
866c8b1
Changed AnyExpression.Constants type from IReadOnlyCollection to IImm…
Aug 3, 2021
d56c7d3
Changed LogicalExpression.Terms type from IReadOnlyCollection to IImm…
Aug 3, 2021
5d164f0
Changed IncludeExpression.Elements type and IncludeElementExpression.…
Aug 3, 2021
dfee19f
Add support for `IReadOnlySet<>` usage in resource models
Aug 3, 2021
56cf588
Changed DisableQueryStringAttribute.ParameterNames type from IReadOnl…
Aug 3, 2021
1c2726b
Changed IResourceContextProvider.GetResourceContexts() to return a se…
Aug 3, 2021
1f56ae8
Updated documentation
Aug 3, 2021
031af89
Fix cibuild
Aug 3, 2021
a925aa0
Use "endpoint" in RequestMethodNotAllowedException message
Aug 4, 2021
4a955a4
Renamed StandardQueryStringParameters to JsonApiQueryStringParameters
Aug 4, 2021
07a74e8
Replaced obsolete IQueryLayerComposer.GetResourceDefinitionAccessor()…
Aug 4, 2021
680e809
Replaced obsolete IResourceFactory.GetResourceDefinitionAccessor() wi…
Aug 4, 2021
52b3773
Made TId in IResourceDefinition contravariant
Aug 4, 2021
2c222b1
Corrected terminology: relationships use left/right instead of primar…
Aug 4, 2021
a1bef68
Removed obsolete IJsonApiRequest.BasePath
Aug 4, 2021
d6b1f9d
Removed EntityFrameworkCoreSupport (no longer needed for EF Core 5+)
Aug 4, 2021
f23ab45
cibuild fixes
Aug 4, 2021
b277e1f
Always set IJsonApiRequest.OperationKind from middleware
Aug 4, 2021
0b15d45
Renamed OperationKind to WriteOperationKind, exposed as IJsonApiReque…
Aug 4, 2021
76dbf36
Automated Code cleanup
Aug 4, 2021
0c576be
Add HTTP Method to trace logging
Aug 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ public JsonApiDeserializerBenchmarks()
IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options);

var serviceContainer = new ServiceContainer();
serviceContainer.AddService(typeof(IResourceDefinitionAccessor), new ResourceDefinitionAccessor(resourceGraph, serviceContainer));
var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);

serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
serviceContainer.AddService(typeof(IResourceDefinition<BenchmarkResource>), new JsonApiResourceDefinition<BenchmarkResource>(resourceGraph));

var targetedFields = new TargetedFields();
var request = new JsonApiRequest();
var resourceFactory = new ResourceFactory(serviceContainer);
var httpContextAccessor = new HttpContextAccessor();

_jsonApiDeserializer = new RequestDeserializer(resourceGraph, resourceFactory, targetedFields, httpContextAccessor, request, options);
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, resourceFactory, targetedFields, httpContextAccessor, request, options,
resourceDefinitionAccessor);
}

[Benchmark]
Expand Down
2 changes: 1 addition & 1 deletion docs/internals/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Processing a request involves the following steps:
- The readers also implement `IQueryConstraintProvider`, which exposes expressions through `ExpressionInScope` objects.
- `QueryLayerComposer` (used from `JsonApiResourceService`) collects all query constraints.
- It combines them with default options and `IResourceDefinition` overrides and composes a tree of `QueryLayer` objects.
- It lifts the tree for nested endpoints like /blogs/1/articles and rewrites includes.
- It lifts the tree for secondary endpoints like /blogs/1/articles and rewrites includes.
- `JsonApiResourceService` contains no more usage of `IQueryable`.
- `EntityFrameworkCoreRepository` delegates to `QueryableBuilder` to transform the `QueryLayer` tree into `IQueryable` expression trees.
`QueryBuilder` depends on `QueryClauseBuilder` implementations that visit the tree nodes, transforming them to `System.Linq.Expression` equivalents.
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/extensibility/resource-definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ public class EmployeeDefinition : JsonApiResourceDefinition<Employee>
{
}

public override IReadOnlyCollection<IncludeElementExpression> OnApplyIncludes(
IReadOnlyCollection<IncludeElementExpression> existingIncludes)
public override IImmutableList<IncludeElementExpression> OnApplyIncludes(
IImmutableList<IncludeElementExpression> existingIncludes)
{
if (existingIncludes.Any(include =>
include.Relationship.Property.Name == nameof(Employee.Manager)))
Expand Down
5 changes: 3 additions & 2 deletions docs/usage/extensibility/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ public class TodoItemService : JsonApiResourceService<TodoItem>
public TodoItemService(IResourceRepositoryAccessor repositoryAccessor,
IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext,
IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request,
IResourceChangeTracker<TodoItem> resourceChangeTracker)
IResourceChangeTracker<TodoItem> resourceChangeTracker,
IResourceDefinitionAccessor resourceDefinitionAccessor)
: base(repositoryAccessor, queryLayerComposer, paginationContext, options,
loggerFactory, request, resourceChangeTracker)
loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor)
{
_notificationService = notificationService;
}
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/reading/including-relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ which is equivalent to:
GET /api/articles?include=author&include=author.livingAddress&include=author.livingAddress.country
```

This can be used on nested endpoints too:
This can be used on secondary endpoints too:

```http
GET /api/blogs/1/articles?include=author.livingAddress.country
Expand Down
4 changes: 1 addition & 3 deletions docs/usage/reading/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ Resources can be paginated. This request would fetch the second page of 10 artic
GET /articles?page[size]=10&page[number]=2 HTTP/1.1
```

## Nesting

Pagination can be used on nested endpoints, such as:
Pagination can be used on secondary endpoints, such as:

```http
GET /blogs/1/articles?page[number]=2 HTTP/1.1
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/reading/sorting.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ GET /api/blogs?sort=count(articles) HTTP/1.1

This sorts the list of blogs by their number of articles.

## Nesting
## Secondary endpoints

Sorting can be used on nested endpoints, such as:
Sorting can be used on secondary endpoints, such as:

```http
GET /api/blogs/1/articles?sort=caption HTTP/1.1
Expand Down
6 changes: 3 additions & 3 deletions docs/usage/reading/sparse-fieldset-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

As an alternative to returning all fields (attributes and relationships) from a resource, the `fields[]` query string parameter can be used to select a subset.
Put the resource type to apply the fieldset on between the brackets.
This can be used on the resource being requested, as well as on nested endpoints and/or included resources.
This can be used on primary and secondary endpoints. The selection is applied on both primary and included resources.

Top-level example:
Primary endpoint example:

```http
GET /articles?fields[articles]=title,body,comments HTTP/1.1
```

Nested endpoint example:
Secondary endpoint example:

```http
GET /api/blogs/1/articles?fields[articles]=title,body,comments HTTP/1.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ private SortExpression GetDefaultSortOrder()
});
}

public override Task OnWritingAsync(TodoItem resource, OperationKind operationKind, CancellationToken cancellationToken)
public override Task OnWritingAsync(TodoItem resource, WriteOperationKind writeOperation, CancellationToken cancellationToken)
{
if (operationKind == OperationKind.CreateResource)
if (writeOperation == WriteOperationKind.CreateResource)
{
resource.CreatedAt = _systemClock.UtcNow;
}
else if (operationKind == OperationKind.UpdateResource)
else if (writeOperation == WriteOperationKind.UpdateResource)
{
resource.LastModifiedAt = _systemClock.UtcNow;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ public sealed class DbContextARepository<TResource> : EntityFrameworkCoreReposit
where TResource : class, IIdentifiable<int>
{
public DbContextARepository(ITargetedFields targetedFields, DbContextResolver<DbContextA> contextResolver, IResourceGraph resourceGraph,
IResourceFactory resourceFactory, IEnumerable<IQueryConstraintProvider> constraintProviders, ILoggerFactory loggerFactory)
: base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory)
IResourceFactory resourceFactory, IEnumerable<IQueryConstraintProvider> constraintProviders, ILoggerFactory loggerFactory,
IResourceDefinitionAccessor resourceDefinitionAccessor)
: base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ public sealed class DbContextBRepository<TResource> : EntityFrameworkCoreReposit
where TResource : class, IIdentifiable<int>
{
public DbContextBRepository(ITargetedFields targetedFields, DbContextResolver<DbContextB> contextResolver, IResourceGraph resourceGraph,
IResourceFactory resourceFactory, IEnumerable<IQueryConstraintProvider> constraintProviders, ILoggerFactory loggerFactory)
: base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory)
IResourceFactory resourceFactory, IEnumerable<IQueryConstraintProvider> constraintProviders, ILoggerFactory loggerFactory,
IResourceDefinitionAccessor resourceDefinitionAccessor)
: base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ public async Task<WorkItem> CreateAsync(WorkItem resource, CancellationToken can
return workItems.Single();
}

public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet<IIdentifiable> secondaryResourceIds,
CancellationToken cancellationToken)
public Task AddToToManyRelationshipAsync(int leftId, string relationshipName, ISet<IIdentifiable> rightResourceIds, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
Expand All @@ -84,7 +83,7 @@ public Task<WorkItem> UpdateAsync(int id, WorkItem resource, CancellationToken c
throw new NotImplementedException();
}

public Task SetRelationshipAsync(int primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken)
public Task SetRelationshipAsync(int leftId, string relationshipName, object rightValue, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
Expand All @@ -99,7 +98,7 @@ public async Task DeleteAsync(int id, CancellationToken cancellationToken)
}, cancellationToken: cancellationToken)));
}

public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet<IIdentifiable> secondaryResourceIds,
public Task RemoveFromToManyRelationshipAsync(int leftId, string relationshipName, ISet<IIdentifiable> rightResourceIds,
CancellationToken cancellationToken)
{
throw new NotImplementedException();
Expand Down
4 changes: 2 additions & 2 deletions src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void Validate(IEnumerable<OperationContainer> operations)

private void ValidateOperation(OperationContainer operation)
{
if (operation.Kind == OperationKind.CreateResource)
if (operation.Kind == WriteOperationKind.CreateResource)
{
DeclareLocalId(operation.Resource);
}
Expand All @@ -70,7 +70,7 @@ private void ValidateOperation(OperationContainer operation)
AssertLocalIdIsAssigned(secondaryResource);
}

if (operation.Kind == OperationKind.CreateResource)
if (operation.Kind == WriteOperationKind.CreateResource)
{
AssignLocalId(operation);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,37 @@ protected virtual IOperationProcessor ResolveProcessor(OperationContainer operat
return (IOperationProcessor)_serviceProvider.GetRequiredService(processorType);
}

private static Type GetProcessorInterface(OperationKind kind)
private static Type GetProcessorInterface(WriteOperationKind writeOperation)
{
switch (kind)
switch (writeOperation)
{
case OperationKind.CreateResource:
case WriteOperationKind.CreateResource:
{
return typeof(ICreateProcessor<,>);
}
case OperationKind.UpdateResource:
case WriteOperationKind.UpdateResource:
{
return typeof(IUpdateProcessor<,>);
}
case OperationKind.DeleteResource:
case WriteOperationKind.DeleteResource:
{
return typeof(IDeleteProcessor<,>);
}
case OperationKind.SetRelationship:
case WriteOperationKind.SetRelationship:
{
return typeof(ISetRelationshipProcessor<,>);
}
case OperationKind.AddToRelationship:
case WriteOperationKind.AddToRelationship:
{
return typeof(IAddToRelationshipProcessor<,>);
}
case OperationKind.RemoveFromRelationship:
case WriteOperationKind.RemoveFromRelationship:
{
return typeof(IRemoveFromRelationshipProcessor<,>);
}
default:
{
throw new NotSupportedException($"Unknown operation kind '{kind}'.");
throw new NotSupportedException($"Unknown write operation kind '{writeOperation}'.");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ protected virtual async Task<OperationContainer> ProcessOperationAsync(Operation

protected void TrackLocalIdsForOperation(OperationContainer operation)
{
if (operation.Kind == OperationKind.CreateResource)
if (operation.Kind == WriteOperationKind.CreateResource)
{
DeclareLocalId(operation.Resource);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public virtual async Task<OperationContainer> ProcessAsync(OperationContainer op
{
ArgumentGuard.NotNull(operation, nameof(operation));

var primaryId = (TId)operation.Resource.GetTypedId();
ISet<IIdentifiable> secondaryResourceIds = operation.GetSecondaryResources();
var leftId = (TId)operation.Resource.GetTypedId();
ISet<IIdentifiable> rightResourceIds = operation.GetSecondaryResources();

await _service.AddToToManyRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, secondaryResourceIds, cancellationToken);
await _service.AddToToManyRelationshipAsync(leftId, operation.Request.Relationship.PublicName, rightResourceIds, cancellationToken);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public virtual async Task<OperationContainer> ProcessAsync(OperationContainer op
{
ArgumentGuard.NotNull(operation, nameof(operation));

var primaryId = (TId)operation.Resource.GetTypedId();
ISet<IIdentifiable> secondaryResourceIds = operation.GetSecondaryResources();
var leftId = (TId)operation.Resource.GetTypedId();
ISet<IIdentifiable> rightResourceIds = operation.GetSecondaryResources();

await _service.RemoveFromToManyRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, secondaryResourceIds, cancellationToken);
await _service.RemoveFromToManyRelationshipAsync(leftId, operation.Request.Relationship.PublicName, rightResourceIds, cancellationToken);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public virtual async Task<OperationContainer> ProcessAsync(OperationContainer op
{
ArgumentGuard.NotNull(operation, nameof(operation));

var primaryId = (TId)operation.Resource.GetTypedId();
var leftId = (TId)operation.Resource.GetTypedId();
object rightValue = GetRelationshipRightValue(operation);

await _service.SetRelationshipAsync(primaryId, operation.Request.Relationship.PublicName, rightValue, cancellationToken);
await _service.SetRelationshipAsync(leftId, operation.Request.Relationship.PublicName, rightValue, cancellationToken);

return null;
}
Expand Down
18 changes: 9 additions & 9 deletions src/JsonApiDotNetCore/CollectionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ namespace JsonApiDotNetCore
{
internal sealed class CollectionConverter
{
private static readonly Type[] HashSetCompatibleCollectionTypes =
private static readonly ISet<Type> HashSetCompatibleCollectionTypes = new HashSet<Type>
{
typeof(HashSet<>),
typeof(ICollection<>),
typeof(ISet<>),
typeof(IEnumerable<>),
typeof(IReadOnlyCollection<>)
typeof(IReadOnlySet<>),
typeof(ICollection<>),
typeof(IReadOnlyCollection<>),
typeof(IEnumerable<>)
};

/// <summary>
Expand Down Expand Up @@ -45,19 +46,18 @@ public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType
/// <summary>
/// Returns a compatible collection type that can be instantiated, for example IList{Article} -> List{Article} or ISet{Article} -> HashSet{Article}
/// </summary>
public Type ToConcreteCollectionType(Type collectionType)
private Type ToConcreteCollectionType(Type collectionType)
{
if (collectionType.IsInterface && collectionType.IsGenericType)
{
Type genericTypeDefinition = collectionType.GetGenericTypeDefinition();
Type openCollectionType = collectionType.GetGenericTypeDefinition();

if (genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(ISet<>) ||
genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(IReadOnlyCollection<>))
if (HashSetCompatibleCollectionTypes.Contains(openCollectionType))
{
return typeof(HashSet<>).MakeGenericType(collectionType.GenericTypeArguments[0]);
}

if (genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyList<>))
if (openCollectionType == typeof(IList<>) || openCollectionType == typeof(IReadOnlyList<>))
{
return typeof(List<>).MakeGenericType(collectionType.GenericTypeArguments[0]);
}
Expand Down
38 changes: 37 additions & 1 deletion src/JsonApiDotNetCore/CollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
return !source.Any();
}

public static int FindIndex<T>(this IList<T> source, Predicate<T> match)
public static int FindIndex<T>(this IReadOnlyList<T> source, Predicate<T> match)
{
ArgumentGuard.NotNull(source, nameof(source));
ArgumentGuard.NotNull(match, nameof(match));
Expand All @@ -34,5 +34,41 @@ public static int FindIndex<T>(this IList<T> source, Predicate<T> match)

return -1;
}

public static bool DictionaryEqual<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> first, IReadOnlyDictionary<TKey, TValue> second,
IEqualityComparer<TValue> valueComparer = null)
{
if (first == second)
{
return true;
}

if (first == null || second == null)
{
return false;
}

if (first.Count != second.Count)
{
return false;
}

IEqualityComparer<TValue> effectiveValueComparer = valueComparer ?? EqualityComparer<TValue>.Default;

foreach ((TKey firstKey, TValue firstValue) in first)
{
if (!second.TryGetValue(firstKey, out TValue secondValue))
{
return false;
}

if (!effectiveValueComparer.Equals(firstValue, secondValue))
{
return false;
}
}

return true;
}
}
}
Loading