From c97d6c4e2c8d0781ac11564d007c3a7146fdd86c Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Fri, 30 Jul 2021 17:13:07 +0200 Subject: [PATCH 01/29] Renamed CollectionNotEmptyExpression to HasExpression --- .../{CollectionNotEmptyExpression.cs => HasExpression.cs} | 8 ++++---- .../Queries/Expressions/QueryExpressionRewriter.cs | 4 ++-- .../Queries/Expressions/QueryExpressionVisitor.cs | 2 +- .../Queries/Internal/Parsing/FilterParser.cs | 4 ++-- .../Internal/QueryableBuilding/WhereClauseBuilder.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename src/JsonApiDotNetCore/Queries/Expressions/{CollectionNotEmptyExpression.cs => HasExpression.cs} (85%) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs similarity index 85% rename from src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs rename to src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs index 5aaa958feb..97f3bb5544 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/CollectionNotEmptyExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs @@ -9,12 +9,12 @@ namespace JsonApiDotNetCore.Queries.Expressions /// Represents the "has" filter function, resulting from text such as: has(articles) or has(articles,equals(isHidden,'false')) /// [PublicAPI] - public class CollectionNotEmptyExpression : FilterExpression + public class HasExpression : FilterExpression { public ResourceFieldChainExpression TargetCollection { get; } public FilterExpression Filter { get; } - public CollectionNotEmptyExpression(ResourceFieldChainExpression targetCollection, FilterExpression filter) + public HasExpression(ResourceFieldChainExpression targetCollection, FilterExpression filter) { ArgumentGuard.NotNull(targetCollection, nameof(targetCollection)); @@ -24,7 +24,7 @@ public CollectionNotEmptyExpression(ResourceFieldChainExpression targetCollectio public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) { - return visitor.VisitCollectionNotEmpty(this, argument); + return visitor.VisitHas(this, argument); } public override string ToString() @@ -57,7 +57,7 @@ public override bool Equals(object obj) return false; } - var other = (CollectionNotEmptyExpression)obj; + var other = (HasExpression)obj; return TargetCollection.Equals(other.TargetCollection) && Equals(Filter, other.Filter); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 5def9a208a..004d12d586 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -85,7 +85,7 @@ public override QueryExpression VisitNot(NotExpression expression, TArgument arg return null; } - public override QueryExpression VisitCollectionNotEmpty(CollectionNotEmptyExpression expression, TArgument argument) + public override QueryExpression VisitHas(HasExpression expression, TArgument argument) { if (expression != null) { @@ -93,7 +93,7 @@ public override QueryExpression VisitCollectionNotEmpty(CollectionNotEmptyExpres { FilterExpression newFilter = expression.Filter != null ? Visit(expression.Filter, argument) as FilterExpression : null; - var newExpression = new CollectionNotEmptyExpression(newTargetCollection, newFilter); + var newExpression = new HasExpression(newTargetCollection, newFilter); return newExpression.Equals(expression) ? expression : newExpression; } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs index f16a7b0424..6d175cb132 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs @@ -48,7 +48,7 @@ public virtual TResult VisitNot(NotExpression expression, TArgument argument) return DefaultVisit(expression, argument); } - public virtual TResult VisitCollectionNotEmpty(CollectionNotEmptyExpression expression, TArgument argument) + public virtual TResult VisitHas(HasExpression expression, TArgument argument) { return DefaultVisit(expression, argument); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index d4a7f04acd..3688c2ba55 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -231,7 +231,7 @@ protected EqualsAnyOfExpression ParseAny() return new EqualsAnyOfExpression(targetAttribute, constants); } - protected CollectionNotEmptyExpression ParseHas() + protected HasExpression ParseHas() { EatText(Keywords.Has); EatSingleCharacterToken(TokenKind.OpenParen); @@ -248,7 +248,7 @@ protected CollectionNotEmptyExpression ParseHas() EatSingleCharacterToken(TokenKind.CloseParen); - return new CollectionNotEmptyExpression(targetCollection, filter); + return new HasExpression(targetCollection, filter); } private FilterExpression ParseFilterInHas(HasManyAttribute hasManyRelationship) diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs index d95d228c25..0b7fae26f7 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs @@ -56,7 +56,7 @@ private Expression WhereExtensionMethodCall(LambdaExpression predicate) return Expression.Call(_extensionType, "Where", LambdaScope.Parameter.Type.AsArray(), _source, predicate); } - public override Expression VisitCollectionNotEmpty(CollectionNotEmptyExpression expression, Type argument) + public override Expression VisitHas(HasExpression expression, Type argument) { Expression property = Visit(expression.TargetCollection, argument); From 5e54e4e388dfe2d43014c7a388f404235ee72bc3 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Fri, 30 Jul 2021 17:15:24 +0200 Subject: [PATCH 02/29] Renamed EqualsAnyOfExpression to AnyExpression --- .../{EqualsAnyOfExpression.cs => AnyExpression.cs} | 8 ++++---- .../Queries/Expressions/QueryExpressionRewriter.cs | 4 ++-- .../Queries/Expressions/QueryExpressionVisitor.cs | 2 +- .../Queries/Internal/Parsing/FilterParser.cs | 4 ++-- .../Queries/Internal/QueryLayerComposer.cs | 2 +- .../Internal/QueryableBuilding/WhereClauseBuilder.cs | 2 +- .../CompositeKeys/CarExpressionRewriter.cs | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename src/JsonApiDotNetCore/Queries/Expressions/{EqualsAnyOfExpression.cs => AnyExpression.cs} (87%) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs similarity index 87% rename from src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs rename to src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs index 5e723bb5bf..fe4c4bef76 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/EqualsAnyOfExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs @@ -11,12 +11,12 @@ namespace JsonApiDotNetCore.Queries.Expressions /// Represents the "any" filter function, resulting from text such as: any(name,'Jack','Joe') /// [PublicAPI] - public class EqualsAnyOfExpression : FilterExpression + public class AnyExpression : FilterExpression { public ResourceFieldChainExpression TargetAttribute { get; } public IReadOnlyCollection Constants { get; } - public EqualsAnyOfExpression(ResourceFieldChainExpression targetAttribute, IReadOnlyCollection constants) + public AnyExpression(ResourceFieldChainExpression targetAttribute, IReadOnlyCollection constants) { ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); ArgumentGuard.NotNull(constants, nameof(constants)); @@ -32,7 +32,7 @@ public EqualsAnyOfExpression(ResourceFieldChainExpression targetAttribute, IRead public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) { - return visitor.VisitEqualsAnyOf(this, argument); + return visitor.VisitAny(this, argument); } public override string ToString() @@ -61,7 +61,7 @@ public override bool Equals(object obj) return false; } - var other = (EqualsAnyOfExpression)obj; + var other = (AnyExpression)obj; return TargetAttribute.Equals(other.TargetAttribute) && Constants.SequenceEqual(other.Constants); } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 004d12d586..7abf814597 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -180,14 +180,14 @@ public override QueryExpression VisitMatchText(MatchTextExpression expression, T return null; } - public override QueryExpression VisitEqualsAnyOf(EqualsAnyOfExpression expression, TArgument argument) + public override QueryExpression VisitAny(AnyExpression expression, TArgument argument) { if (expression != null) { var newTargetAttribute = Visit(expression.TargetAttribute, argument) as ResourceFieldChainExpression; IReadOnlyCollection newConstants = VisitSequence(expression.Constants, argument); - var newExpression = new EqualsAnyOfExpression(newTargetAttribute, newConstants); + var newExpression = new AnyExpression(newTargetAttribute, newConstants); return newExpression.Equals(expression) ? expression : newExpression; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs index 6d175cb132..dad2958931 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionVisitor.cs @@ -78,7 +78,7 @@ public virtual TResult VisitMatchText(MatchTextExpression expression, TArgument return DefaultVisit(expression, argument); } - public virtual TResult VisitEqualsAnyOf(EqualsAnyOfExpression expression, TArgument argument) + public virtual TResult VisitAny(AnyExpression expression, TArgument argument) { return DefaultVisit(expression, argument); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index 3688c2ba55..358ae79985 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -187,7 +187,7 @@ protected MatchTextExpression ParseTextMatch(string matchFunctionName) return new MatchTextExpression(targetAttribute, constant, matchKind); } - protected EqualsAnyOfExpression ParseAny() + protected AnyExpression ParseAny() { EatText(Keywords.Any); EatSingleCharacterToken(TokenKind.OpenParen); @@ -228,7 +228,7 @@ protected EqualsAnyOfExpression ParseAny() } } - return new EqualsAnyOfExpression(targetAttribute, constants); + return new AnyExpression(targetAttribute, constants); } protected HasExpression ParseHas() diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 070faee6f2..f2dd01c4e8 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -310,7 +310,7 @@ private FilterExpression CreateFilterByIds(ICollection ids, AttrAttrib else if (ids.Count > 1) { List constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToList(); - filter = new EqualsAnyOfExpression(idChain, constants); + filter = new AnyExpression(idChain, constants); } // @formatter:keep_existing_linebreaks true diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs index 0b7fae26f7..8009328405 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs @@ -115,7 +115,7 @@ public override Expression VisitMatchText(MatchTextExpression expression, Type a return Expression.Call(property, "Contains", null, text); } - public override Expression VisitEqualsAnyOf(EqualsAnyOfExpression expression, Type argument) + public override Expression VisitAny(AnyExpression expression, Type argument) { Expression property = Visit(expression.TargetAttribute, argument); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs index f25b890546..53d2cc1d70 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs @@ -51,7 +51,7 @@ public override QueryExpression VisitComparison(ComparisonExpression expression, return base.VisitComparison(expression, argument); } - public override QueryExpression VisitEqualsAnyOf(EqualsAnyOfExpression expression, object argument) + public override QueryExpression VisitAny(AnyExpression expression, object argument) { PropertyInfo property = expression.TargetAttribute.Fields.Last().Property; @@ -61,7 +61,7 @@ public override QueryExpression VisitEqualsAnyOf(EqualsAnyOfExpression expressio return RewriteFilterOnCarStringIds(expression.TargetAttribute, carStringIds); } - return base.VisitEqualsAnyOf(expression, argument); + return base.VisitAny(expression, argument); } public override QueryExpression VisitMatchText(MatchTextExpression expression, object argument) From b408bad3c9b413bfcc8e9c92fc44910c01560442 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Mon, 2 Aug 2021 13:17:13 +0200 Subject: [PATCH 03/29] Changed SparseFieldSetExpression.Fields type from IReadOnlyCollection to IImmutableSet --- .../Expressions/SparseFieldSetExpression.cs | 10 +-- .../SparseFieldSetExpressionExtensions.cs | 13 ++-- .../Internal/Parsing/SparseFieldSetParser.cs | 9 +-- .../Queries/Internal/QueryLayerComposer.cs | 7 +- .../Queries/Internal/SparseFieldSetCache.cs | 72 +++++++++---------- ...parseFieldSetQueryStringParameterReader.cs | 3 +- .../Building/IncludedResourceObjectBuilder.cs | 3 +- .../Building/ResponseResourceObjectBuilder.cs | 3 +- .../Serialization/FieldsToSerialize.cs | 16 ++++- .../SparseFieldSetParseTests.cs | 2 +- 10 files changed, 72 insertions(+), 66 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs index 7511ab309a..bf96dad409 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Resources.Annotations; @@ -12,9 +12,9 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class SparseFieldSetExpression : QueryExpression { - public IReadOnlyCollection Fields { get; } + public IImmutableSet Fields { get; } - public SparseFieldSetExpression(IReadOnlyCollection fields) + public SparseFieldSetExpression(IImmutableSet fields) { ArgumentGuard.NotNullNorEmpty(fields, nameof(fields)); @@ -28,7 +28,7 @@ public override TResult Accept(QueryExpressionVisitor child.PublicName)); + return string.Join(",", Fields.Select(child => child.PublicName).OrderBy(name => name)); } public override bool Equals(object obj) @@ -45,7 +45,7 @@ public override bool Equals(object obj) var other = (SparseFieldSetExpression)obj; - return Fields.SequenceEqual(other.Fields); + return Fields.SetEquals(other.Fields); } public override int GetHashCode() diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs index 62010b7e11..35aff8b711 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using System.Linq.Expressions; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -36,9 +35,8 @@ private static SparseFieldSetExpression IncludeField(SparseFieldSetExpression sp return sparseFieldSet; } - HashSet fieldSet = sparseFieldSet.Fields.ToHashSet(); - fieldSet.Add(fieldToInclude); - return new SparseFieldSetExpression(fieldSet); + IImmutableSet newSparseFieldSet = sparseFieldSet.Fields.Add(fieldToInclude); + return new SparseFieldSetExpression(newSparseFieldSet); } public static SparseFieldSetExpression Excluding(this SparseFieldSetExpression sparseFieldSet, @@ -70,9 +68,8 @@ private static SparseFieldSetExpression ExcludeField(SparseFieldSetExpression sp return sparseFieldSet; } - HashSet fieldSet = sparseFieldSet.Fields.ToHashSet(); - fieldSet.Remove(fieldToExclude); - return new SparseFieldSetExpression(fieldSet); + IImmutableSet newSparseFieldSet = sparseFieldSet.Fields.Remove(fieldToExclude); + return new SparseFieldSetExpression(newSparseFieldSet); } } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs index 7ce7168a0d..6f2f7bfbf1 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -38,21 +39,21 @@ public SparseFieldSetExpression Parse(string source, ResourceContext resourceCon protected SparseFieldSetExpression ParseSparseFieldSet() { - var fields = new Dictionary(); + ImmutableHashSet.Builder fieldSetBuilder = ImmutableHashSet.CreateBuilder(); while (TokenStack.Any()) { - if (fields.Count > 0) + if (fieldSetBuilder.Count > 0) { EatSingleCharacterToken(TokenKind.Comma); } ResourceFieldChainExpression nextChain = ParseFieldChain(FieldChainRequirements.EndsInAttribute, "Field name expected."); ResourceFieldAttribute nextField = nextChain.Fields.Single(); - fields[nextField.PublicName] = nextField; + fieldSetBuilder.Add(nextField); } - return fields.Any() ? new SparseFieldSetExpression(fields.Values) : null; + return fieldSetBuilder.Any() ? new SparseFieldSetExpression(fieldSetBuilder.ToImmutable()) : null; } protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index f2dd01c4e8..14a51febce 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -253,7 +254,7 @@ public QueryLayer ComposeSecondaryLayerForRelationship(ResourceContext secondary private IDictionary GetProjectionForRelationship(ResourceContext secondaryResourceContext) { - IReadOnlyCollection secondaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(secondaryResourceContext); + IImmutableSet secondaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(secondaryResourceContext); return secondaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null); } @@ -269,7 +270,7 @@ public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, IncludeExpression innerInclude = secondaryLayer.Include; secondaryLayer.Include = null; - IReadOnlyCollection primaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(primaryResourceContext); + IImmutableSet primaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(primaryResourceContext); Dictionary primaryProjection = primaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null); @@ -484,7 +485,7 @@ protected virtual IDictionary GetProjectionF { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForQuery(resourceContext); + IImmutableSet fieldSet = _sparseFieldSetCache.GetSparseFieldSetForQuery(resourceContext); if (!fieldSet.Any()) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs index c44a0a34db..66c336d674 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -17,8 +18,8 @@ namespace JsonApiDotNetCore.Queries.Internal public sealed class SparseFieldSetCache { private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; - private readonly Lazy>> _lazySourceTable; - private readonly IDictionary> _visitedTable; + private readonly Lazy>> _lazySourceTable; + private readonly IDictionary> _visitedTable; public SparseFieldSetCache(IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor) { @@ -26,11 +27,12 @@ public SparseFieldSetCache(IEnumerable constraintProvi ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); _resourceDefinitionAccessor = resourceDefinitionAccessor; - _lazySourceTable = new Lazy>>(() => BuildSourceTable(constraintProviders)); - _visitedTable = new Dictionary>(); + _lazySourceTable = new Lazy>>(() => BuildSourceTable(constraintProviders)); + _visitedTable = new Dictionary>(); } - private static IDictionary> BuildSourceTable(IEnumerable constraintProviders) + private static IDictionary> BuildSourceTable( + IEnumerable constraintProviders) { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true @@ -47,30 +49,31 @@ private static IDictionary> Bui // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore - var mergedTable = new Dictionary>(); + var mergedTable = new Dictionary.Builder>(); foreach ((ResourceContext resourceContext, SparseFieldSetExpression sparseFieldSet) in sparseFieldTables) { if (!mergedTable.ContainsKey(resourceContext)) { - mergedTable[resourceContext] = new HashSet(); + mergedTable[resourceContext] = ImmutableHashSet.CreateBuilder(); } AddSparseFieldsToSet(sparseFieldSet.Fields, mergedTable[resourceContext]); } - return mergedTable; + return mergedTable.ToDictionary(pair => pair.Key, pair => (IImmutableSet)pair.Value.ToImmutable()); } - private static void AddSparseFieldsToSet(IReadOnlyCollection sparseFieldsToAdd, HashSet sparseFieldSet) + private static void AddSparseFieldsToSet(IImmutableSet sparseFieldsToAdd, + ImmutableHashSet.Builder sparseFieldSetBuilder) { foreach (ResourceFieldAttribute field in sparseFieldsToAdd) { - sparseFieldSet.Add(field); + sparseFieldSetBuilder.Add(field); } } - public IReadOnlyCollection GetSparseFieldSetForQuery(ResourceContext resourceContext) + public IImmutableSet GetSparseFieldSetForQuery(ResourceContext resourceContext) { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); @@ -82,9 +85,9 @@ public IReadOnlyCollection GetSparseFieldSetForQuery(Res SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); - HashSet outputFields = outputExpression == null - ? new HashSet() - : outputExpression.Fields.ToHashSet(); + IImmutableSet outputFields = outputExpression == null + ? ImmutableHashSet.Empty + : outputExpression.Fields; _visitedTable[resourceContext] = outputFields; } @@ -92,48 +95,39 @@ public IReadOnlyCollection GetSparseFieldSetForQuery(Res return _visitedTable[resourceContext]; } - public IReadOnlyCollection GetIdAttributeSetForRelationshipQuery(ResourceContext resourceContext) + public IImmutableSet GetIdAttributeSetForRelationshipQuery(ResourceContext resourceContext) { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); AttrAttribute idAttribute = resourceContext.Attributes.Single(attr => attr.Property.Name == nameof(Identifiable.Id)); - var inputExpression = new SparseFieldSetExpression(idAttribute.AsArray()); + var inputExpression = new SparseFieldSetExpression(ImmutableHashSet.Create(idAttribute)); // Intentionally not cached, as we are fetching ID only (ignoring any sparse fieldset that came from query string). SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); - HashSet outputAttributes = outputExpression == null - ? new HashSet() - : outputExpression.Fields.OfType().ToHashSet(); + ImmutableHashSet outputAttributes = outputExpression == null + ? ImmutableHashSet.Empty + : outputExpression.Fields.OfType().ToImmutableHashSet(); - outputAttributes.Add(idAttribute); + outputAttributes = outputAttributes.Add(idAttribute); return outputAttributes; } - public IReadOnlyCollection GetSparseFieldSetForSerializer(ResourceContext resourceContext) + public IImmutableSet GetSparseFieldSetForSerializer(ResourceContext resourceContext) { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); if (!_visitedTable.ContainsKey(resourceContext)) { - HashSet inputFields = _lazySourceTable.Value.ContainsKey(resourceContext) + IImmutableSet inputFields = _lazySourceTable.Value.ContainsKey(resourceContext) ? _lazySourceTable.Value[resourceContext] : GetResourceFields(resourceContext); var inputExpression = new SparseFieldSetExpression(inputFields); SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression); - HashSet outputFields; - - if (outputExpression == null) - { - outputFields = GetResourceFields(resourceContext); - } - else - { - outputFields = new HashSet(inputFields); - outputFields.IntersectWith(outputExpression.Fields); - } + IImmutableSet outputFields = + outputExpression == null ? GetResourceFields(resourceContext) : inputFields.Intersect(outputExpression.Fields); _visitedTable[resourceContext] = outputFields; } @@ -141,25 +135,23 @@ public IReadOnlyCollection GetSparseFieldSetForSerialize return _visitedTable[resourceContext]; } -#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type - private HashSet GetResourceFields(ResourceContext resourceContext) -#pragma warning restore AV1130 // Return type in method signature should be a collection interface instead of a concrete type + private IImmutableSet GetResourceFields(ResourceContext resourceContext) { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - var fieldSet = new HashSet(); + ImmutableHashSet.Builder fieldSetBuilder = ImmutableHashSet.CreateBuilder(); foreach (AttrAttribute attribute in resourceContext.Attributes.Where(attr => attr.Capabilities.HasFlag(AttrCapabilities.AllowView))) { - fieldSet.Add(attribute); + fieldSetBuilder.Add(attribute); } foreach (RelationshipAttribute relationship in resourceContext.Relationships) { - fieldSet.Add(relationship); + fieldSetBuilder.Add(relationship); } - return fieldSet; + return fieldSetBuilder.ToImmutable(); } public void Reset() diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index 1355689bcd..8da23e6df5 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -89,7 +90,7 @@ private SparseFieldSetExpression GetSparseFieldSet(string parameterValue, Resour { // We add ID on an incoming empty fieldset, so that callers can distinguish between no fieldset and an empty one. AttrAttribute idAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Identifiable.Id)); - return new SparseFieldSetExpression(ArrayFactory.Create(idAttribute)); + return new SparseFieldSetExpression(ImmutableHashSet.Create(idAttribute)); } return sparseFieldSet; diff --git a/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs index 170eb2d97c..451a3f0f7e 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -92,7 +93,7 @@ private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) { ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); - IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); + IImmutableSet fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return fieldSet.Contains(relationship); } diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs index 02aebcdc2e..2003fe0f8f 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -113,7 +114,7 @@ private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) { ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType); - IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); + IImmutableSet fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); return fieldSet.Contains(relationship); } diff --git a/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs b/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs index 763ee59b6c..d4ace8450d 100644 --- a/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs +++ b/src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -44,9 +45,20 @@ public IReadOnlyCollection GetAttributes(Type resourceType) } ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); - IReadOnlyCollection fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); + IImmutableSet fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext); - return fieldSet.OfType().ToArray(); + return SortAttributesInDeclarationOrder(fieldSet, resourceContext).ToArray(); + } + + private IEnumerable SortAttributesInDeclarationOrder(IImmutableSet fieldSet, ResourceContext resourceContext) + { + foreach (AttrAttribute attribute in resourceContext.Attributes) + { + if (fieldSet.Contains(attribute)) + { + yield return attribute; + } + } } /// diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs index 42e63a5a84..262e04b5e4 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs @@ -83,7 +83,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin } [Theory] - [InlineData("fields[blogPosts]", "caption,url,author", "blogPosts(caption,url,author)")] + [InlineData("fields[blogPosts]", "caption,url,author", "blogPosts(author,caption,url)")] [InlineData("fields[blogPosts]", "author,comments,labels", "blogPosts(author,comments,labels)")] [InlineData("fields[blogs]", "id", "blogs(id)")] [InlineData("fields[blogs]", "", "blogs(id)")] From 48f895436f32eb503e953b6849620eb09df75dbb Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Mon, 2 Aug 2021 14:34:48 +0200 Subject: [PATCH 04/29] Changed SparseFieldTableExpression.Table type from IReadOnlyDictionary to IImmutableDictionary and fixed equality comparison to discard order --- src/JsonApiDotNetCore/CollectionExtensions.cs | 36 +++++++++++++++++++ .../Expressions/QueryExpressionRewriter.cs | 6 ++-- .../Expressions/SparseFieldTableExpression.cs | 9 +++-- ...parseFieldSetQueryStringParameterReader.cs | 11 +++--- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs index 68997f60b3..57d09267d4 100644 --- a/src/JsonApiDotNetCore/CollectionExtensions.cs +++ b/src/JsonApiDotNetCore/CollectionExtensions.cs @@ -34,5 +34,41 @@ public static int FindIndex(this IList source, Predicate match) return -1; } + + public static bool DictionaryEqual(this IReadOnlyDictionary first, IReadOnlyDictionary second, + IEqualityComparer valueComparer = null) + { + if (first == second) + { + return true; + } + + if (first == null || second == null) + { + return false; + } + + if (first.Count != second.Count) + { + return false; + } + + IEqualityComparer effectiveValueComparer = valueComparer ?? EqualityComparer.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; + } } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 7abf814597..d70469625b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -198,7 +199,8 @@ public override QueryExpression VisitSparseFieldTable(SparseFieldTableExpression { if (expression != null) { - var newTable = new Dictionary(); + ImmutableDictionary.Builder newTable = + ImmutableDictionary.CreateBuilder(); foreach ((ResourceContext resourceContext, SparseFieldSetExpression sparseFieldSet) in expression.Table) { @@ -210,7 +212,7 @@ public override QueryExpression VisitSparseFieldTable(SparseFieldTableExpression if (newTable.Count > 0) { - var newExpression = new SparseFieldTableExpression(newTable); + var newExpression = new SparseFieldTableExpression(newTable.ToImmutable()); return newExpression.Equals(expression) ? expression : newExpression; } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs index 4fdc92ec60..9cf7922349 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using System.Text; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -13,9 +12,9 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class SparseFieldTableExpression : QueryExpression { - public IReadOnlyDictionary Table { get; } + public IImmutableDictionary Table { get; } - public SparseFieldTableExpression(IReadOnlyDictionary table) + public SparseFieldTableExpression(IImmutableDictionary table) { ArgumentGuard.NotNullNorEmpty(table, nameof(table), "entries"); @@ -61,7 +60,7 @@ public override bool Equals(object obj) var other = (SparseFieldTableExpression)obj; - return Table.SequenceEqual(other.Table); + return Table.DictionaryEqual(other.Table); } public override int GetHashCode() diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index 8da23e6df5..4b8312714a 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -21,7 +21,10 @@ public class SparseFieldSetQueryStringParameterReader : QueryStringParameterRead { private readonly SparseFieldTypeParser _sparseFieldTypeParser; private readonly SparseFieldSetParser _sparseFieldSetParser; - private readonly Dictionary _sparseFieldTable = new(); + + private readonly ImmutableDictionary.Builder _sparseFieldTableBuilder = + ImmutableDictionary.CreateBuilder(); + private string _lastParameterName; /// @@ -69,7 +72,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) ResourceContext targetResource = GetSparseFieldType(parameterName); SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue, targetResource); - _sparseFieldTable[targetResource] = sparseFieldSet; + _sparseFieldTableBuilder[targetResource] = sparseFieldSet; } catch (QueryParseException exception) { @@ -99,8 +102,8 @@ private SparseFieldSetExpression GetSparseFieldSet(string parameterValue, Resour /// public virtual IReadOnlyCollection GetConstraints() { - return _sparseFieldTable.Any() - ? new ExpressionInScope(null, new SparseFieldTableExpression(_sparseFieldTable)).AsArray() + return _sparseFieldTableBuilder.Any() + ? new ExpressionInScope(null, new SparseFieldTableExpression(_sparseFieldTableBuilder.ToImmutable())).AsArray() : Array.Empty(); } } From ae4a57bba481efc506b96197b066d866a3cde1ab Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Mon, 2 Aug 2021 16:37:59 +0200 Subject: [PATCH 05/29] Changed SortExpression.Elements type from IReadOnlyDictionary to IImmutableList --- .../Expressions/QueryExpressionRewriter.cs | 18 +++++++++++++++++- .../Queries/Expressions/SortExpression.cs | 6 +++--- .../Queries/Internal/Parsing/SortParser.cs | 8 +++++--- .../Queries/Internal/QueryLayerComposer.cs | 3 ++- .../Resources/JsonApiResourceDefinition.cs | 7 ++++--- .../CompositeKeys/CarExpressionRewriter.cs | 12 ++++++------ .../CompositeKeys/CarRepository.cs | 10 ++++------ 7 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index d70469625b..abd0ed037d 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -136,7 +136,7 @@ public override QueryExpression VisitSort(SortExpression expression, TArgument a { if (expression != null) { - IReadOnlyCollection newElements = VisitSequence(expression.Elements, argument); + IImmutableList newElements = VisitList(expression.Elements, argument); if (newElements.Count != 0) { @@ -317,5 +317,21 @@ protected virtual IReadOnlyCollection VisitSequence(IE return newElements; } + + protected virtual IImmutableList VisitList(IImmutableList elements, TArgument argument) + where TExpression : QueryExpression + { + ImmutableArray.Builder arrayBuilder = ImmutableArray.CreateBuilder(elements.Count); + + foreach (TExpression element in elements) + { + if (Visit(element, argument) is TExpression newElement) + { + arrayBuilder.Add(newElement); + } + } + + return arrayBuilder.ToImmutable(); + } } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs index 9f9c74b668..38f68df707 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; @@ -11,9 +11,9 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class SortExpression : QueryExpression { - public IReadOnlyCollection Elements { get; } + public IImmutableList Elements { get; } - public SortExpression(IReadOnlyCollection elements) + public SortExpression(IImmutableList elements) { ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs index 3b146b1785..eabb51c407 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -40,17 +41,18 @@ protected SortExpression ParseSort() { SortElementExpression firstElement = ParseSortElement(); - List elements = firstElement.AsList(); + ImmutableArray.Builder elementsBuilder = ImmutableArray.CreateBuilder(); + elementsBuilder.Add(firstElement); while (TokenStack.Any()) { EatSingleCharacterToken(TokenKind.Comma); SortElementExpression nextElement = ParseSortElement(); - elements.Add(nextElement); + elementsBuilder.Add(nextElement); } - return new SortExpression(elements); + return new SortExpression(elementsBuilder.ToImmutable()); } protected SortElementExpression ParseSortElement() diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 14a51febce..9776e1defd 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -461,7 +461,8 @@ protected virtual SortExpression GetSort(IReadOnlyCollection ex if (sort == null) { AttrAttribute idAttribute = GetIdAttribute(resourceContext); - sort = new SortExpression(new SortElementExpression(new ResourceFieldChainExpression(idAttribute), true).AsArray()); + var idAscendingSort = new SortElementExpression(new ResourceFieldChainExpression(idAttribute), true); + sort = new SortExpression(ImmutableArray.Create(idAscendingSort)); } return sort; diff --git a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs index 3498d74a7a..5c86928e6e 100644 --- a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; @@ -84,7 +85,7 @@ protected SortExpression CreateSortExpressionFromLambda(PropertySortOrder keySel { ArgumentGuard.NotNull(keySelectors, nameof(keySelectors)); - var sortElements = new List(); + ImmutableArray.Builder elementsBuilder = ImmutableArray.CreateBuilder(keySelectors.Count); foreach ((Expression> keySelector, ListSortDirection sortDirection) in keySelectors) { @@ -92,10 +93,10 @@ protected SortExpression CreateSortExpressionFromLambda(PropertySortOrder keySel AttrAttribute attribute = ResourceGraph.GetAttributes(keySelector).Single(); var sortElement = new SortElementExpression(new ResourceFieldChainExpression(attribute), isAscending); - sortElements.Add(sortElement); + elementsBuilder.Add(sortElement); } - return new SortExpression(sortElements); + return new SortExpression(elementsBuilder.ToImmutable()); } /// diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs index 53d2cc1d70..cc128db85f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection; using JsonApiDotNetCore; @@ -27,7 +28,6 @@ public CarExpressionRewriter(IResourceContextProvider resourceContextProvider) ResourceContext carResourceContext = resourceContextProvider.GetResourceContext(); _regionIdAttribute = carResourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Car.RegionId)); - _licensePlateAttribute = carResourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Car.LicensePlate)); } @@ -121,25 +121,25 @@ private FilterExpression CreateEqualityComparisonOnCompositeKey(ResourceFieldCha public override QueryExpression VisitSort(SortExpression expression, object argument) { - var newSortElements = new List(); + ImmutableArray.Builder elementsBuilder = ImmutableArray.CreateBuilder(expression.Elements.Count); foreach (SortElementExpression sortElement in expression.Elements) { if (IsSortOnCarId(sortElement)) { ResourceFieldChainExpression regionIdSort = ReplaceLastAttributeInChain(sortElement.TargetAttribute, _regionIdAttribute); - newSortElements.Add(new SortElementExpression(regionIdSort, sortElement.IsAscending)); + elementsBuilder.Add(new SortElementExpression(regionIdSort, sortElement.IsAscending)); ResourceFieldChainExpression licensePlateSort = ReplaceLastAttributeInChain(sortElement.TargetAttribute, _licensePlateAttribute); - newSortElements.Add(new SortElementExpression(licensePlateSort, sortElement.IsAscending)); + elementsBuilder.Add(new SortElementExpression(licensePlateSort, sortElement.IsAscending)); } else { - newSortElements.Add(sortElement); + elementsBuilder.Add(sortElement); } } - return new SortExpression(newSortElements); + return new SortExpression(elementsBuilder.ToImmutable()); } private static bool IsSortOnCarId(SortElementExpression sortElement) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs index 8142d8d378..b7858ec889 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs @@ -13,13 +13,13 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class CarRepository : EntityFrameworkCoreRepository { - private readonly IResourceGraph _resourceGraph; + private readonly CarExpressionRewriter _writer; public CarRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) { - _resourceGraph = resourceGraph; + _writer = new CarExpressionRewriter(resourceGraph); } protected override IQueryable ApplyQueryLayer(QueryLayer layer) @@ -33,14 +33,12 @@ private void RecursiveRewriteFilterInLayer(QueryLayer queryLayer) { if (queryLayer.Filter != null) { - var writer = new CarExpressionRewriter(_resourceGraph); - queryLayer.Filter = (FilterExpression)writer.Visit(queryLayer.Filter, null); + queryLayer.Filter = (FilterExpression)_writer.Visit(queryLayer.Filter, null); } if (queryLayer.Sort != null) { - var writer = new CarExpressionRewriter(_resourceGraph); - queryLayer.Sort = (SortExpression)writer.Visit(queryLayer.Sort, null); + queryLayer.Sort = (SortExpression)_writer.Visit(queryLayer.Sort, null); } if (queryLayer.Projection != null) From d0f7cc1f3bb26ac8f202517c3733ce1f88315dda Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Mon, 2 Aug 2021 17:26:33 +0200 Subject: [PATCH 06/29] Changed ResourceFieldChainExpression.Fields type from IReadOnlyCollection to IImmutableList --- .../Expressions/IncludeChainConverter.cs | 14 ++++-- .../ResourceFieldChainExpression.cs | 8 +-- .../Queries/Internal/Parsing/FilterParser.cs | 10 ++-- .../Queries/Internal/Parsing/IncludeParser.cs | 3 +- .../Internal/Parsing/PaginationParser.cs | 3 +- .../Internal/Parsing/QueryExpressionParser.cs | 5 +- .../QueryStringParameterScopeParser.cs | 4 +- .../Parsing/ResourceFieldChainResolver.cs | 50 +++++++++---------- .../Queries/Internal/Parsing/SortParser.cs | 3 +- .../Internal/Parsing/SparseFieldSetParser.cs | 5 +- .../Internal/Parsing/SparseFieldTypeParser.cs | 4 +- .../QueryableBuilding/QueryClauseBuilder.cs | 4 +- .../Internal/QueryStringParameterReader.cs | 3 +- .../CompositeKeys/CarExpressionRewriter.cs | 11 ++-- .../Middleware/JsonApiRequestTests.cs | 2 +- .../Serialization/SerializerTestsSetup.cs | 9 ++-- 16 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs index d2b3417b99..7a1631fc0b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JsonApiDotNetCore.Resources.Annotations; @@ -63,7 +64,7 @@ public IReadOnlyCollection GetRelationshipChains(I /// } /// ]]> /// - public IncludeExpression FromRelationshipChains(IReadOnlyCollection chains) + public IncludeExpression FromRelationshipChains(IEnumerable chains) { ArgumentGuard.NotNull(chains, nameof(chains)); @@ -71,7 +72,7 @@ public IncludeExpression FromRelationshipChains(IReadOnlyCollection ConvertChainsToElements(IReadOnlyCollection chains) + private static IReadOnlyCollection ConvertChainsToElements(IEnumerable chains) { var rootNode = new MutableIncludeNode(null); @@ -137,10 +138,13 @@ public override object VisitIncludeElement(IncludeElementExpression expression, private void FlushChain(IncludeElementExpression expression) { - List fieldsInChain = _parentRelationshipStack.Reverse().ToList(); - fieldsInChain.Add(expression.Relationship); + ImmutableArray.Builder chainBuilder = + ImmutableArray.CreateBuilder(_parentRelationshipStack.Count + 1); - Chains.Add(new ResourceFieldChainExpression(fieldsInChain)); + chainBuilder.AddRange(_parentRelationshipStack.Reverse()); + chainBuilder.Add(expression.Relationship); + + Chains.Add(new ResourceFieldChainExpression(chainBuilder.ToImmutable())); } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs index 2ebec58d17..502cd03976 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Resources.Annotations; @@ -12,16 +12,16 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class ResourceFieldChainExpression : IdentifierExpression { - public IReadOnlyCollection Fields { get; } + public IImmutableList Fields { get; } public ResourceFieldChainExpression(ResourceFieldAttribute field) { ArgumentGuard.NotNull(field, nameof(field)); - Fields = field.AsArray(); + Fields = ImmutableArray.Create(field); } - public ResourceFieldChainExpression(IReadOnlyCollection fields) + public ResourceFieldChainExpression(IImmutableList fields) { ArgumentGuard.NotNullNorEmpty(fields, nameof(fields)); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index 358ae79985..edd914830d 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using System.Reflection; using Humanizer; using JetBrains.Annotations; @@ -158,7 +158,7 @@ protected ComparisonExpression ParseComparison(string operatorName) OnResolveFieldChain(leftChain.ToString(), FieldChainRequirements.EndsInAttribute); } - PropertyInfo leftProperty = leftChain.Fields.Last().Property; + PropertyInfo leftProperty = leftChain.Fields[^1].Property; if (leftProperty.Name == nameof(Identifiable.Id) && rightTerm is LiteralConstantExpression rightConstant) { @@ -216,7 +216,7 @@ protected AnyExpression ParseAny() EatSingleCharacterToken(TokenKind.CloseParen); - PropertyInfo targetAttributeProperty = targetAttribute.Fields.Last().Property; + PropertyInfo targetAttributeProperty = targetAttribute.Fields[^1].Property; if (targetAttributeProperty.Name == nameof(Identifiable.Id)) { @@ -243,7 +243,7 @@ protected HasExpression ParseHas() { EatSingleCharacterToken(TokenKind.Comma); - filter = ParseFilterInHas((HasManyAttribute)targetCollection.Fields.Last()); + filter = ParseFilterInHas((HasManyAttribute)targetCollection.Fields[^1]); } EatSingleCharacterToken(TokenKind.CloseParen); @@ -332,7 +332,7 @@ private string DeObfuscateStringId(Type resourceType, string stringId) return tempResource.GetTypedId().ToString(); } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { if (chainRequirements == FieldChainRequirements.EndsInToMany) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 30f0a0b819..45bb64d39f 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -72,7 +73,7 @@ private static void ValidateMaximumIncludeDepth(int? maximumDepth, IReadOnlyColl } } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { return ChainResolver.ResolveRelationshipChain(_resourceContextInScope, path, _validateSingleRelationshipCallback); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs index 156c5281dc..a3947e0844 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -105,7 +106,7 @@ protected PaginationElementQueryStringValueExpression ParsePaginationElement() return null; } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { return ChainResolver.ResolveToManyChain(_resourceContextInScope, path, _validateSingleFieldCallback); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs index 48fcb44a07..99a195bb6c 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryExpressionParser.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -28,7 +29,7 @@ protected QueryExpressionParser(IResourceContextProvider resourceContextProvider /// /// Takes a dotted path and walks the resource graph to produce a chain of fields. /// - protected abstract IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements); + protected abstract IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements); protected virtual void Tokenize(string source) { @@ -40,7 +41,7 @@ protected ResourceFieldChainExpression ParseFieldChain(FieldChainRequirements ch { if (TokenStack.TryPop(out Token token) && token.Kind == TokenKind.Text) { - IReadOnlyCollection chain = OnResolveFieldChain(token.Value, chainRequirements); + IImmutableList chain = OnResolveFieldChain(token.Value, chainRequirements); if (chain.Any()) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs index 4944f090b1..feabc655ea 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries.Expressions; @@ -60,7 +60,7 @@ protected QueryStringParameterScopeExpression ParseQueryStringParameterScope() return new QueryStringParameterScopeExpression(name, scope); } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { if (chainRequirements == FieldChainRequirements.EndsInToMany) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs index 029c76fcb1..3772c75c2d 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources.Annotations; @@ -23,10 +23,10 @@ public ResourceFieldChainResolver(IResourceContextProvider resourceContextProvid /// /// Resolves a chain of relationships that ends in a to-many relationship, for example: blogs.owner.articles.comments /// - public IReadOnlyCollection ResolveToManyChain(ResourceContext resourceContext, string path, + public IImmutableList ResolveToManyChain(ResourceContext resourceContext, string path, Action validateCallback = null) { - var chain = new List(); + ImmutableArray.Builder chainBuilder = ImmutableArray.CreateBuilder(); string[] publicNameParts = path.Split("."); ResourceContext nextResourceContext = resourceContext; @@ -37,7 +37,7 @@ public IReadOnlyCollection ResolveToManyChain(ResourceCo validateCallback?.Invoke(relationship, nextResourceContext, path); - chain.Add(relationship); + chainBuilder.Add(relationship); nextResourceContext = _resourceContextProvider.GetResourceContext(relationship.RightType); } @@ -46,8 +46,8 @@ public IReadOnlyCollection ResolveToManyChain(ResourceCo validateCallback?.Invoke(lastToManyRelationship, nextResourceContext, path); - chain.Add(lastToManyRelationship); - return chain; + chainBuilder.Add(lastToManyRelationship); + return chainBuilder.ToImmutable(); } /// @@ -62,10 +62,10 @@ public IReadOnlyCollection ResolveToManyChain(ResourceCo /// articles.revisions.author /// /// - public IReadOnlyCollection ResolveRelationshipChain(ResourceContext resourceContext, string path, + public IImmutableList ResolveRelationshipChain(ResourceContext resourceContext, string path, Action validateCallback = null) { - var chain = new List(); + ImmutableArray.Builder chainBuilder = ImmutableArray.CreateBuilder(); ResourceContext nextResourceContext = resourceContext; foreach (string publicName in path.Split(".")) @@ -74,11 +74,11 @@ public IReadOnlyCollection ResolveRelationshipChain(Reso validateCallback?.Invoke(relationship, nextResourceContext, path); - chain.Add(relationship); + chainBuilder.Add(relationship); nextResourceContext = _resourceContextProvider.GetResourceContext(relationship.RightType); } - return chain; + return chainBuilder.ToImmutable(); } /// @@ -88,10 +88,10 @@ public IReadOnlyCollection ResolveRelationshipChain(Reso /// /// name /// - public IReadOnlyCollection ResolveToOneChainEndingInAttribute(ResourceContext resourceContext, string path, + public IImmutableList ResolveToOneChainEndingInAttribute(ResourceContext resourceContext, string path, Action validateCallback = null) { - var chain = new List(); + ImmutableArray.Builder chainBuilder = ImmutableArray.CreateBuilder(); string[] publicNameParts = path.Split("."); ResourceContext nextResourceContext = resourceContext; @@ -102,7 +102,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr validateCallback?.Invoke(toOneRelationship, nextResourceContext, path); - chain.Add(toOneRelationship); + chainBuilder.Add(toOneRelationship); nextResourceContext = _resourceContextProvider.GetResourceContext(toOneRelationship.RightType); } @@ -111,8 +111,8 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr validateCallback?.Invoke(lastAttribute, nextResourceContext, path); - chain.Add(lastAttribute); - return chain; + chainBuilder.Add(lastAttribute); + return chainBuilder.ToImmutable(); } /// @@ -124,10 +124,10 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr /// comments /// /// - public IReadOnlyCollection ResolveToOneChainEndingInToMany(ResourceContext resourceContext, string path, + public IImmutableList ResolveToOneChainEndingInToMany(ResourceContext resourceContext, string path, Action validateCallback = null) { - var chain = new List(); + ImmutableArray.Builder chainBuilder = ImmutableArray.CreateBuilder(); string[] publicNameParts = path.Split("."); ResourceContext nextResourceContext = resourceContext; @@ -138,7 +138,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInToMa validateCallback?.Invoke(toOneRelationship, nextResourceContext, path); - chain.Add(toOneRelationship); + chainBuilder.Add(toOneRelationship); nextResourceContext = _resourceContextProvider.GetResourceContext(toOneRelationship.RightType); } @@ -148,8 +148,8 @@ public IReadOnlyCollection ResolveToOneChainEndingInToMa validateCallback?.Invoke(toManyRelationship, nextResourceContext, path); - chain.Add(toManyRelationship); - return chain; + chainBuilder.Add(toManyRelationship); + return chainBuilder.ToImmutable(); } /// @@ -161,10 +161,10 @@ public IReadOnlyCollection ResolveToOneChainEndingInToMa /// author.address /// /// - public IReadOnlyCollection ResolveToOneChainEndingInAttributeOrToOne(ResourceContext resourceContext, string path, + public IImmutableList ResolveToOneChainEndingInAttributeOrToOne(ResourceContext resourceContext, string path, Action validateCallback = null) { - var chain = new List(); + ImmutableArray.Builder chainBuilder = ImmutableArray.CreateBuilder(); string[] publicNameParts = path.Split("."); ResourceContext nextResourceContext = resourceContext; @@ -175,7 +175,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr validateCallback?.Invoke(toOneRelationship, nextResourceContext, path); - chain.Add(toOneRelationship); + chainBuilder.Add(toOneRelationship); nextResourceContext = _resourceContextProvider.GetResourceContext(toOneRelationship.RightType); } @@ -191,8 +191,8 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr validateCallback?.Invoke(lastField, nextResourceContext, path); - chain.Add(lastField); - return chain; + chainBuilder.Add(lastField); + return chainBuilder.ToImmutable(); } private RelationshipAttribute GetRelationship(string publicName, ResourceContext resourceContext, string path) diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs index eabb51c407..d9f0413935 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; @@ -77,7 +76,7 @@ protected SortElementExpression ParseSortElement() return new SortElementExpression(targetAttribute, isAscending); } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { if (chainRequirements == FieldChainRequirements.EndsInToMany) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs index 6f2f7bfbf1..dd45d1fe24 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; @@ -56,13 +55,13 @@ protected SparseFieldSetExpression ParseSparseFieldSet() return fieldSetBuilder.Any() ? new SparseFieldSetExpression(fieldSetBuilder.ToImmutable()) : null; } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { ResourceFieldAttribute field = ChainResolver.GetField(path, _resourceContext, path); _validateSingleFieldCallback?.Invoke(field, _resourceContext, path); - return field.AsArray(); + return ImmutableArray.Create(field); } } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs index 361710e345..cc255e173d 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources.Annotations; @@ -66,7 +66,7 @@ private ResourceContext GetResourceContext(string resourceName) return resourceContext; } - protected override IReadOnlyCollection OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) + protected override IImmutableList OnResolveFieldChain(string path, FieldChainRequirements chainRequirements) { throw new NotSupportedException(); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs index 4d396dfbcc..3351c6f951 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs @@ -4,7 +4,6 @@ using System.Linq.Expressions; using System.Reflection; using JsonApiDotNetCore.Queries.Expressions; -using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding { @@ -61,8 +60,7 @@ private static Expression TryGetCollectionCount(Expression collectionExpression) public override Expression VisitResourceFieldChain(ResourceFieldChainExpression expression, TArgument argument) { - string[] components = expression.Fields - .Select(field => field is RelationshipAttribute relationship ? relationship.Property.Name : field.Property.Name).ToArray(); + string[] components = expression.Fields.Select(field => field.Property.Name).ToArray(); return CreatePropertyExpressionFromComponents(LambdaScope.Accessor, components); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs index 79c698627e..e6fb1226da 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Queries.Expressions; @@ -34,7 +33,7 @@ protected ResourceContext GetResourceContextForScope(ResourceFieldChainExpressio return RequestResource; } - ResourceFieldAttribute lastField = scope.Fields.Last(); + ResourceFieldAttribute lastField = scope.Fields[^1]; Type type = lastField is RelationshipAttribute relationship ? relationship.RightType : lastField.Property.PropertyType; return _resourceContextProvider.GetResourceContext(type); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs index cc128db85f..f814a743fb 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs @@ -35,7 +35,7 @@ public override QueryExpression VisitComparison(ComparisonExpression expression, { if (expression.Left is ResourceFieldChainExpression leftChain && expression.Right is LiteralConstantExpression rightConstant) { - PropertyInfo leftProperty = leftChain.Fields.Last().Property; + PropertyInfo leftProperty = leftChain.Fields[^1].Property; if (IsCarId(leftProperty)) { @@ -53,7 +53,7 @@ public override QueryExpression VisitComparison(ComparisonExpression expression, public override QueryExpression VisitAny(AnyExpression expression, object argument) { - PropertyInfo property = expression.TargetAttribute.Fields.Last().Property; + PropertyInfo property = expression.TargetAttribute.Fields[^1].Property; if (IsCarId(property)) { @@ -66,7 +66,7 @@ public override QueryExpression VisitAny(AnyExpression expression, object argume public override QueryExpression VisitMatchText(MatchTextExpression expression, object argument) { - PropertyInfo property = expression.TargetAttribute.Fields.Last().Property; + PropertyInfo property = expression.TargetAttribute.Fields[^1].Property; if (IsCarId(property)) { @@ -146,7 +146,7 @@ private static bool IsSortOnCarId(SortElementExpression sortElement) { if (sortElement.TargetAttribute != null) { - PropertyInfo property = sortElement.TargetAttribute.Fields.Last().Property; + PropertyInfo property = sortElement.TargetAttribute.Fields[^1].Property; if (IsCarId(property)) { @@ -159,8 +159,7 @@ private static bool IsSortOnCarId(SortElementExpression sortElement) private static ResourceFieldChainExpression ReplaceLastAttributeInChain(ResourceFieldChainExpression resourceFieldChain, AttrAttribute attribute) { - List fields = resourceFieldChain.Fields.ToList(); - fields[^1] = attribute; + IImmutableList fields = resourceFieldChain.Fields.SetItem(resourceFieldChain.Fields.Count - 1, attribute); return new ResourceFieldChainExpression(fields); } } diff --git a/test/UnitTests/Middleware/JsonApiRequestTests.cs b/test/UnitTests/Middleware/JsonApiRequestTests.cs index c64a2a0434..5c0c010d13 100644 --- a/test/UnitTests/Middleware/JsonApiRequestTests.cs +++ b/test/UnitTests/Middleware/JsonApiRequestTests.cs @@ -96,7 +96,7 @@ private static void SetupRoutes(HttpContext httpContext, string requestMethod, s if (pathSegments.Length >= 3) { - feature.RouteValues["relationshipName"] = pathSegments.Last(); + feature.RouteValues["relationshipName"] = pathSegments[^1]; } } diff --git a/test/UnitTests/Serialization/SerializerTestsSetup.cs b/test/UnitTests/Serialization/SerializerTestsSetup.cs index 302668e09a..08ff89ad21 100644 --- a/test/UnitTests/Serialization/SerializerTestsSetup.cs +++ b/test/UnitTests/Serialization/SerializerTestsSetup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JsonApiDotNetCore; using JsonApiDotNetCore.Configuration; @@ -140,8 +141,8 @@ private IEnumerable GetIncludeConstraints(IEnumerable< if (inclusionChains != null) { - List chains = inclusionChains.Select(relationships => new ResourceFieldChainExpression(relationships.ToArray())) - .ToList(); + List chains = inclusionChains.Select(relationships => + new ResourceFieldChainExpression(relationships.Cast().ToImmutableArray())).ToList(); IncludeExpression includeExpression = IncludeChainConverter.FromRelationshipChains(chains); expressionsInScope.Add(new ExpressionInScope(null, includeExpression)); @@ -161,8 +162,8 @@ private IEvaluatedIncludeCache GetEvaluatedIncludeCache(IEnumerable chains = inclusionChains.Select(relationships => new ResourceFieldChainExpression(relationships.ToArray())) - .ToList(); + List chains = inclusionChains.Select(relationships => + new ResourceFieldChainExpression(relationships.Cast().ToImmutableArray())).ToList(); IncludeExpression includeExpression = IncludeChainConverter.FromRelationshipChains(chains); From 8dddf5c91ba3904b991eba86be86b4f8efdac242 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Mon, 2 Aug 2021 17:30:49 +0200 Subject: [PATCH 07/29] Resharper quickfix: Use negated pattern --- .../Middleware/AsyncConvertEmptyActionResultFilter.cs | 2 +- .../Queries/Internal/Parsing/FilterParser.cs | 2 +- .../Queries/Internal/Parsing/ResourceFieldChainResolver.cs | 4 ++-- .../Repositories/ResourceRepositoryAccessor.cs | 2 +- src/JsonApiDotNetCore/Serialization/JsonApiReader.cs | 4 ++-- test/UnitTests/Builders/ResourceGraphBuilderTests.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs index f21dcad2b4..efe5a54f00 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs @@ -16,7 +16,7 @@ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultE if (context.HttpContext.IsJsonApiRequest()) { - if (!(context.Result is ObjectResult objectResult) || objectResult.Value == null) + if (context.Result is not ObjectResult objectResult || objectResult.Value == null) { if (context.Result is IStatusCodeActionResult statusCodeResult) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index edd914830d..60c66d5f16 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -152,7 +152,7 @@ protected ComparisonExpression ParseComparison(string operatorName) if (leftTerm is ResourceFieldChainExpression leftChain) { - if (leftChainRequirements.HasFlag(FieldChainRequirements.EndsInToOne) && !(rightTerm is NullConstantExpression)) + if (leftChainRequirements.HasFlag(FieldChainRequirements.EndsInToOne) && rightTerm is not NullConstantExpression) { // Run another pass over left chain to have it fail when chain ends in relationship. OnResolveFieldChain(leftChain.ToString(), FieldChainRequirements.EndsInAttribute); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs index 3772c75c2d..71c86ed9ba 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs @@ -213,7 +213,7 @@ private RelationshipAttribute GetToManyRelationship(string publicName, ResourceC { RelationshipAttribute relationship = GetRelationship(publicName, resourceContext, path); - if (!(relationship is HasManyAttribute)) + if (relationship is not HasManyAttribute) { throw new QueryParseException(path == publicName ? $"Relationship '{publicName}' must be a to-many relationship on resource '{resourceContext.PublicName}'." @@ -227,7 +227,7 @@ private RelationshipAttribute GetToOneRelationship(string publicName, ResourceCo { RelationshipAttribute relationship = GetRelationship(publicName, resourceContext, path); - if (!(relationship is HasOneAttribute)) + if (relationship is not HasOneAttribute) { throw new QueryParseException(path == publicName ? $"Relationship '{publicName}' must be a to-one relationship on resource '{resourceContext.PublicName}'." diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 25de9a3409..d9e7c5d33e 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -148,7 +148,7 @@ private object GetWriteRepository(Type resourceType) if (_request.TransactionId != null) { - if (!(writeRepository is IRepositorySupportsTransaction repository)) + if (writeRepository is not IRepositorySupportsTransaction repository) { ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType); throw new MissingTransactionSupportException(resourceContext.PublicName); diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs index 1841da6987..ced4ffbd26 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs @@ -258,14 +258,14 @@ private void ValidateForRelationshipType(string requestMethod, object model, str throw new ToManyRelationshipRequiredException(_request.Relationship.PublicName); } - if (model != null && !(model is IIdentifiable)) + if (model is { } and not IIdentifiable) { throw new InvalidRequestBodyException("Expected single data element for to-one relationship.", $"Expected single data element for '{_request.Relationship.PublicName}' relationship.", body); } } - if (_request.Relationship is HasManyAttribute && !(model is IEnumerable)) + if (_request.Relationship is HasManyAttribute && model is not IEnumerable) { throw new InvalidRequestBodyException("Expected data[] element for to-many relationship.", $"Expected data[] element for '{_request.Relationship.PublicName}' relationship.", body); diff --git a/test/UnitTests/Builders/ResourceGraphBuilderTests.cs b/test/UnitTests/Builders/ResourceGraphBuilderTests.cs index 0c64dcc897..d17c2ac432 100644 --- a/test/UnitTests/Builders/ResourceGraphBuilderTests.cs +++ b/test/UnitTests/Builders/ResourceGraphBuilderTests.cs @@ -78,7 +78,7 @@ public void Relationships_Without_Names_Specified_Will_Use_Configured_Formatter( // Assert ResourceContext resource = resourceGraph.GetResourceContext(typeof(TestResource)); Assert.Equal("relatedResource", resource.Relationships.Single(relationship => relationship is HasOneAttribute).PublicName); - Assert.Equal("relatedResources", resource.Relationships.Single(relationship => !(relationship is HasOneAttribute)).PublicName); + Assert.Equal("relatedResources", resource.Relationships.Single(relationship => relationship is not HasOneAttribute).PublicName); } private sealed class NonDbResource : Identifiable From 159b99f43f5b7516f1809e36db2a9518a8b5e8d2 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 10:40:45 +0200 Subject: [PATCH 08/29] Changed PaginationQueryStringValueExpression.Elements type from IReadOnlyCollection to IImmutableList --- src/JsonApiDotNetCore/CollectionExtensions.cs | 2 +- .../PaginationQueryStringValueExpression.cs | 6 +++--- .../Expressions/QueryExpressionRewriter.cs | 2 +- .../Queries/Internal/Parsing/PaginationParser.cs | 10 +++++----- .../PaginationQueryStringParameterReader.cs | 6 +++--- .../Serialization/Building/LinkBuilder.cs | 16 +++++++++------- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs index 57d09267d4..41596c7b22 100644 --- a/src/JsonApiDotNetCore/CollectionExtensions.cs +++ b/src/JsonApiDotNetCore/CollectionExtensions.cs @@ -19,7 +19,7 @@ public static bool IsNullOrEmpty(this IEnumerable source) return !source.Any(); } - public static int FindIndex(this IList source, Predicate match) + public static int FindIndex(this IReadOnlyList source, Predicate match) { ArgumentGuard.NotNull(source, nameof(source)); ArgumentGuard.NotNull(match, nameof(match)); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs index 1850ab0b02..706d3d9e15 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; @@ -11,9 +11,9 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class PaginationQueryStringValueExpression : QueryExpression { - public IReadOnlyCollection Elements { get; } + public IImmutableList Elements { get; } - public PaginationQueryStringValueExpression(IReadOnlyCollection elements) + public PaginationQueryStringValueExpression(IImmutableList elements) { ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index abd0ed037d..1f0bb16cc3 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -244,7 +244,7 @@ public override QueryExpression PaginationQueryStringValue(PaginationQueryString { if (expression != null) { - IReadOnlyCollection newElements = VisitSequence(expression.Elements, argument); + IImmutableList newElements = VisitList(expression.Elements, argument); var newExpression = new PaginationQueryStringValueExpression(newElements); return newExpression.Equals(expression) ? expression : newExpression; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs index a3947e0844..54c56b2f13 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; @@ -39,20 +38,21 @@ public PaginationQueryStringValueExpression Parse(string source, ResourceContext protected PaginationQueryStringValueExpression ParsePagination() { - var elements = new List(); + ImmutableArray.Builder elementsBuilder = + ImmutableArray.CreateBuilder(); PaginationElementQueryStringValueExpression element = ParsePaginationElement(); - elements.Add(element); + elementsBuilder.Add(element); while (TokenStack.Any()) { EatSingleCharacterToken(TokenKind.Comma); element = ParsePaginationElement(); - elements.Add(element); + elementsBuilder.Add(element); } - return new PaginationQueryStringValueExpression(elements); + return new PaginationQueryStringValueExpression(elementsBuilder.ToImmutable()); } protected PaginationElementQueryStringValueExpression ParsePaginationElement() diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs index c8d61fc7ba..c061c526f9 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs @@ -1,5 +1,5 @@ -using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -123,7 +123,7 @@ public virtual IReadOnlyCollection GetConstraints() var context = new PaginationContext(); foreach (PaginationElementQueryStringValueExpression element in _pageSizeConstraint?.Elements ?? - Array.Empty()) + ImmutableArray.Empty) { MutablePaginationEntry entry = context.ResolveEntryInScope(element.Scope); entry.PageSize = element.Value == 0 ? null : new PageSize(element.Value); @@ -131,7 +131,7 @@ public virtual IReadOnlyCollection GetConstraints() } foreach (PaginationElementQueryStringValueExpression element in _pageNumberConstraint?.Elements ?? - Array.Empty()) + ImmutableArray.Empty) { MutablePaginationEntry entry = context.ResolveEntryInScope(element.Scope); entry.PageNumber = new PageNumber(element.Value); diff --git a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs index 542923368c..8d33b6b109 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -141,7 +142,7 @@ private string CalculatePageSizeValue(PageSize topPageSize, ResourceContext requ private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPageSize, ResourceContext requestContext) { - IList elements = ParsePageSizeExpression(pageSizeParameterValue, requestContext); + IImmutableList elements = ParsePageSizeExpression(pageSizeParameterValue, requestContext); int elementInTopScopeIndex = elements.FindIndex(expression => expression.Scope == null); if (topPageSize != null) @@ -150,18 +151,18 @@ private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPage if (elementInTopScopeIndex != -1) { - elements[elementInTopScopeIndex] = topPageSizeElement; + elements = elements.SetItem(elementInTopScopeIndex, topPageSizeElement); } else { - elements.Insert(0, topPageSizeElement); + elements = elements.Insert(0, topPageSizeElement); } } else { if (elementInTopScopeIndex != -1) { - elements.RemoveAt(elementInTopScopeIndex); + elements = elements.RemoveAt(elementInTopScopeIndex); } } @@ -171,17 +172,18 @@ private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPage return parameterValue == string.Empty ? null : parameterValue; } - private IList ParsePageSizeExpression(string pageSizeParameterValue, ResourceContext requestResource) + private IImmutableList ParsePageSizeExpression(string pageSizeParameterValue, + ResourceContext requestResource) { if (pageSizeParameterValue == null) { - return new List(); + return ImmutableArray.Empty; } var parser = new PaginationParser(_resourceContextProvider); PaginationQueryStringValueExpression paginationExpression = parser.Parse(pageSizeParameterValue, requestResource); - return paginationExpression.Elements.ToList(); + return paginationExpression.Elements; } private string GetLinkForPagination(int pageOffset, string pageSizeValue) From 866c8b136cd1dfdfecbbe559228b0fb3e393105e Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 11:12:55 +0200 Subject: [PATCH 09/29] Changed AnyExpression.Constants type from IReadOnlyCollection to IImmutableSet --- .../Queries/Expressions/AnyExpression.cs | 10 +++--- .../Expressions/QueryExpressionRewriter.cs | 18 +++++++++- .../Queries/Internal/Parsing/FilterParser.cs | 35 +++++++++++++------ .../Queries/Internal/QueryLayerComposer.cs | 4 +-- .../QueryStringParameters/FilterParseTests.cs | 2 +- 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs index fe4c4bef76..3c049e86b4 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; using JetBrains.Annotations; @@ -14,9 +14,9 @@ namespace JsonApiDotNetCore.Queries.Expressions public class AnyExpression : FilterExpression { public ResourceFieldChainExpression TargetAttribute { get; } - public IReadOnlyCollection Constants { get; } + public IImmutableSet Constants { get; } - public AnyExpression(ResourceFieldChainExpression targetAttribute, IReadOnlyCollection constants) + public AnyExpression(ResourceFieldChainExpression targetAttribute, IImmutableSet constants) { ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); ArgumentGuard.NotNull(constants, nameof(constants)); @@ -43,7 +43,7 @@ public override string ToString() builder.Append('('); builder.Append(TargetAttribute); builder.Append(','); - builder.Append(string.Join(",", Constants.Select(constant => constant.ToString()))); + builder.Append(string.Join(",", Constants.Select(constant => constant.ToString()).OrderBy(value => value))); builder.Append(')'); return builder.ToString(); @@ -63,7 +63,7 @@ public override bool Equals(object obj) var other = (AnyExpression)obj; - return TargetAttribute.Equals(other.TargetAttribute) && Constants.SequenceEqual(other.Constants); + return TargetAttribute.Equals(other.TargetAttribute) && Constants.SetEquals(other.Constants); } public override int GetHashCode() diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 1f0bb16cc3..4a31e405a6 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -186,7 +186,7 @@ public override QueryExpression VisitAny(AnyExpression expression, TArgument arg if (expression != null) { var newTargetAttribute = Visit(expression.TargetAttribute, argument) as ResourceFieldChainExpression; - IReadOnlyCollection newConstants = VisitSequence(expression.Constants, argument); + IImmutableSet newConstants = VisitSet(expression.Constants, argument); var newExpression = new AnyExpression(newTargetAttribute, newConstants); return newExpression.Equals(expression) ? expression : newExpression; @@ -333,5 +333,21 @@ protected virtual IImmutableList VisitList(IImmutableL return arrayBuilder.ToImmutable(); } + + protected virtual IImmutableSet VisitSet(IImmutableSet elements, TArgument argument) + where TExpression : QueryExpression + { + ImmutableHashSet.Builder setBuilder = ImmutableHashSet.CreateBuilder(); + + foreach (TExpression element in elements) + { + if (Visit(element, argument) is TExpression newElement) + { + setBuilder.Add(newElement); + } + } + + return setBuilder.ToImmutable(); + } } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index 60c66d5f16..dac9240a30 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -196,39 +196,52 @@ protected AnyExpression ParseAny() EatSingleCharacterToken(TokenKind.Comma); - var constants = new List(); + ImmutableHashSet.Builder constantsBuilder = ImmutableHashSet.CreateBuilder(); LiteralConstantExpression constant = ParseConstant(); - constants.Add(constant); + constantsBuilder.Add(constant); EatSingleCharacterToken(TokenKind.Comma); constant = ParseConstant(); - constants.Add(constant); + constantsBuilder.Add(constant); while (TokenStack.TryPeek(out Token nextToken) && nextToken.Kind == TokenKind.Comma) { EatSingleCharacterToken(TokenKind.Comma); constant = ParseConstant(); - constants.Add(constant); + constantsBuilder.Add(constant); } EatSingleCharacterToken(TokenKind.CloseParen); + IImmutableSet constantSet = constantsBuilder.ToImmutable(); + PropertyInfo targetAttributeProperty = targetAttribute.Fields[^1].Property; if (targetAttributeProperty.Name == nameof(Identifiable.Id)) { - for (int index = 0; index < constants.Count; index++) - { - string stringId = constants[index].Value; - string id = DeObfuscateStringId(targetAttributeProperty.ReflectedType, stringId); - constants[index] = new LiteralConstantExpression(id); - } + constantSet = DeObfuscateIdConstants(constantSet, targetAttributeProperty); + } + + return new AnyExpression(targetAttribute, constantSet); + } + + private IImmutableSet DeObfuscateIdConstants(IImmutableSet constantSet, + PropertyInfo targetAttributeProperty) + { + ImmutableHashSet.Builder idConstantsBuilder = ImmutableHashSet.CreateBuilder(); + + foreach (LiteralConstantExpression idConstant in constantSet) + { + string stringId = idConstant.Value; + string id = DeObfuscateStringId(targetAttributeProperty.ReflectedType, stringId); + + idConstantsBuilder.Add(new LiteralConstantExpression(id)); } - return new AnyExpression(targetAttribute, constants); + return idConstantsBuilder.ToImmutable(); } protected HasExpression ParseHas() diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 9776e1defd..0f73977896 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -297,7 +297,7 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression r return new IncludeExpression(parentElement.AsArray()); } - private FilterExpression CreateFilterByIds(ICollection ids, AttrAttribute idAttribute, FilterExpression existingFilter) + private FilterExpression CreateFilterByIds(IReadOnlyCollection ids, AttrAttribute idAttribute, FilterExpression existingFilter) { var idChain = new ResourceFieldChainExpression(idAttribute); @@ -310,7 +310,7 @@ private FilterExpression CreateFilterByIds(ICollection ids, AttrAttrib } else if (ids.Count > 1) { - List constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToList(); + ImmutableHashSet constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToImmutableHashSet(); filter = new AnyExpression(idChain, constants); } diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs index 2ebd5a05fd..774031bc19 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs @@ -131,7 +131,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin [InlineData("filter", "contains(title,'this')", null, "contains(title,'this')")] [InlineData("filter", "startsWith(title,'this')", null, "startsWith(title,'this')")] [InlineData("filter", "endsWith(title,'this')", null, "endsWith(title,'this')")] - [InlineData("filter", "any(title,'this','that','there')", null, "any(title,'this','that','there')")] + [InlineData("filter", "any(title,'this','that','there')", null, "any(title,'that','there','this')")] [InlineData("filter", "and(contains(title,'sales'),contains(title,'marketing'),contains(title,'advertising'))", null, "and(contains(title,'sales'),contains(title,'marketing'),contains(title,'advertising'))")] [InlineData("filter[posts]", "or(and(not(equals(author.userName,null)),not(equals(author.displayName,null))),not(has(comments,startsWith(text,'A'))))", From d56c7d37c2bb5a0124aee8e77d47511db2b838a0 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 11:53:25 +0200 Subject: [PATCH 10/29] Changed LogicalExpression.Terms type from IReadOnlyCollection to IImmutableList --- .../Queries/Expressions/LogicalExpression.cs | 11 ++++++++--- .../Expressions/QueryExpressionRewriter.cs | 4 ++-- .../Queries/Internal/Parsing/FilterParser.cs | 11 +++++------ .../Queries/Internal/QueryLayerComposer.cs | 17 ++++------------- .../FilterQueryStringParameterReader.cs | 15 ++++++++------- .../Building/ResponseResourceObjectBuilder.cs | 2 +- .../Archiving/TelevisionBroadcastDefinition.cs | 8 ++------ .../CompositeKeys/CarExpressionRewriter.cs | 12 ++++-------- .../Reading/PlanetDefinition.cs | 5 +---- 9 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs index ab991aad9a..30d77e8a5b 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; using Humanizer; @@ -14,9 +14,14 @@ namespace JsonApiDotNetCore.Queries.Expressions public class LogicalExpression : FilterExpression { public LogicalOperator Operator { get; } - public IReadOnlyCollection Terms { get; } + public IImmutableList Terms { get; } - public LogicalExpression(LogicalOperator @operator, IReadOnlyCollection terms) + public LogicalExpression(LogicalOperator @operator, params FilterExpression[] terms) + : this(@operator, terms.ToImmutableArray()) + { + } + + public LogicalExpression(LogicalOperator @operator, IImmutableList terms) { ArgumentGuard.NotNull(terms, nameof(terms)); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 4a31e405a6..7ca9aa4876 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -55,11 +55,11 @@ public override QueryExpression VisitLogical(LogicalExpression expression, TArgu { if (expression != null) { - IReadOnlyCollection newTerms = VisitSequence(expression.Terms, argument); + var newTerms = VisitList(expression.Terms, argument); if (newTerms.Count == 1) { - return newTerms.First(); + return newTerms[0]; } if (newTerms.Count != 0) diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index dac9240a30..5461fb994c 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection; using Humanizer; @@ -106,28 +105,28 @@ protected LogicalExpression ParseLogical(string operatorName) EatText(operatorName); EatSingleCharacterToken(TokenKind.OpenParen); - var terms = new List(); + ImmutableArray.Builder termsBuilder = ImmutableArray.CreateBuilder(); FilterExpression term = ParseFilter(); - terms.Add(term); + termsBuilder.Add(term); EatSingleCharacterToken(TokenKind.Comma); term = ParseFilter(); - terms.Add(term); + termsBuilder.Add(term); while (TokenStack.TryPeek(out Token nextToken) && nextToken.Kind == TokenKind.Comma) { EatSingleCharacterToken(TokenKind.Comma); term = ParseFilter(); - terms.Add(term); + termsBuilder.Add(term); } EatSingleCharacterToken(TokenKind.CloseParen); var logicalOperator = Enum.Parse(operatorName.Pascalize()); - return new LogicalExpression(logicalOperator, terms); + return new LogicalExpression(logicalOperator, termsBuilder.ToImmutable()); } protected ComparisonExpression ParseComparison(string operatorName) diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 0f73977896..67bac2e702 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -310,19 +310,11 @@ private FilterExpression CreateFilterByIds(IReadOnlyCollection ids, At } else if (ids.Count > 1) { - ImmutableHashSet constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToImmutableHashSet(); + IImmutableSet constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToImmutableHashSet(); filter = new AnyExpression(idChain, constants); } - // @formatter:keep_existing_linebreaks true - - return filter == null - ? existingFilter - : existingFilter == null - ? filter - : new LogicalExpression(LogicalOperator.And, ArrayFactory.Create(filter, existingFilter)); - - // @formatter:keep_existing_linebreaks restore + return filter == null ? existingFilter : existingFilter == null ? filter : new LogicalExpression(LogicalOperator.And, filter, existingFilter); } /// @@ -442,9 +434,8 @@ protected virtual FilterExpression GetFilter(IReadOnlyCollection().ToArray(); - - FilterExpression filter = filters.Length > 1 ? new LogicalExpression(LogicalOperator.And, filters) : filters.FirstOrDefault(); + IImmutableList filters = expressionsInScope.OfType().ToImmutableArray(); + FilterExpression filter = filters.Count > 1 ? new LogicalExpression(LogicalOperator.And, filters) : filters.FirstOrDefault(); return _resourceDefinitionAccessor.OnApplyFilter(resourceContext.ResourceType, filter); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index a7732f108a..8486f48ded 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -23,8 +24,8 @@ public class FilterQueryStringParameterReader : QueryStringParameterReader, IFil private readonly IJsonApiOptions _options; private readonly QueryStringParameterScopeParser _scopeParser; private readonly FilterParser _filterParser; - private readonly List _filtersInGlobalScope = new(); - private readonly Dictionary> _filtersPerScope = new(); + private readonly ImmutableArray.Builder _filtersInGlobalScope = ImmutableArray.CreateBuilder(); + private readonly Dictionary.Builder> _filtersPerScope = new(); private string _lastParameterName; @@ -142,7 +143,7 @@ private void StoreFilterInScope(FilterExpression filter, ResourceFieldChainExpre { if (!_filtersPerScope.ContainsKey(scope)) { - _filtersPerScope[scope] = new List(); + _filtersPerScope[scope] = ImmutableArray.CreateBuilder(); } _filtersPerScope[scope].Add(filter); @@ -159,18 +160,18 @@ private IEnumerable EnumerateFiltersInScopes() { if (_filtersInGlobalScope.Any()) { - FilterExpression filter = MergeFilters(_filtersInGlobalScope); + FilterExpression filter = MergeFilters(_filtersInGlobalScope.ToImmutable()); yield return new ExpressionInScope(null, filter); } - foreach ((ResourceFieldChainExpression scope, List filters) in _filtersPerScope) + foreach ((ResourceFieldChainExpression scope, ImmutableArray.Builder filtersBuilder) in _filtersPerScope) { - FilterExpression filter = MergeFilters(filters); + FilterExpression filter = MergeFilters(filtersBuilder.ToImmutable()); yield return new ExpressionInScope(scope, filter); } } - private static FilterExpression MergeFilters(IReadOnlyCollection filters) + private static FilterExpression MergeFilters(IImmutableList filters) { return filters.Count > 1 ? new LogicalExpression(LogicalOperator.Or, filters) : filters.First(); } diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs index 2003fe0f8f..e23eef6a82 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs @@ -130,7 +130,7 @@ private IReadOnlyCollection> GetInclu foreach (ResourceFieldChainExpression chain in chains) { - if (chain.Fields.First().Equals(relationship)) + if (chain.Fields[0].Equals(relationship)) { inclusionChains.Add(chain.Fields.Cast().ToArray()); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs index 191bca53dc..fc7493c370 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using JsonApiDotNetCore; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; @@ -45,14 +44,11 @@ public override FilterExpression OnApplyFilter(FilterExpression existingFilter) if (IsReturningCollectionOfTelevisionBroadcasts() && !HasFilterOnArchivedAt(existingFilter)) { AttrAttribute archivedAtAttribute = ResourceContext.Attributes.Single(attr => attr.Property.Name == nameof(TelevisionBroadcast.ArchivedAt)); - var archivedAtChain = new ResourceFieldChainExpression(archivedAtAttribute); FilterExpression isUnarchived = new ComparisonExpression(ComparisonOperator.Equals, archivedAtChain, new NullConstantExpression()); - return existingFilter == null - ? isUnarchived - : new LogicalExpression(LogicalOperator.And, ArrayFactory.Create(existingFilter, isUnarchived)); + return existingFilter == null ? isUnarchived : new LogicalExpression(LogicalOperator.And, existingFilter, isUnarchived); } } @@ -192,7 +188,7 @@ private sealed class FilterWalker : QueryExpressionRewriter public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object argument) { - if (expression.Fields.First().Property.Name == nameof(TelevisionBroadcast.ArchivedAt)) + if (expression.Fields[0].Property.Name == nameof(TelevisionBroadcast.ArchivedAt)) { HasFilterOnArchivedAt = true; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs index f814a743fb..68fcb52cb7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarExpressionRewriter.cs @@ -83,7 +83,7 @@ private static bool IsCarId(PropertyInfo property) private QueryExpression RewriteFilterOnCarStringIds(ResourceFieldChainExpression existingCarIdChain, IEnumerable carStringIds) { - var outerTerms = new List(); + ImmutableArray.Builder outerTermsBuilder = ImmutableArray.CreateBuilder(); foreach (string carStringId in carStringIds) { @@ -93,10 +93,10 @@ private QueryExpression RewriteFilterOnCarStringIds(ResourceFieldChainExpression }; FilterExpression keyComparison = CreateEqualityComparisonOnCompositeKey(existingCarIdChain, tempCar.RegionId, tempCar.LicensePlate); - outerTerms.Add(keyComparison); + outerTermsBuilder.Add(keyComparison); } - return outerTerms.Count == 1 ? outerTerms[0] : new LogicalExpression(LogicalOperator.Or, outerTerms); + return outerTermsBuilder.Count == 1 ? outerTermsBuilder[0] : new LogicalExpression(LogicalOperator.Or, outerTermsBuilder.ToImmutable()); } private FilterExpression CreateEqualityComparisonOnCompositeKey(ResourceFieldChainExpression existingCarIdChain, long regionIdValue, @@ -112,11 +112,7 @@ private FilterExpression CreateEqualityComparisonOnCompositeKey(ResourceFieldCha var licensePlateComparison = new ComparisonExpression(ComparisonOperator.Equals, licensePlateChain, new LiteralConstantExpression(licensePlateValue)); - return new LogicalExpression(LogicalOperator.And, new[] - { - regionIdComparison, - licensePlateComparison - }); + return new LogicalExpression(LogicalOperator.And, regionIdComparison, licensePlateComparison); } public override QueryExpression VisitSort(SortExpression expression, object argument) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs index de0da36670..40d3b7dcce 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Net; using JetBrains.Annotations; -using JsonApiDotNetCore; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Queries.Expressions; @@ -55,9 +54,7 @@ public override FilterExpression OnApplyFilter(FilterExpression existingFilter) FilterExpression hasNoPrivateName = new ComparisonExpression(ComparisonOperator.Equals, new ResourceFieldChainExpression(privateNameAttribute), new NullConstantExpression()); - return existingFilter == null - ? hasNoPrivateName - : new LogicalExpression(LogicalOperator.And, ArrayFactory.Create(hasNoPrivateName, existingFilter)); + return existingFilter == null ? hasNoPrivateName : new LogicalExpression(LogicalOperator.And, hasNoPrivateName, existingFilter); } return existingFilter; From 5d164f063638fb429c7d42dbce8c1c09a4f9161e Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 12:46:08 +0200 Subject: [PATCH 11/29] Changed IncludeExpression.Elements type and IncludeElementExpression.Children type from IReadOnlyCollection to IImmutableList --- .../Expressions/IncludeChainConverter.cs | 8 ++-- .../Expressions/IncludeElementExpression.cs | 8 ++-- .../Queries/Expressions/IncludeExpression.cs | 7 +-- .../Expressions/QueryExpressionRewriter.cs | 24 ++-------- .../Queries/Internal/Parsing/IncludeParser.cs | 2 +- .../Queries/Internal/QueryLayerComposer.cs | 45 ++++++++++--------- .../QueryableBuilding/QueryClauseBuilder.cs | 2 +- .../Resources/IResourceDefinition.cs | 3 +- .../Resources/IResourceDefinitionAccessor.cs | 3 +- .../Resources/JsonApiResourceDefinition.cs | 2 +- .../Resources/ResourceDefinitionAccessor.cs | 3 +- .../Reading/MoonDefinition.cs | 9 ++-- .../Reading/PlanetDefinition.cs | 4 +- .../ServiceCollectionExtensionsTests.cs | 5 ++- .../NeverResourceDefinitionAccessor.cs | 3 +- 15 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs index 7a1631fc0b..dbe3b9b0dd 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs @@ -68,11 +68,11 @@ public IncludeExpression FromRelationshipChains(IEnumerable elements = ConvertChainsToElements(chains); + IImmutableList elements = ConvertChainsToElements(chains); return elements.Any() ? new IncludeExpression(elements) : IncludeExpression.Empty; } - private static IReadOnlyCollection ConvertChainsToElements(IEnumerable chains) + private static IImmutableList ConvertChainsToElements(IEnumerable chains) { var rootNode = new MutableIncludeNode(null); @@ -81,7 +81,7 @@ private static IReadOnlyCollection ConvertChainsToElem ConvertChainToElement(chain, rootNode); } - return rootNode.Children.Values.Select(child => child.ToExpression()).ToArray(); + return rootNode.Children.Values.Select(child => child.ToExpression()).ToImmutableArray(); } private static void ConvertChainToElement(ResourceFieldChainExpression chain, MutableIncludeNode rootNode) @@ -161,7 +161,7 @@ public MutableIncludeNode(RelationshipAttribute relationship) public IncludeElementExpression ToExpression() { - IncludeElementExpression[] elementChildren = Children.Values.Select(child => child.ToExpression()).ToArray(); + ImmutableArray elementChildren = Children.Values.Select(child => child.ToExpression()).ToImmutableArray(); return new IncludeElementExpression(_relationship, elementChildren); } } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs index f279f4ede4..a63db4c707 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; using JetBrains.Annotations; @@ -14,14 +14,14 @@ namespace JsonApiDotNetCore.Queries.Expressions public class IncludeElementExpression : QueryExpression { public RelationshipAttribute Relationship { get; } - public IReadOnlyCollection Children { get; } + public IImmutableList Children { get; } public IncludeElementExpression(RelationshipAttribute relationship) - : this(relationship, Array.Empty()) + : this(relationship, ImmutableArray.Empty) { } - public IncludeElementExpression(RelationshipAttribute relationship, IReadOnlyCollection children) + public IncludeElementExpression(RelationshipAttribute relationship, IImmutableList children) { ArgumentGuard.NotNull(relationship, nameof(relationship)); ArgumentGuard.NotNull(children, nameof(children)); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs index 54054db8ec..482ba0158d 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; @@ -15,9 +16,9 @@ public class IncludeExpression : QueryExpression public static readonly IncludeExpression Empty = new(); - public IReadOnlyCollection Elements { get; } + public IImmutableList Elements { get; } - public IncludeExpression(IReadOnlyCollection elements) + public IncludeExpression(IImmutableList elements) { ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); @@ -26,7 +27,7 @@ public IncludeExpression(IReadOnlyCollection elements) private IncludeExpression() { - Elements = Array.Empty(); + Elements = ImmutableArray.Empty; } public override TResult Accept(QueryExpressionVisitor visitor, TArgument argument) diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs index 7ca9aa4876..bd4d1e4de8 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryExpressionRewriter.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -55,7 +53,7 @@ public override QueryExpression VisitLogical(LogicalExpression expression, TArgu { if (expression != null) { - var newTerms = VisitList(expression.Terms, argument); + IImmutableList newTerms = VisitList(expression.Terms, argument); if (newTerms.Count == 1) { @@ -270,7 +268,7 @@ public override QueryExpression VisitInclude(IncludeExpression expression, TArgu { if (expression != null) { - IReadOnlyCollection newElements = VisitSequence(expression.Elements, argument); + IImmutableList newElements = VisitList(expression.Elements, argument); if (newElements.Count == 0) { @@ -288,7 +286,7 @@ public override QueryExpression VisitIncludeElement(IncludeElementExpression exp { if (expression != null) { - IReadOnlyCollection newElements = VisitSequence(expression.Children, argument); + IImmutableList newElements = VisitList(expression.Children, argument); var newExpression = new IncludeElementExpression(expression.Relationship, newElements); return newExpression.Equals(expression) ? expression : newExpression; @@ -302,22 +300,6 @@ public override QueryExpression VisitQueryableHandler(QueryableHandlerExpression return expression; } - protected virtual IReadOnlyCollection VisitSequence(IEnumerable elements, TArgument argument) - where TExpression : QueryExpression - { - var newElements = new List(); - - foreach (TExpression element in elements) - { - if (Visit(element, argument) is TExpression newElement) - { - newElements.Add(newElement); - } - } - - return newElements; - } - protected virtual IImmutableList VisitList(IImmutableList elements, TArgument argument) where TExpression : QueryExpression { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 45bb64d39f..b6cb69d6b9 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -58,7 +58,7 @@ protected IncludeExpression ParseInclude(int? maximumDepth) return IncludeChainConverter.FromRelationshipChains(chains); } - private static void ValidateMaximumIncludeDepth(int? maximumDepth, IReadOnlyCollection chains) + private static void ValidateMaximumIncludeDepth(int? maximumDepth, IEnumerable chains) { if (maximumDepth != null) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 67bac2e702..b7daa9bf62 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -125,7 +125,7 @@ private IncludeExpression ComposeChildren(QueryLayer topLayer, ICollection includeElements = + IImmutableList includeElements = ProcessIncludeSet(include.Elements, topLayer, new List(), constraints); return !ReferenceEquals(includeElements, include.Elements) @@ -133,13 +133,13 @@ private IncludeExpression ComposeChildren(QueryLayer topLayer, ICollection ProcessIncludeSet(IReadOnlyCollection includeElements, - QueryLayer parentLayer, ICollection parentRelationshipChain, ICollection constraints) + private IImmutableList ProcessIncludeSet(IImmutableList includeElements, QueryLayer parentLayer, + ICollection parentRelationshipChain, ICollection constraints) { - IReadOnlyCollection includeElementsEvaluated = - GetIncludeElements(includeElements, parentLayer.ResourceContext) ?? Array.Empty(); + IImmutableList includeElementsEvaluated = + GetIncludeElements(includeElements, parentLayer.ResourceContext) ?? ImmutableArray.Empty; - var updatesInChildren = new Dictionary>(); + var updatesInChildren = new Dictionary>(); foreach (IncludeElementExpression includeElement in includeElementsEvaluated) { @@ -181,7 +181,7 @@ private IReadOnlyCollection ProcessIncludeSet(IReadOnl if (includeElement.Children.Any()) { - IReadOnlyCollection updatedChildren = + IImmutableList updatedChildren = ProcessIncludeSet(includeElement.Children, child, relationshipChain, constraints); if (!ReferenceEquals(includeElement.Children, updatedChildren)) @@ -195,18 +195,19 @@ private IReadOnlyCollection ProcessIncludeSet(IReadOnl return !updatesInChildren.Any() ? includeElementsEvaluated : ApplyIncludeElementUpdates(includeElementsEvaluated, updatesInChildren); } - private static IReadOnlyCollection ApplyIncludeElementUpdates(IEnumerable includeElements, - IDictionary> updatesInChildren) + private static IImmutableList ApplyIncludeElementUpdates(IImmutableList includeElements, + IDictionary> updatesInChildren) { - List newIncludeElements = includeElements.ToList(); + ImmutableArray.Builder newElementsBuilder = ImmutableArray.CreateBuilder(includeElements.Count); + newElementsBuilder.AddRange(includeElements); - foreach ((IncludeElementExpression existingElement, IReadOnlyCollection updatedChildren) in updatesInChildren) + foreach ((IncludeElementExpression existingElement, IImmutableList updatedChildren) in updatesInChildren) { - int existingIndex = newIncludeElements.IndexOf(existingElement); - newIncludeElements[existingIndex] = new IncludeElementExpression(existingElement.Relationship, updatedChildren); + int existingIndex = newElementsBuilder.IndexOf(existingElement); + newElementsBuilder[existingIndex] = new IncludeElementExpression(existingElement.Relationship, updatedChildren); } - return newIncludeElements; + return newElementsBuilder.ToImmutable(); } /// @@ -294,7 +295,7 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression r ? new IncludeElementExpression(secondaryRelationship, relativeInclude.Elements) : new IncludeElementExpression(secondaryRelationship); - return new IncludeExpression(parentElement.AsArray()); + return new IncludeExpression(ImmutableArray.Create(parentElement)); } private FilterExpression CreateFilterByIds(IReadOnlyCollection ids, AttrAttribute idAttribute, FilterExpression existingFilter) @@ -310,7 +311,7 @@ private FilterExpression CreateFilterByIds(IReadOnlyCollection ids, At } else if (ids.Count > 1) { - IImmutableSet constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToImmutableHashSet(); + ImmutableHashSet constants = ids.Select(id => new LiteralConstantExpression(id.ToString())).ToImmutableHashSet(); filter = new AnyExpression(idChain, constants); } @@ -322,8 +323,8 @@ public QueryLayer ComposeForUpdate(TId id, ResourceContext primaryResource) { ArgumentGuard.NotNull(primaryResource, nameof(primaryResource)); - IncludeElementExpression[] includeElements = _targetedFields.Relationships - .Select(relationship => new IncludeElementExpression(relationship)).ToArray(); + ImmutableArray includeElements = _targetedFields.Relationships + .Select(relationship => new IncludeElementExpression(relationship)).ToImmutableArray(); AttrAttribute primaryIdAttribute = GetIdAttribute(primaryResource); @@ -398,7 +399,7 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T return new QueryLayer(leftResourceContext) { - Include = new IncludeExpression(new IncludeElementExpression(hasManyRelationship).AsArray()), + Include = new IncludeExpression(ImmutableArray.Create(new IncludeElementExpression(hasManyRelationship))), Filter = leftFilter, Projection = new Dictionary { @@ -421,7 +422,7 @@ public IResourceDefinitionAccessor GetResourceDefinitionAccessor() return _resourceDefinitionAccessor; } - protected virtual IReadOnlyCollection GetIncludeElements(IReadOnlyCollection includeElements, + protected virtual IImmutableList GetIncludeElements(IImmutableList includeElements, ResourceContext resourceContext) { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); @@ -434,8 +435,8 @@ protected virtual FilterExpression GetFilter(IReadOnlyCollection filters = expressionsInScope.OfType().ToImmutableArray(); - FilterExpression filter = filters.Count > 1 ? new LogicalExpression(LogicalOperator.And, filters) : filters.FirstOrDefault(); + ImmutableArray filters = expressionsInScope.OfType().ToImmutableArray(); + FilterExpression filter = filters.Length > 1 ? new LogicalExpression(LogicalOperator.And, filters) : filters.FirstOrDefault(); return _resourceDefinitionAccessor.OnApplyFilter(resourceContext.ResourceType, filter); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs index 3351c6f951..2a51b561c9 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs @@ -65,7 +65,7 @@ public override Expression VisitResourceFieldChain(ResourceFieldChainExpression return CreatePropertyExpressionFromComponents(LambdaScope.Accessor, components); } - private static MemberExpression CreatePropertyExpressionFromComponents(Expression source, IReadOnlyCollection components) + private static MemberExpression CreatePropertyExpressionFromComponents(Expression source, IEnumerable components) { MemberExpression property = null; diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs index 8664d98914..ab1b9f7b34 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -44,7 +45,7 @@ public interface IResourceDefinition /// /// The new set of includes. Return an empty collection to remove all inclusions (never return null). /// - IReadOnlyCollection OnApplyIncludes(IReadOnlyCollection existingIncludes); + IImmutableList OnApplyIncludes(IImmutableList existingIncludes); /// /// Enables to extend, replace or remove a filter that is being applied on a set of this resource type. diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs index 042a5553c4..f19de8543e 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,7 +18,7 @@ public interface IResourceDefinitionAccessor /// /// Invokes for the specified resource type. /// - IReadOnlyCollection OnApplyIncludes(Type resourceType, IReadOnlyCollection existingIncludes); + IImmutableList OnApplyIncludes(Type resourceType, IImmutableList existingIncludes); /// /// Invokes for the specified resource type. diff --git a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs index 5c86928e6e..722315bb5e 100644 --- a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs @@ -52,7 +52,7 @@ public JsonApiResourceDefinition(IResourceGraph resourceGraph) } /// - public virtual IReadOnlyCollection OnApplyIncludes(IReadOnlyCollection existingIncludes) + public virtual IImmutableList OnApplyIncludes(IImmutableList existingIncludes) { return existingIncludes; } diff --git a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs index b6ed7774ca..695c0b40e5 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -28,7 +29,7 @@ public ResourceDefinitionAccessor(IResourceContextProvider resourceContextProvid } /// - public IReadOnlyCollection OnApplyIncludes(Type resourceType, IReadOnlyCollection existingIncludes) + public IImmutableList OnApplyIncludes(Type resourceType, IImmutableList existingIncludes) { ArgumentGuard.NotNull(resourceType, nameof(resourceType)); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs index e3c17a8733..1f19221d70 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -25,7 +25,7 @@ public MoonDefinition(IResourceGraph resourceGraph, IClientSettingsProvider clie _hitCounter = hitCounter; } - public override IReadOnlyCollection OnApplyIncludes(IReadOnlyCollection existingIncludes) + public override IImmutableList OnApplyIncludes(IImmutableList existingIncludes) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnApplyIncludes); @@ -38,10 +38,7 @@ public override IReadOnlyCollection OnApplyIncludes(IR RelationshipAttribute orbitsAroundRelationship = ResourceContext.Relationships.Single(relationship => relationship.Property.Name == nameof(Moon.OrbitsAround)); - return new List(existingIncludes) - { - new(orbitsAroundRelationship) - }; + return existingIncludes.Add(new IncludeElementExpression(orbitsAroundRelationship)); } public override QueryStringParameterHandlers OnRegisterQueryableHandlersForQueryStringParameters() diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs index 40d3b7dcce..9b547cf117 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/Reading/PlanetDefinition.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Net; using JetBrains.Annotations; @@ -27,7 +27,7 @@ public PlanetDefinition(IResourceGraph resourceGraph, IClientSettingsProvider cl _hitCounter = hitCounter; } - public override IReadOnlyCollection OnApplyIncludes(IReadOnlyCollection existingIncludes) + public override IImmutableList OnApplyIncludes(IImmutableList existingIncludes) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnApplyIncludes); diff --git a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs index bd94ee6c7c..330269f335 100644 --- a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -416,7 +417,7 @@ public Task RemoveFromToManyRelationshipAsync(GuidResource primaryResource, ISet [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] private sealed class IntResourceDefinition : IResourceDefinition { - public IReadOnlyCollection OnApplyIncludes(IReadOnlyCollection existingIncludes) + public IImmutableList OnApplyIncludes(IImmutableList existingIncludes) { throw new NotImplementedException(); } @@ -504,7 +505,7 @@ public void OnSerialize(IntResource resource) [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] private sealed class GuidResourceDefinition : IResourceDefinition { - public IReadOnlyCollection OnApplyIncludes(IReadOnlyCollection existingIncludes) + public IImmutableList OnApplyIncludes(IImmutableList existingIncludes) { throw new NotImplementedException(); } diff --git a/test/UnitTests/NeverResourceDefinitionAccessor.cs b/test/UnitTests/NeverResourceDefinitionAccessor.cs index ddc89a95ba..35e02e9023 100644 --- a/test/UnitTests/NeverResourceDefinitionAccessor.cs +++ b/test/UnitTests/NeverResourceDefinitionAccessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Middleware; @@ -11,7 +12,7 @@ namespace UnitTests { internal sealed class NeverResourceDefinitionAccessor : IResourceDefinitionAccessor { - public IReadOnlyCollection OnApplyIncludes(Type resourceType, IReadOnlyCollection existingIncludes) + public IImmutableList OnApplyIncludes(Type resourceType, IImmutableList existingIncludes) { return existingIncludes; } From dfee19f5e78ab464667081a0554d21c7d7d33f0f Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 20:59:55 +0200 Subject: [PATCH 12/29] Add support for `IReadOnlySet<>` usage in resource models --- src/JsonApiDotNetCore/CollectionConverter.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/JsonApiDotNetCore/CollectionConverter.cs b/src/JsonApiDotNetCore/CollectionConverter.cs index 212f2a6f3b..c6f148a26e 100644 --- a/src/JsonApiDotNetCore/CollectionConverter.cs +++ b/src/JsonApiDotNetCore/CollectionConverter.cs @@ -8,13 +8,14 @@ namespace JsonApiDotNetCore { internal sealed class CollectionConverter { - private static readonly Type[] HashSetCompatibleCollectionTypes = + private static readonly ISet HashSetCompatibleCollectionTypes = new HashSet { typeof(HashSet<>), - typeof(ICollection<>), typeof(ISet<>), - typeof(IEnumerable<>), - typeof(IReadOnlyCollection<>) + typeof(IReadOnlySet<>), + typeof(ICollection<>), + typeof(IReadOnlyCollection<>), + typeof(IEnumerable<>) }; /// @@ -49,15 +50,14 @@ public 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]); } From 56cf588292b8d9defce78abb915a2a336b3e0cba Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 21:55:45 +0200 Subject: [PATCH 13/29] Changed DisableQueryStringAttribute.ParameterNames type from IReadOnlyCollection to IReadOnlySet --- .../Controllers/Annotations/DisableQueryStringAttribute.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs index acb62b44d8..98999ff18c 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs @@ -22,14 +22,15 @@ namespace JsonApiDotNetCore.Controllers.Annotations public sealed class DisableQueryStringAttribute : Attribute { public static readonly DisableQueryStringAttribute Empty = new(StandardQueryStringParameters.None); - public IReadOnlyCollection ParameterNames { get; } + + public IReadOnlySet ParameterNames { get; } /// /// Disables one or more of the builtin query parameters for a controller. /// public DisableQueryStringAttribute(StandardQueryStringParameters parameters) { - var parameterNames = new List(); + var parameterNames = new HashSet(); foreach (StandardQueryStringParameters value in Enum.GetValues(typeof(StandardQueryStringParameters))) { @@ -50,7 +51,7 @@ public DisableQueryStringAttribute(string parameterNames) { ArgumentGuard.NotNullNorEmpty(parameterNames, nameof(parameterNames)); - ParameterNames = parameterNames.Split(",").ToList(); + ParameterNames = parameterNames.Split(",").ToHashSet(); } public bool ContainsParameter(StandardQueryStringParameters parameter) From 1c2726b402a9054e4ef7f8394648e3317292cc71 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 22:08:01 +0200 Subject: [PATCH 14/29] Changed IResourceContextProvider.GetResourceContexts() to return a set instead of a list. Made ResourceContext sealed and immutable and implemented Equals/GetHashCode so it works with sets. --- .../Configuration/IResourceContextProvider.cs | 8 +- .../Configuration/ResourceContext.cs | 92 +++++++++++++++++-- .../Configuration/ResourceGraph.cs | 22 ++--- .../Configuration/ResourceGraphBuilder.cs | 40 +++----- src/JsonApiDotNetCore/ObjectExtensions.cs | 10 +- .../UnitTests/Links/LinkInclusionTests.cs | 30 ++---- .../Middleware/JsonApiMiddlewareTests.cs | 16 +--- 7 files changed, 134 insertions(+), 84 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs b/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs index a452bf30c9..62f4e729a6 100644 --- a/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs +++ b/src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs @@ -10,14 +10,14 @@ namespace JsonApiDotNetCore.Configuration public interface IResourceContextProvider { /// - /// Gets all registered resource contexts. + /// Gets the metadata for all registered resources. /// - IReadOnlyCollection GetResourceContexts(); + IReadOnlySet GetResourceContexts(); /// - /// Gets the resource metadata for the specified exposed resource name. + /// Gets the resource metadata for the resource that is publicly exposed by the specified name. /// - ResourceContext GetResourceContext(string resourceName); + ResourceContext GetResourceContext(string publicName); /// /// Gets the resource metadata for the specified resource type. diff --git a/src/JsonApiDotNetCore/Configuration/ResourceContext.cs b/src/JsonApiDotNetCore/Configuration/ResourceContext.cs index 4ba2975a10..5a18a8c2f1 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceContext.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceContext.cs @@ -10,39 +10,39 @@ namespace JsonApiDotNetCore.Configuration /// Provides metadata for a resource, such as its attributes and relationships. /// [PublicAPI] - public class ResourceContext + public sealed class ResourceContext { private IReadOnlyCollection _fields; /// /// The publicly exposed resource name. /// - public string PublicName { get; set; } + public string PublicName { get; } /// /// The CLR type of the resource. /// - public Type ResourceType { get; set; } + public Type ResourceType { get; } /// /// The identity type of the resource. /// - public Type IdentityType { get; set; } + public Type IdentityType { get; } /// /// Exposed resource attributes. See https://jsonapi.org/format/#document-resource-object-attributes. /// - public IReadOnlyCollection Attributes { get; set; } + public IReadOnlyCollection Attributes { get; } /// /// Exposed resource relationships. See https://jsonapi.org/format/#document-resource-object-relationships. /// - public IReadOnlyCollection Relationships { get; set; } + public IReadOnlyCollection Relationships { get; } /// /// Related entities that are not exposed as resource relationships. /// - public IReadOnlyCollection EagerLoads { get; set; } + public IReadOnlyCollection EagerLoads { get; } /// /// Exposed resource attributes and relationships. See https://jsonapi.org/format/#document-resource-object-fields. @@ -56,7 +56,7 @@ public class ResourceContext /// /// In the process of building the resource graph, this value is set based on usage. /// - public LinkTypes TopLevelLinks { get; internal set; } = LinkTypes.NotConfigured; + public LinkTypes TopLevelLinks { get; } /// /// Configures which links to show in the object for this resource type. Defaults to @@ -65,7 +65,7 @@ public class ResourceContext /// /// In the process of building the resource graph, this value is set based on usage. /// - public LinkTypes ResourceLinks { get; internal set; } = LinkTypes.NotConfigured; + public LinkTypes ResourceLinks { get; } /// /// Configures which links to show in the object for all relationships of this resource type. @@ -75,11 +75,83 @@ public class ResourceContext /// /// In the process of building the resource graph, this value is set based on usage. /// - public LinkTypes RelationshipLinks { get; internal set; } = LinkTypes.NotConfigured; + public LinkTypes RelationshipLinks { get; } + + public ResourceContext(string publicName, Type resourceType, Type identityType, IReadOnlyCollection attributes, + IReadOnlyCollection relationships, IReadOnlyCollection eagerLoads, + LinkTypes topLevelLinks = LinkTypes.NotConfigured, LinkTypes resourceLinks = LinkTypes.NotConfigured, + LinkTypes relationshipLinks = LinkTypes.NotConfigured) + { + ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName)); + ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(identityType, nameof(identityType)); + ArgumentGuard.NotNull(attributes, nameof(attributes)); + ArgumentGuard.NotNull(relationships, nameof(relationships)); + ArgumentGuard.NotNull(eagerLoads, nameof(eagerLoads)); + + PublicName = publicName; + ResourceType = resourceType; + IdentityType = identityType; + Attributes = attributes; + Relationships = relationships; + EagerLoads = eagerLoads; + TopLevelLinks = topLevelLinks; + ResourceLinks = resourceLinks; + RelationshipLinks = relationshipLinks; + } public override string ToString() { return PublicName; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is null || GetType() != obj.GetType()) + { + return false; + } + + var other = (ResourceContext)obj; + + return PublicName == other.PublicName && ResourceType == other.ResourceType && IdentityType == other.IdentityType && + Attributes.SequenceEqual(other.Attributes) && Relationships.SequenceEqual(other.Relationships) && EagerLoads.SequenceEqual(other.EagerLoads) && + TopLevelLinks == other.TopLevelLinks && ResourceLinks == other.ResourceLinks && RelationshipLinks == other.RelationshipLinks; + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + + hashCode.Add(PublicName); + hashCode.Add(ResourceType); + hashCode.Add(IdentityType); + + foreach (AttrAttribute attribute in Attributes) + { + hashCode.Add(attribute); + } + + foreach (RelationshipAttribute relationship in Relationships) + { + hashCode.Add(relationship); + } + + foreach (EagerLoadAttribute eagerLoad in EagerLoads) + { + hashCode.Add(eagerLoad); + } + + hashCode.Add(TopLevelLinks); + hashCode.Add(ResourceLinks); + hashCode.Add(RelationshipLinks); + + return hashCode.ToHashCode(); + } } } diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs index 14cb0547cd..26ada5f798 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs @@ -14,27 +14,27 @@ namespace JsonApiDotNetCore.Configuration public sealed class ResourceGraph : IResourceGraph { private static readonly Type ProxyTargetAccessorType = Type.GetType("Castle.DynamicProxy.IProxyTargetAccessor, Castle.Core"); - private readonly IReadOnlyCollection _resources; + private readonly IReadOnlySet _resourceContexts; - public ResourceGraph(IReadOnlyCollection resources) + public ResourceGraph(IReadOnlySet resourceContexts) { - ArgumentGuard.NotNull(resources, nameof(resources)); + ArgumentGuard.NotNull(resourceContexts, nameof(resourceContexts)); - _resources = resources; + _resourceContexts = resourceContexts; } /// - public IReadOnlyCollection GetResourceContexts() + public IReadOnlySet GetResourceContexts() { - return _resources; + return _resourceContexts; } /// - public ResourceContext GetResourceContext(string resourceName) + public ResourceContext GetResourceContext(string publicName) { - ArgumentGuard.NotNullNorEmpty(resourceName, nameof(resourceName)); + ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName)); - return _resources.SingleOrDefault(resourceContext => resourceContext.PublicName == resourceName); + return _resourceContexts.SingleOrDefault(resourceContext => resourceContext.PublicName == publicName); } /// @@ -43,8 +43,8 @@ public ResourceContext GetResourceContext(Type resourceType) ArgumentGuard.NotNull(resourceType, nameof(resourceType)); return IsLazyLoadingProxyForResourceType(resourceType) - ? _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType.BaseType) - : _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType); + ? _resourceContexts.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType.BaseType) + : _resourceContexts.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType); } private bool IsLazyLoadingProxyForResourceType(Type resourceType) diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index 64eca6dc41..24bf42dc24 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -17,7 +17,7 @@ public class ResourceGraphBuilder { private readonly IJsonApiOptions _options; private readonly ILogger _logger; - private readonly List _resources = new(); + private readonly HashSet _resourceContexts = new(); private readonly TypeLocator _typeLocator = new(); public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactory) @@ -34,20 +34,7 @@ public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactor /// public IResourceGraph Build() { - _resources.ForEach(SetResourceLinksOptions); - return new ResourceGraph(_resources); - } - - private void SetResourceLinksOptions(ResourceContext resourceContext) - { - var attribute = (ResourceLinksAttribute)resourceContext.ResourceType.GetCustomAttribute(typeof(ResourceLinksAttribute)); - - if (attribute != null) - { - resourceContext.RelationshipLinks = attribute.RelationshipLinks; - resourceContext.ResourceLinks = attribute.ResourceLinks; - resourceContext.TopLevelLinks = attribute.TopLevelLinks; - } + return new ResourceGraph(_resourceContexts); } /// @@ -102,7 +89,7 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu { ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - if (_resources.Any(resourceContext => resourceContext.ResourceType == resourceType)) + if (_resourceContexts.Any(resourceContext => resourceContext.ResourceType == resourceType)) { return this; } @@ -113,7 +100,7 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu Type effectiveIdType = idType ?? _typeLocator.TryGetIdType(resourceType); ResourceContext resourceContext = CreateResourceContext(effectivePublicName, resourceType, effectiveIdType); - _resources.Add(resourceContext); + _resourceContexts.Add(resourceContext); } else { @@ -125,15 +112,16 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu private ResourceContext CreateResourceContext(string publicName, Type resourceType, Type idType) { - return new() - { - PublicName = publicName, - ResourceType = resourceType, - IdentityType = idType, - Attributes = GetAttributes(resourceType), - Relationships = GetRelationships(resourceType), - EagerLoads = GetEagerLoads(resourceType) - }; + IReadOnlyCollection attributes = GetAttributes(resourceType); + IReadOnlyCollection relationships = GetRelationships(resourceType); + IReadOnlyCollection eagerLoads = GetEagerLoads(resourceType); + + var linksAttribute = (ResourceLinksAttribute)resourceType.GetCustomAttribute(typeof(ResourceLinksAttribute)); + + return linksAttribute == null + ? new ResourceContext(publicName, resourceType, idType, attributes, relationships, eagerLoads) + : new ResourceContext(publicName, resourceType, idType, attributes, relationships, eagerLoads, linksAttribute.TopLevelLinks, + linksAttribute.ResourceLinks, linksAttribute.RelationshipLinks); } private IReadOnlyCollection GetAttributes(Type resourceType) diff --git a/src/JsonApiDotNetCore/ObjectExtensions.cs b/src/JsonApiDotNetCore/ObjectExtensions.cs index e7351f5527..e0f4ce6af7 100644 --- a/src/JsonApiDotNetCore/ObjectExtensions.cs +++ b/src/JsonApiDotNetCore/ObjectExtensions.cs @@ -21,7 +21,15 @@ public static T[] AsArray(this T element) public static List AsList(this T element) { - return new() + return new List + { + element + }; + } + + public static HashSet AsHashSet(this T element) + { + return new HashSet { element }; diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs index bd00582b87..590292daa7 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs @@ -56,12 +56,8 @@ public sealed class LinkInclusionTests public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected) { // Arrange - var exampleResourceContext = new ResourceContext - { - PublicName = nameof(ExampleResource), - ResourceType = typeof(ExampleResource), - TopLevelLinks = linksInResourceContext - }; + var exampleResourceContext = new ResourceContext(nameof(ExampleResource), typeof(ExampleResource), typeof(int), Array.Empty(), + Array.Empty(), Array.Empty(), linksInResourceContext); var options = new JsonApiOptions { @@ -84,7 +80,7 @@ public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInReso TotalResourceCount = 10 }; - var resourceGraph = new ResourceGraph(exampleResourceContext.AsArray()); + var resourceGraph = new ResourceGraph(exampleResourceContext.AsHashSet()); var httpContextAccessor = new FakeHttpContextAccessor(); var linkGenerator = new FakeLinkGenerator(); var controllerResourceMapping = new FakeControllerResourceMapping(); @@ -157,12 +153,8 @@ public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInReso public void Applies_cascading_settings_for_resource_links(LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected) { // Arrange - var exampleResourceContext = new ResourceContext - { - PublicName = nameof(ExampleResource), - ResourceType = typeof(ExampleResource), - ResourceLinks = linksInResourceContext - }; + var exampleResourceContext = new ResourceContext(nameof(ExampleResource), typeof(ExampleResource), typeof(int), Array.Empty(), + Array.Empty(), Array.Empty(), resourceLinks: linksInResourceContext); var options = new JsonApiOptions { @@ -171,7 +163,7 @@ public void Applies_cascading_settings_for_resource_links(LinkTypes linksInResou var request = new JsonApiRequest(); var paginationContext = new PaginationContext(); - var resourceGraph = new ResourceGraph(exampleResourceContext.AsArray()); + var resourceGraph = new ResourceGraph(exampleResourceContext.AsHashSet()); var httpContextAccessor = new FakeHttpContextAccessor(); var linkGenerator = new FakeLinkGenerator(); var controllerResourceMapping = new FakeControllerResourceMapping(); @@ -323,12 +315,8 @@ public void Applies_cascading_settings_for_relationship_links(LinkTypes linksInR LinkTypes linksInOptions, LinkTypes expected) { // Arrange - var exampleResourceContext = new ResourceContext - { - PublicName = nameof(ExampleResource), - ResourceType = typeof(ExampleResource), - RelationshipLinks = linksInResourceContext - }; + var exampleResourceContext = new ResourceContext(nameof(ExampleResource), typeof(ExampleResource), typeof(int), Array.Empty(), + Array.Empty(), Array.Empty(), relationshipLinks: linksInResourceContext); var options = new JsonApiOptions { @@ -337,7 +325,7 @@ public void Applies_cascading_settings_for_relationship_links(LinkTypes linksInR var request = new JsonApiRequest(); var paginationContext = new PaginationContext(); - var resourceGraph = new ResourceGraph(exampleResourceContext.AsArray()); + var resourceGraph = new ResourceGraph(exampleResourceContext.AsHashSet()); var httpContextAccessor = new FakeHttpContextAccessor(); var linkGenerator = new FakeLinkGenerator(); var controllerResourceMapping = new FakeControllerResourceMapping(); diff --git a/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs b/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs index 8014907020..f789b8ba0f 100644 --- a/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs +++ b/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs @@ -167,24 +167,18 @@ private Mock CreateMockResourceGraph(string resourceName, bool i { var mockGraph = new Mock(); - var resourceContext = new ResourceContext - { - PublicName = resourceName, - IdentityType = typeof(string) - }; + var resourceContext = new ResourceContext(resourceName, typeof(object), typeof(string), Array.Empty(), + Array.Empty(), Array.Empty()); ISetupSequentialResult seq = mockGraph.SetupSequence(resourceGraph => resourceGraph.GetResourceContext(It.IsAny())) .Returns(resourceContext); if (includeRelationship) { - var relResourceContext = new ResourceContext - { - PublicName = "todoItems", - IdentityType = typeof(string) - }; + var relatedContext = new ResourceContext("todoItems", typeof(object), typeof(string), Array.Empty(), + Array.Empty(), Array.Empty()); - seq.Returns(relResourceContext); + seq.Returns(relatedContext); } return mockGraph; From 1f56ae8e05544e927f1f52032fe6f34241844513 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 22:18:25 +0200 Subject: [PATCH 15/29] Updated documentation --- docs/usage/extensibility/resource-definitions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/usage/extensibility/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md index adf156ba1f..5f0ca406be 100644 --- a/docs/usage/extensibility/resource-definitions.md +++ b/docs/usage/extensibility/resource-definitions.md @@ -202,8 +202,8 @@ public class EmployeeDefinition : JsonApiResourceDefinition { } - public override IReadOnlyCollection OnApplyIncludes( - IReadOnlyCollection existingIncludes) + public override IImmutableList OnApplyIncludes( + IImmutableList existingIncludes) { if (existingIncludes.Any(include => include.Relationship.Property.Name == nameof(Employee.Manager))) From 031af894fb15734967b58b4464addb481f81a4a8 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 3 Aug 2021 22:48:15 +0200 Subject: [PATCH 16/29] Fix cibuild --- src/JsonApiDotNetCore/ObjectExtensions.cs | 4 ++-- .../Serialization/Building/LinkBuilder.cs | 9 +-------- .../Serialization/RequestDeserializer.cs | 2 +- .../Archiving/TelevisionBroadcastDefinition.cs | 2 +- .../SkipCacheQueryStringParameterReader.cs | 1 - 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/JsonApiDotNetCore/ObjectExtensions.cs b/src/JsonApiDotNetCore/ObjectExtensions.cs index e0f4ce6af7..8657b64e96 100644 --- a/src/JsonApiDotNetCore/ObjectExtensions.cs +++ b/src/JsonApiDotNetCore/ObjectExtensions.cs @@ -21,7 +21,7 @@ public static T[] AsArray(this T element) public static List AsList(this T element) { - return new List + return new() { element }; @@ -29,7 +29,7 @@ public static List AsList(this T element) public static HashSet AsHashSet(this T element) { - return new HashSet + return new() { element }; diff --git a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs index 8d33b6b109..84ddaf1a9f 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs @@ -149,14 +149,7 @@ private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPage { var topPageSizeElement = new PaginationElementQueryStringValueExpression(null, topPageSize.Value); - if (elementInTopScopeIndex != -1) - { - elements = elements.SetItem(elementInTopScopeIndex, topPageSizeElement); - } - else - { - elements = elements.Insert(0, topPageSizeElement); - } + elements = elementInTopScopeIndex != -1 ? elements.SetItem(elementInTopScopeIndex, topPageSizeElement) : elements.Insert(0, topPageSizeElement); } else { diff --git a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs index d7ea3aabc1..8066223c0c 100644 --- a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs @@ -192,7 +192,7 @@ private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperati ResourceContext resourceContextInRef = GetExistingResourceContext(operation.Ref.Type); - if (resourceContextInRef != primaryResourceContext) + if (!primaryResourceContext.Equals(resourceContextInRef)) { throw new JsonApiSerializationException("Resource type mismatch between 'ref.type' and 'data.type' element.", $"Expected resource of type '{resourceContextInRef.PublicName}' in 'data.type', instead of '{primaryResourceContext.PublicName}'.", diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs index fc7493c370..10e2e0e6ff 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs @@ -64,7 +64,7 @@ private bool IsRequestingCollectionOfTelevisionBroadcasts() { if (_request.IsCollection) { - if (_request.PrimaryResource == ResourceContext || _request.SecondaryResource == ResourceContext) + if (ResourceContext.Equals(_request.PrimaryResource) || ResourceContext.Equals(_request.SecondaryResource)) { return true; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/SkipCacheQueryStringParameterReader.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/SkipCacheQueryStringParameterReader.cs index f129f5121e..0ec6475b15 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/SkipCacheQueryStringParameterReader.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/SkipCacheQueryStringParameterReader.cs @@ -1,4 +1,3 @@ -using System.Linq; using JetBrains.Annotations; using JsonApiDotNetCore.Controllers.Annotations; using JsonApiDotNetCore.Errors; From a925aa0267e6d95a0d1f690b049fada634eda928 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 10:23:43 +0200 Subject: [PATCH 17/29] Use "endpoint" in RequestMethodNotAllowedException message --- .../Errors/RequestMethodNotAllowedException.cs | 2 +- .../RestrictedControllers/HttpReadOnlyTests.cs | 6 +++--- .../RestrictedControllers/NoHttpDeleteTests.cs | 2 +- .../RestrictedControllers/NoHttpPatchTests.cs | 2 +- .../RestrictedControllers/NoHttpPostTests.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/JsonApiDotNetCore/Errors/RequestMethodNotAllowedException.cs b/src/JsonApiDotNetCore/Errors/RequestMethodNotAllowedException.cs index 0a2db9e061..3d3c5b163b 100644 --- a/src/JsonApiDotNetCore/Errors/RequestMethodNotAllowedException.cs +++ b/src/JsonApiDotNetCore/Errors/RequestMethodNotAllowedException.cs @@ -17,7 +17,7 @@ public RequestMethodNotAllowedException(HttpMethod method) : base(new Error(HttpStatusCode.MethodNotAllowed) { Title = "The request method is not allowed.", - Detail = $"Resource does not support {method} requests." + Detail = $"Endpoint does not support {method} requests." }) { Method = method; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/HttpReadOnlyTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/HttpReadOnlyTests.cs index 680dda6c86..03a726ab4e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/HttpReadOnlyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/HttpReadOnlyTests.cs @@ -62,7 +62,7 @@ public async Task Cannot_create_resource() Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); error.Title.Should().Be("The request method is not allowed."); - error.Detail.Should().Be("Resource does not support POST requests."); + error.Detail.Should().Be("Endpoint does not support POST requests."); } [Fact] @@ -102,7 +102,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); error.Title.Should().Be("The request method is not allowed."); - error.Detail.Should().Be("Resource does not support PATCH requests."); + error.Detail.Should().Be("Endpoint does not support PATCH requests."); } [Fact] @@ -130,7 +130,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); error.Title.Should().Be("The request method is not allowed."); - error.Detail.Should().Be("Resource does not support DELETE requests."); + error.Detail.Should().Be("Endpoint does not support DELETE requests."); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpDeleteTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpDeleteTests.cs index 56a808e7da..d04a93fe6e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpDeleteTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpDeleteTests.cs @@ -116,7 +116,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); error.Title.Should().Be("The request method is not allowed."); - error.Detail.Should().Be("Resource does not support DELETE requests."); + error.Detail.Should().Be("Endpoint does not support DELETE requests."); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPatchTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPatchTests.cs index 61c051e62e..33a9695c10 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPatchTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPatchTests.cs @@ -95,7 +95,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); error.Title.Should().Be("The request method is not allowed."); - error.Detail.Should().Be("Resource does not support PATCH requests."); + error.Detail.Should().Be("Endpoint does not support PATCH requests."); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPostTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPostTests.cs index 241c1f1770..e248087b7e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPostTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/NoHttpPostTests.cs @@ -62,7 +62,7 @@ public async Task Cannot_create_resource() Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed); error.Title.Should().Be("The request method is not allowed."); - error.Detail.Should().Be("Resource does not support POST requests."); + error.Detail.Should().Be("Endpoint does not support POST requests."); } [Fact] From 4a955a4e35bf7a4d30b9ea26b205b5a35a0068bf Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 10:32:21 +0200 Subject: [PATCH 18/29] Renamed StandardQueryStringParameters to JsonApiQueryStringParameters --- .../Annotations/DisableQueryStringAttribute.cs | 14 +++++++------- .../DefaultsQueryStringParameterReader.cs | 2 +- .../FilterQueryStringParameterReader.cs | 2 +- .../IncludeQueryStringParameterReader.cs | 2 +- .../NullsQueryStringParameterReader.cs | 2 +- .../PaginationQueryStringParameterReader.cs | 2 +- .../Internal/SortQueryStringParameterReader.cs | 2 +- ...SparseFieldSetQueryStringParameterReader.cs | 2 +- ...ters.cs => JsonApiQueryStringParameters.cs} | 2 +- .../BlockingHttpDeleteController.cs | 2 +- .../DefaultsParseTests.cs | 18 +++++++++--------- .../QueryStringParameters/FilterParseTests.cs | 10 +++++----- .../QueryStringParameters/IncludeParseTests.cs | 10 +++++----- .../QueryStringParameters/NullsParseTests.cs | 18 +++++++++--------- .../PaginationParseTests.cs | 10 +++++----- .../QueryStringParameters/SortParseTests.cs | 10 +++++----- .../SparseFieldSetParseTests.cs | 10 +++++----- 17 files changed, 59 insertions(+), 59 deletions(-) rename src/JsonApiDotNetCore/QueryStrings/{StandardQueryStringParameters.cs => JsonApiQueryStringParameters.cs} (91%) diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs index 98999ff18c..1ca196c51b 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCore.Controllers.Annotations /// Used on an ASP.NET Core controller class to indicate which query string parameters are blocked. /// /// { } /// ]]> /// ParameterNames { get; } /// /// Disables one or more of the builtin query parameters for a controller. /// - public DisableQueryStringAttribute(StandardQueryStringParameters parameters) + public DisableQueryStringAttribute(JsonApiQueryStringParameters parameters) { var parameterNames = new HashSet(); - foreach (StandardQueryStringParameters value in Enum.GetValues(typeof(StandardQueryStringParameters))) + foreach (JsonApiQueryStringParameters value in Enum.GetValues(typeof(JsonApiQueryStringParameters))) { - if (value != StandardQueryStringParameters.None && value != StandardQueryStringParameters.All && parameters.HasFlag(value)) + if (value != JsonApiQueryStringParameters.None && value != JsonApiQueryStringParameters.All && parameters.HasFlag(value)) { parameterNames.Add(value.ToString()); } @@ -45,7 +45,7 @@ public DisableQueryStringAttribute(StandardQueryStringParameters parameters) /// /// It is allowed to use a comma-separated list of strings to indicate which query parameters should be disabled, because the user may have defined - /// custom query parameters that are not included in the enum. + /// custom query parameters that are not included in the enum. /// public DisableQueryStringAttribute(string parameterNames) { @@ -54,7 +54,7 @@ public DisableQueryStringAttribute(string parameterNames) ParameterNames = parameterNames.Split(",").ToHashSet(); } - public bool ContainsParameter(StandardQueryStringParameters parameter) + public bool ContainsParameter(JsonApiQueryStringParameters parameter) { string name = parameter.ToString(); return ParameterNames.Contains(name); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs index 0faa4d6ff4..a58a16fdd3 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/DefaultsQueryStringParameterReader.cs @@ -30,7 +30,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); return _options.AllowQueryStringOverrideForSerializerDefaultValueHandling && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Defaults); + !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Defaults); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index 8486f48ded..899363178b 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -54,7 +54,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Filter); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Filter); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs index c0fbb4a60d..afdabbfbe9 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs @@ -46,7 +46,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Include); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Include); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs index 3f20c6ba2a..aa40afbf25 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/NullsQueryStringParameterReader.cs @@ -30,7 +30,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); return _options.AllowQueryStringOverrideForSerializerNullValueHandling && - !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Nulls); + !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Nulls); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs index c061c526f9..ae6d8c4e67 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs @@ -39,7 +39,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Page); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Page); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs index e06b8431e1..d96efbb4f5 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs @@ -42,7 +42,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Sort); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Sort); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index 4b8312714a..08b8fa902f 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -51,7 +51,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr { ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); - return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(StandardQueryStringParameters.Fields); + return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Fields); } /// diff --git a/src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs b/src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs similarity index 91% rename from src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs rename to src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs index 521a9af37b..95406014ef 100644 --- a/src/JsonApiDotNetCore/QueryStrings/StandardQueryStringParameters.cs +++ b/src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.QueryStrings /// Lists query string parameters used by . /// [Flags] - public enum StandardQueryStringParameters + public enum JsonApiQueryStringParameters { None = 0, Filter = 1, diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/BlockingHttpDeleteController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/BlockingHttpDeleteController.cs index 52c01ab6b8..1c0761bc83 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/BlockingHttpDeleteController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RestrictedControllers/BlockingHttpDeleteController.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.RestrictedControllers { [NoHttpDelete] - [DisableQueryString(StandardQueryStringParameters.Sort | StandardQueryStringParameters.Page)] + [DisableQueryString(JsonApiQueryStringParameters.Sort | JsonApiQueryStringParameters.Page)] public sealed class BlockingHttpDeleteController : JsonApiController { public BlockingHttpDeleteController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/DefaultsParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/DefaultsParseTests.cs index cc54815e60..d790bac795 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/DefaultsParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/DefaultsParseTests.cs @@ -34,15 +34,15 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Defaults, false, false)] - [InlineData(StandardQueryStringParameters.Defaults, true, false)] - [InlineData(StandardQueryStringParameters.All, false, false)] - [InlineData(StandardQueryStringParameters.All, true, false)] - [InlineData(StandardQueryStringParameters.None, false, false)] - [InlineData(StandardQueryStringParameters.None, true, true)] - [InlineData(StandardQueryStringParameters.Filter, false, false)] - [InlineData(StandardQueryStringParameters.Filter, true, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool allowOverride, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Defaults, false, false)] + [InlineData(JsonApiQueryStringParameters.Defaults, true, false)] + [InlineData(JsonApiQueryStringParameters.All, false, false)] + [InlineData(JsonApiQueryStringParameters.All, true, false)] + [InlineData(JsonApiQueryStringParameters.None, false, false)] + [InlineData(JsonApiQueryStringParameters.None, true, true)] + [InlineData(JsonApiQueryStringParameters.Filter, false, false)] + [InlineData(JsonApiQueryStringParameters.Filter, true, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool allowOverride, bool expectIsEnabled) { // Arrange var options = new JsonApiOptions diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs index 774031bc19..b4319447f1 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/FilterParseTests.cs @@ -43,11 +43,11 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Filter, false)] - [InlineData(StandardQueryStringParameters.All, false)] - [InlineData(StandardQueryStringParameters.None, true)] - [InlineData(StandardQueryStringParameters.Page, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Filter, false)] + [InlineData(JsonApiQueryStringParameters.All, false)] + [InlineData(JsonApiQueryStringParameters.None, true)] + [InlineData(JsonApiQueryStringParameters.Page, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool expectIsEnabled) { // Act bool isEnabled = _reader.IsEnabled(new DisableQueryStringAttribute(parametersDisabled)); diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/IncludeParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/IncludeParseTests.cs index 1836fbaaee..dcb1ce2557 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/IncludeParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/IncludeParseTests.cs @@ -37,11 +37,11 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Include, false)] - [InlineData(StandardQueryStringParameters.All, false)] - [InlineData(StandardQueryStringParameters.None, true)] - [InlineData(StandardQueryStringParameters.Filter, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Include, false)] + [InlineData(JsonApiQueryStringParameters.All, false)] + [InlineData(JsonApiQueryStringParameters.None, true)] + [InlineData(JsonApiQueryStringParameters.Filter, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool expectIsEnabled) { // Act bool isEnabled = _reader.IsEnabled(new DisableQueryStringAttribute(parametersDisabled)); diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/NullsParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/NullsParseTests.cs index 90a8739a91..63a7d5ed77 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/NullsParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/NullsParseTests.cs @@ -34,15 +34,15 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Nulls, false, false)] - [InlineData(StandardQueryStringParameters.Nulls, true, false)] - [InlineData(StandardQueryStringParameters.All, false, false)] - [InlineData(StandardQueryStringParameters.All, true, false)] - [InlineData(StandardQueryStringParameters.None, false, false)] - [InlineData(StandardQueryStringParameters.None, true, true)] - [InlineData(StandardQueryStringParameters.Filter, false, false)] - [InlineData(StandardQueryStringParameters.Filter, true, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool allowOverride, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Nulls, false, false)] + [InlineData(JsonApiQueryStringParameters.Nulls, true, false)] + [InlineData(JsonApiQueryStringParameters.All, false, false)] + [InlineData(JsonApiQueryStringParameters.All, true, false)] + [InlineData(JsonApiQueryStringParameters.None, false, false)] + [InlineData(JsonApiQueryStringParameters.None, true, true)] + [InlineData(JsonApiQueryStringParameters.Filter, false, false)] + [InlineData(JsonApiQueryStringParameters.Filter, true, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool allowOverride, bool expectIsEnabled) { // Arrange var options = new JsonApiOptions diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/PaginationParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/PaginationParseTests.cs index f3501b4609..f188a00ace 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/PaginationParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/PaginationParseTests.cs @@ -40,11 +40,11 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Page, false)] - [InlineData(StandardQueryStringParameters.All, false)] - [InlineData(StandardQueryStringParameters.None, true)] - [InlineData(StandardQueryStringParameters.Sort, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Page, false)] + [InlineData(JsonApiQueryStringParameters.All, false)] + [InlineData(JsonApiQueryStringParameters.None, true)] + [InlineData(JsonApiQueryStringParameters.Sort, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool expectIsEnabled) { // Act bool isEnabled = _reader.IsEnabled(new DisableQueryStringAttribute(parametersDisabled)); diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SortParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SortParseTests.cs index 0513fb5ca9..27d2262123 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SortParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SortParseTests.cs @@ -39,11 +39,11 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Sort, false)] - [InlineData(StandardQueryStringParameters.All, false)] - [InlineData(StandardQueryStringParameters.None, true)] - [InlineData(StandardQueryStringParameters.Filter, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Sort, false)] + [InlineData(JsonApiQueryStringParameters.All, false)] + [InlineData(JsonApiQueryStringParameters.None, true)] + [InlineData(JsonApiQueryStringParameters.Filter, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool expectIsEnabled) { // Act bool isEnabled = _reader.IsEnabled(new DisableQueryStringAttribute(parametersDisabled)); diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs index 262e04b5e4..41b7672d08 100644 --- a/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/QueryStringParameters/SparseFieldSetParseTests.cs @@ -39,11 +39,11 @@ public void Reader_Supports_Parameter_Name(string parameterName, bool expectCanP } [Theory] - [InlineData(StandardQueryStringParameters.Fields, false)] - [InlineData(StandardQueryStringParameters.All, false)] - [InlineData(StandardQueryStringParameters.None, true)] - [InlineData(StandardQueryStringParameters.Filter, true)] - public void Reader_Is_Enabled(StandardQueryStringParameters parametersDisabled, bool expectIsEnabled) + [InlineData(JsonApiQueryStringParameters.Fields, false)] + [InlineData(JsonApiQueryStringParameters.All, false)] + [InlineData(JsonApiQueryStringParameters.None, true)] + [InlineData(JsonApiQueryStringParameters.Filter, true)] + public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, bool expectIsEnabled) { // Act bool isEnabled = _reader.IsEnabled(new DisableQueryStringAttribute(parametersDisabled)); From 07a74e8e7af3096c9063f6b7a1ee64672dcb7a60 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 10:42:07 +0200 Subject: [PATCH 19/29] Replaced obsolete IQueryLayerComposer.GetResourceDefinitionAccessor() with injected service --- docs/usage/extensibility/services.md | 5 +++-- .../Queries/IQueryLayerComposer.cs | 9 --------- .../Queries/Internal/QueryLayerComposer.cs | 6 ------ .../Services/JsonApiResourceService.cs | 13 ++++++------- test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs | 1 + test/DiscoveryTests/TestResourceService.cs | 6 ++++-- .../ExceptionHandling/ConsumerArticleService.cs | 5 +++-- .../MultiTenancy/MultiTenantResourceService.cs | 10 ++++++---- .../SoftDeletionAwareResourceService.cs | 9 +++++---- 9 files changed, 28 insertions(+), 36 deletions(-) diff --git a/docs/usage/extensibility/services.md b/docs/usage/extensibility/services.md index 2e4dcd43dc..2c157ae432 100644 --- a/docs/usage/extensibility/services.md +++ b/docs/usage/extensibility/services.md @@ -18,9 +18,10 @@ public class TodoItemService : JsonApiResourceService public TodoItemService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, - IResourceChangeTracker resourceChangeTracker) + IResourceChangeTracker resourceChangeTracker, + IResourceDefinitionAccessor resourceDefinitionAccessor) : base(repositoryAccessor, queryLayerComposer, paginationContext, options, - loggerFactory, request, resourceChangeTracker) + loggerFactory, request, resourceChangeTracker, resourceDefinitionAccessor) { _notificationService = notificationService; } diff --git a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs index 35094f0e43..c3fa8428e4 100644 --- a/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs @@ -1,10 +1,8 @@ -using System; using System.Collections.Generic; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Services; namespace JsonApiDotNetCore.Queries { @@ -59,12 +57,5 @@ QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, Resourc /// Builds a query for a to-many relationship with a filter to match on its left and right resource IDs. /// QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, TId leftId, ICollection rightResourceIds); - - /// - /// Provides access to the request-scoped instance. This method has been added solely to prevent introducing a - /// breaking change in the constructor and will be removed in the next major version. - /// - [Obsolete] - IResourceDefinitionAccessor GetResourceDefinitionAccessor(); } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index b7daa9bf62..a61e9c6555 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -416,12 +416,6 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T }; } - /// - public IResourceDefinitionAccessor GetResourceDefinitionAccessor() - { - return _resourceDefinitionAccessor; - } - protected virtual IImmutableList GetIncludeElements(IImmutableList includeElements, ResourceContext resourceContext) { diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 92064666dc..369f9ae175 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -35,7 +35,7 @@ public class JsonApiResourceService : IResourceService resourceChangeTracker) + IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) { ArgumentGuard.NotNull(repositoryAccessor, nameof(repositoryAccessor)); ArgumentGuard.NotNull(queryLayerComposer, nameof(queryLayerComposer)); @@ -44,6 +44,7 @@ public JsonApiResourceService(IResourceRepositoryAccessor repositoryAccessor, IQ ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); ArgumentGuard.NotNull(request, nameof(request)); ArgumentGuard.NotNull(resourceChangeTracker, nameof(resourceChangeTracker)); + ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); _repositoryAccessor = repositoryAccessor; _queryLayerComposer = queryLayerComposer; @@ -51,11 +52,8 @@ public JsonApiResourceService(IResourceRepositoryAccessor repositoryAccessor, IQ _options = options; _request = request; _resourceChangeTracker = resourceChangeTracker; + _resourceDefinitionAccessor = resourceDefinitionAccessor; _traceWriter = new TraceLogWriter>(loggerFactory); - -#pragma warning disable 612 // Method is obsolete - _resourceDefinitionAccessor = queryLayerComposer.GetResourceDefinitionAccessor(); -#pragma warning restore 612 } /// @@ -477,8 +475,9 @@ public class JsonApiResourceService : JsonApiResourceService resourceChangeTracker) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker) + IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) { } } diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 98615ced4b..8392b92baa 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -39,6 +39,7 @@ public ServiceDiscoveryFacadeTests() _services.AddScoped(_ => new Mock().Object); _services.AddScoped(_ => new Mock().Object); _services.AddScoped(_ => new Mock().Object); + _services.AddScoped(_ => new Mock().Object); _resourceGraphBuilder = new ResourceGraphBuilder(_options, LoggerFactory); } diff --git a/test/DiscoveryTests/TestResourceService.cs b/test/DiscoveryTests/TestResourceService.cs index d5e6e53d0f..f2d565ba4d 100644 --- a/test/DiscoveryTests/TestResourceService.cs +++ b/test/DiscoveryTests/TestResourceService.cs @@ -13,8 +13,10 @@ namespace DiscoveryTests public sealed class TestResourceService : JsonApiResourceService { public TestResourceService(IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, - IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker) + IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs index fcb6af0d6b..d0b52daed3 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ExceptionHandling/ConsumerArticleService.cs @@ -20,8 +20,9 @@ public sealed class ConsumerArticleService : JsonApiResourceService resourceChangeTracker) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker) + IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs index ba6ad3e96f..dcfeb03e43 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs @@ -23,8 +23,9 @@ public class MultiTenantResourceService : JsonApiResourceService public MultiTenantResourceService(ITenantProvider tenantProvider, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, - IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker) + IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) { _tenantProvider = tenantProvider; } @@ -90,8 +91,9 @@ public class MultiTenantResourceService : MultiTenantResourceService< { public MultiTenantResourceService(ITenantProvider tenantProvider, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, - IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker) - : base(tenantProvider, repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker) + IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(tenantProvider, repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs index 002f8df2b1..001cbf569e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs @@ -26,8 +26,9 @@ public class SoftDeletionAwareResourceService : JsonApiResourceS public SoftDeletionAwareResourceService(ISystemClock systemClock, ITargetedFields targetedFields, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, - IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker) - : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker) + IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, resourceChangeTracker, + resourceDefinitionAccessor) { _systemClock = systemClock; _targetedFields = targetedFields; @@ -119,9 +120,9 @@ public class SoftDeletionAwareResourceService : SoftDeletionAwareReso { public SoftDeletionAwareResourceService(ISystemClock systemClock, ITargetedFields targetedFields, IResourceRepositoryAccessor repositoryAccessor, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext, IJsonApiOptions options, ILoggerFactory loggerFactory, - IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker) + IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) : base(systemClock, targetedFields, repositoryAccessor, queryLayerComposer, paginationContext, options, loggerFactory, request, - resourceChangeTracker) + resourceChangeTracker, resourceDefinitionAccessor) { } } From 680e809893ad630187eaac3d8e900dbade0a2db4 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 11:03:12 +0200 Subject: [PATCH 20/29] Replaced obsolete IResourceFactory.GetResourceDefinitionAccessor() with injected service --- .../JsonApiDeserializerBenchmarks.cs | 7 ++++-- .../Repositories/DbContextARepository.cs | 5 +++-- .../Repositories/DbContextBRepository.cs | 5 +++-- .../EntityFrameworkCoreRepository.cs | 22 +++++++++---------- .../Resources/IResourceFactory.cs | 8 ------- .../Resources/ResourceFactory.cs | 6 ----- .../Serialization/RequestDeserializer.cs | 8 +++---- test/DiscoveryTests/TestResourceRepository.cs | 5 +++-- .../Transactions/LyricRepository.cs | 5 +++-- .../Transactions/MusicTrackRepository.cs | 5 +++-- .../CompositeKeys/CarRepository.cs | 4 ++-- .../EagerLoading/BuildingRepository.cs | 5 +++-- .../ResultCapturingRepository.cs | 4 ++-- .../Models/ResourceConstructionTests.cs | 7 +++--- .../Server/RequestDeserializerTests.cs | 3 ++- test/UnitTests/TestResourceFactory.cs | 5 ----- 16 files changed, 47 insertions(+), 57 deletions(-) diff --git a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs index 467603226e..6e3bcf2b61 100644 --- a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs @@ -38,7 +38,9 @@ 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), new JsonApiResourceDefinition(resourceGraph)); var targetedFields = new TargetedFields(); @@ -46,7 +48,8 @@ public JsonApiDeserializerBenchmarks() 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] diff --git a/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs b/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs index 13fb0bbe48..5b07948005 100644 --- a/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs +++ b/src/Examples/MultiDbContextExample/Repositories/DbContextARepository.cs @@ -14,8 +14,9 @@ public sealed class DbContextARepository : EntityFrameworkCoreReposit where TResource : class, IIdentifiable { public DbContextARepository(ITargetedFields targetedFields, DbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { } } diff --git a/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs b/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs index 0b65caa945..afa7ed4bde 100644 --- a/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs +++ b/src/Examples/MultiDbContextExample/Repositories/DbContextBRepository.cs @@ -14,8 +14,9 @@ public sealed class DbContextBRepository : EntityFrameworkCoreReposit where TResource : class, IIdentifiable { public DbContextBRepository(ITargetedFields targetedFields, DbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { } } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 927665c965..a74b03233f 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -34,32 +34,31 @@ public class EntityFrameworkCoreRepository : IResourceRepository private readonly IResourceGraph _resourceGraph; private readonly IResourceFactory _resourceFactory; private readonly IEnumerable _constraintProviders; - private readonly TraceLogWriter> _traceWriter; private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; + private readonly TraceLogWriter> _traceWriter; /// public virtual string TransactionId => _dbContext.Database.CurrentTransaction?.TransactionId.ToString(); public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) { - ArgumentGuard.NotNull(contextResolver, nameof(contextResolver)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); + ArgumentGuard.NotNull(contextResolver, nameof(contextResolver)); ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); + ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); + ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); _targetedFields = targetedFields; + _dbContext = contextResolver.GetContext(); _resourceGraph = resourceGraph; _resourceFactory = resourceFactory; _constraintProviders = constraintProviders; - _dbContext = contextResolver.GetContext(); + _resourceDefinitionAccessor = resourceDefinitionAccessor; _traceWriter = new TraceLogWriter>(loggerFactory); - -#pragma warning disable 612 // Method is obsolete - _resourceDefinitionAccessor = resourceFactory.GetResourceDefinitionAccessor(); -#pragma warning restore 612 } /// @@ -533,8 +532,9 @@ public class EntityFrameworkCoreRepository : EntityFrameworkCoreRepos where TResource : class, IIdentifiable { public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { } } diff --git a/src/JsonApiDotNetCore/Resources/IResourceFactory.cs b/src/JsonApiDotNetCore/Resources/IResourceFactory.cs index cd520b4f6f..38a25ad996 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceFactory.cs @@ -1,6 +1,5 @@ using System; using System.Linq.Expressions; -using JsonApiDotNetCore.Repositories; namespace JsonApiDotNetCore.Resources { @@ -24,12 +23,5 @@ public TResource CreateInstance() /// Returns an expression tree that represents creating a new resource object instance. /// public NewExpression CreateNewExpression(Type resourceType); - - /// - /// Provides access to the request-scoped instance. This method has been added solely to prevent introducing a - /// breaking change in the constructor and will be removed in the next major version. - /// - [Obsolete] - IResourceDefinitionAccessor GetResourceDefinitionAccessor(); } } diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 721022d6f5..4b98b8e985 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -19,12 +19,6 @@ public ResourceFactory(IServiceProvider serviceProvider) _serviceProvider = serviceProvider; } - /// - public IResourceDefinitionAccessor GetResourceDefinitionAccessor() - { - return _serviceProvider.GetRequiredService(); - } - /// public IIdentifiable CreateInstance(Type resourceType) { diff --git a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs index 8066223c0c..9a6697c130 100644 --- a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs @@ -29,22 +29,20 @@ public class RequestDeserializer : BaseDeserializer, IJsonApiDeserializer private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; public RequestDeserializer(IResourceContextProvider resourceContextProvider, IResourceFactory resourceFactory, ITargetedFields targetedFields, - IHttpContextAccessor httpContextAccessor, IJsonApiRequest request, IJsonApiOptions options) + IHttpContextAccessor httpContextAccessor, IJsonApiRequest request, IJsonApiOptions options, IResourceDefinitionAccessor resourceDefinitionAccessor) : base(resourceContextProvider, resourceFactory) { ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); ArgumentGuard.NotNull(request, nameof(request)); ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); _targetedFields = targetedFields; _httpContextAccessor = httpContextAccessor; _request = request; _options = options; - -#pragma warning disable 612 // Method is obsolete - _resourceDefinitionAccessor = resourceFactory.GetResourceDefinitionAccessor(); -#pragma warning restore 612 + _resourceDefinitionAccessor = resourceDefinitionAccessor; } /// diff --git a/test/DiscoveryTests/TestResourceRepository.cs b/test/DiscoveryTests/TestResourceRepository.cs index b0c60f8b1b..096da8abd9 100644 --- a/test/DiscoveryTests/TestResourceRepository.cs +++ b/test/DiscoveryTests/TestResourceRepository.cs @@ -12,8 +12,9 @@ namespace DiscoveryTests public sealed class TestResourceRepository : EntityFrameworkCoreRepository { public TestResourceRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs index 9854ca9397..69bba3f622 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs @@ -16,8 +16,9 @@ public sealed class LyricRepository : EntityFrameworkCoreRepository public override string TransactionId => _extraDbContext.Database.CurrentTransaction.TransactionId.ToString(); public LyricRepository(ExtraDbContext extraDbContext, ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { _extraDbContext = extraDbContext; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs index 044cfa0a14..aabf45bb25 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs @@ -15,8 +15,9 @@ public sealed class MusicTrackRepository : EntityFrameworkCoreRepository null; public MusicTrackRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs index b7858ec889..af9690cdf4 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarRepository.cs @@ -16,8 +16,8 @@ public sealed class CarRepository : EntityFrameworkCoreRepository private readonly CarExpressionRewriter _writer; public CarRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, - IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { _writer = new CarExpressionRewriter(resourceGraph); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs index 8bf5086e06..839fe71fab 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingRepository.cs @@ -14,8 +14,9 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading public sealed class BuildingRepository : EntityFrameworkCoreRepository { public BuildingRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, - IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, + IResourceDefinitionAccessor resourceDefinitionAccessor) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs index 4435f79e56..1ebbd87fd2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs @@ -21,8 +21,8 @@ public sealed class ResultCapturingRepository : EntityFrameworkCoreRe public ResultCapturingRepository(ITargetedFields targetedFields, IDbContextResolver contextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, - ResourceCaptureStore captureStore) - : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory) + IResourceDefinitionAccessor resourceDefinitionAccessor, ResourceCaptureStore captureStore) + : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory, resourceDefinitionAccessor) { _captureStore = captureStore; } diff --git a/test/UnitTests/Models/ResourceConstructionTests.cs b/test/UnitTests/Models/ResourceConstructionTests.cs index f31d140029..82a152dd41 100644 --- a/test/UnitTests/Models/ResourceConstructionTests.cs +++ b/test/UnitTests/Models/ResourceConstructionTests.cs @@ -16,6 +16,7 @@ public sealed class ResourceConstructionTests { private readonly Mock _requestMock; private readonly Mock _mockHttpContextAccessor; + private readonly Mock _resourceDefinitionAccessorMock = new(); public ResourceConstructionTests() { @@ -37,7 +38,7 @@ public void When_resource_has_default_constructor_it_must_succeed() serviceContainer.AddService(typeof(IResourceDefinitionAccessor), new NeverResourceDefinitionAccessor()); var serializer = new RequestDeserializer(graph, new ResourceFactory(serviceContainer), new TargetedFields(), _mockHttpContextAccessor.Object, - _requestMock.Object, options); + _requestMock.Object, options, _resourceDefinitionAccessorMock.Object); var body = new { @@ -70,7 +71,7 @@ public void When_resource_has_default_constructor_that_throws_it_must_fail() serviceContainer.AddService(typeof(IResourceDefinitionAccessor), new NeverResourceDefinitionAccessor()); var serializer = new RequestDeserializer(graph, new ResourceFactory(serviceContainer), new TargetedFields(), _mockHttpContextAccessor.Object, - _requestMock.Object, options); + _requestMock.Object, options, _resourceDefinitionAccessorMock.Object); var body = new { @@ -105,7 +106,7 @@ public void When_resource_has_constructor_with_string_parameter_it_must_fail() serviceContainer.AddService(typeof(IResourceDefinitionAccessor), new NeverResourceDefinitionAccessor()); var serializer = new RequestDeserializer(graph, new ResourceFactory(serviceContainer), new TargetedFields(), _mockHttpContextAccessor.Object, - _requestMock.Object, options); + _requestMock.Object, options, _resourceDefinitionAccessorMock.Object); var body = new { diff --git a/test/UnitTests/Serialization/Server/RequestDeserializerTests.cs b/test/UnitTests/Serialization/Server/RequestDeserializerTests.cs index 3aeadc4654..e683e67c02 100644 --- a/test/UnitTests/Serialization/Server/RequestDeserializerTests.cs +++ b/test/UnitTests/Serialization/Server/RequestDeserializerTests.cs @@ -16,11 +16,12 @@ public sealed class RequestDeserializerTests : DeserializerTestsSetup private readonly RequestDeserializer _deserializer; private readonly Mock _fieldsManagerMock = new(); private readonly Mock _requestMock = new(); + private readonly Mock _resourceDefinitionAccessorMock = new(); public RequestDeserializerTests() { _deserializer = new RequestDeserializer(ResourceGraph, new TestResourceFactory(), _fieldsManagerMock.Object, MockHttpContextAccessor.Object, - _requestMock.Object, new JsonApiOptions()); + _requestMock.Object, new JsonApiOptions(), _resourceDefinitionAccessorMock.Object); } [Fact] diff --git a/test/UnitTests/TestResourceFactory.cs b/test/UnitTests/TestResourceFactory.cs index b27e334650..8df6c2e4b1 100644 --- a/test/UnitTests/TestResourceFactory.cs +++ b/test/UnitTests/TestResourceFactory.cs @@ -21,10 +21,5 @@ public NewExpression CreateNewExpression(Type resourceType) { return Expression.New(resourceType); } - - public IResourceDefinitionAccessor GetResourceDefinitionAccessor() - { - return new NeverResourceDefinitionAccessor(); - } } } From 52b3773e1fe2202dfc6e3ecf37e43ef2fda65e37 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 11:05:03 +0200 Subject: [PATCH 21/29] Made TId in IResourceDefinition contravariant --- src/JsonApiDotNetCore/Resources/IResourceDefinition.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs index ab1b9f7b34..fd52922129 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs @@ -32,8 +32,7 @@ public interface IResourceDefinition : IResourceDefinition [PublicAPI] - // ReSharper disable once TypeParameterCanBeVariant -- Justification: making TId contravariant is a breaking change. - public interface IResourceDefinition + public interface IResourceDefinition where TResource : class, IIdentifiable { /// From 2c222b1f2a95f86ceaf9a187b4e7a3f0d66a5001 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 12:34:37 +0200 Subject: [PATCH 22/29] Corrected terminology: relationships use left/right instead of primary/secondary --- docs/internals/queries.md | 2 +- docs/usage/reading/including-relationships.md | 2 +- docs/usage/reading/pagination.md | 4 +- docs/usage/reading/sorting.md | 4 +- .../reading/sparse-fieldset-selection.md | 6 +- .../Services/WorkItemService.cs | 6 +- .../Processors/AddToRelationshipProcessor.cs | 6 +- .../RemoveFromRelationshipProcessor.cs | 6 +- .../Processors/SetRelationshipProcessor.cs | 4 +- .../Controllers/BaseJsonApiController.cs | 34 +++---- .../Controllers/JsonApiCommandController.cs | 12 +-- .../Controllers/JsonApiController.cs | 12 +-- .../Internal/Parsing/SparseFieldTypeParser.cs | 6 +- .../EntityFrameworkCoreRepository.cs | 90 ++++++++++--------- .../IResourceRepositoryAccessor.cs | 6 +- .../Repositories/IResourceWriteRepository.cs | 6 +- .../ResourceRepositoryAccessor.cs | 12 +-- .../Annotations/RelationshipAttribute.cs | 4 +- .../Serialization/Building/ILinkBuilder.cs | 6 +- .../Serialization/Building/LinkBuilder.cs | 18 ++-- .../Building/ResourceObjectBuilder.cs | 4 +- .../Services/IAddToRelationshipService.cs | 8 +- .../IRemoveFromRelationshipService.cs | 8 +- .../Services/ISetRelationshipService.cs | 8 +- .../Services/JsonApiResourceService.cs | 76 ++++++++-------- .../Transactions/PerformerRepository.cs | 6 +- .../ObfuscatedIdentifiableController.cs | 12 +-- .../MultiTenantResourceService.cs | 14 +-- .../SoftDeletionAwareResourceService.cs | 14 +-- .../ServiceCollectionExtensionsTests.cs | 24 ++--- 30 files changed, 210 insertions(+), 210 deletions(-) diff --git a/docs/internals/queries.md b/docs/internals/queries.md index 9268ff24dc..b5e5c2cf19 100644 --- a/docs/internals/queries.md +++ b/docs/internals/queries.md @@ -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. diff --git a/docs/usage/reading/including-relationships.md b/docs/usage/reading/including-relationships.md index dba43fcb9f..f22d2321aa 100644 --- a/docs/usage/reading/including-relationships.md +++ b/docs/usage/reading/including-relationships.md @@ -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 diff --git a/docs/usage/reading/pagination.md b/docs/usage/reading/pagination.md index 77772288b3..ea4e30e621 100644 --- a/docs/usage/reading/pagination.md +++ b/docs/usage/reading/pagination.md @@ -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 diff --git a/docs/usage/reading/sorting.md b/docs/usage/reading/sorting.md index dfadc325fa..707720e8d9 100644 --- a/docs/usage/reading/sorting.md +++ b/docs/usage/reading/sorting.md @@ -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 diff --git a/docs/usage/reading/sparse-fieldset-selection.md b/docs/usage/reading/sparse-fieldset-selection.md index 7d90bf9d26..5c08bc6ae4 100644 --- a/docs/usage/reading/sparse-fieldset-selection.md +++ b/docs/usage/reading/sparse-fieldset-selection.md @@ -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 diff --git a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs index f934e7dc9b..6d0d81ffc8 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs @@ -73,7 +73,7 @@ public async Task CreateAsync(WorkItem resource, CancellationToken can return workItems.Single(); } - public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, + public Task AddToToManyRelationshipAsync(int leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -84,7 +84,7 @@ public Task 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(); } @@ -99,7 +99,7 @@ public async Task DeleteAsync(int id, CancellationToken cancellationToken) }, cancellationToken: cancellationToken))); } - public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, + public Task RemoveFromToManyRelationshipAsync(int leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs index 127316556d..775e896ebc 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs @@ -26,10 +26,10 @@ public virtual async Task ProcessAsync(OperationContainer op { ArgumentGuard.NotNull(operation, nameof(operation)); - var primaryId = (TId)operation.Resource.GetTypedId(); - ISet secondaryResourceIds = operation.GetSecondaryResources(); + var leftId = (TId)operation.Resource.GetTypedId(); + ISet 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; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs index b41169510d..74197f417f 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs @@ -26,10 +26,10 @@ public virtual async Task ProcessAsync(OperationContainer op { ArgumentGuard.NotNull(operation, nameof(operation)); - var primaryId = (TId)operation.Resource.GetTypedId(); - ISet secondaryResourceIds = operation.GetSecondaryResources(); + var leftId = (TId)operation.Resource.GetTypedId(); + ISet 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; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs index 8147435de6..92bd69942e 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs @@ -29,10 +29,10 @@ public virtual async Task 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; } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 1386ee76a3..c7bddff7b6 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -211,36 +211,36 @@ public virtual async Task PostAsync([FromBody] TResource resource /// Adds resources to a to-many relationship. Example: POST /articles/1/revisions HTTP/1.1 /// /// - /// The identifier of the primary resource. + /// Identifies the left side of the relationship. /// /// /// The relationship to add resources to. /// - /// + /// /// The set of resources to add to the relationship. /// /// /// Propagates notification that request handling should be canceled. /// - public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public virtual async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { id, relationshipName, - secondaryResourceIds + rightResourceIds }); ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); + ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); if (_addToRelationship == null) { throw new RequestMethodNotAllowedException(HttpMethod.Post); } - await _addToRelationship.AddToToManyRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + await _addToRelationship.AddToToManyRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); return NoContent(); } @@ -279,25 +279,25 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource /// /articles/1/relationships/revisions HTTP/1.1 /// /// - /// The identifier of the primary resource. + /// Identifies the left side of the relationship. /// /// /// The relationship for which to perform a complete replacement. /// - /// + /// /// The resource or set of resources to assign to the relationship. /// /// /// Propagates notification that request handling should be canceled. /// - public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, + public virtual async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object rightValue, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { id, relationshipName, - secondaryResourceIds + rightValue }); ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); @@ -307,7 +307,7 @@ public virtual async Task PatchRelationshipAsync(TId id, string r throw new RequestMethodNotAllowedException(HttpMethod.Patch); } - await _setRelationship.SetRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + await _setRelationship.SetRelationshipAsync(id, relationshipName, rightValue, cancellationToken); return NoContent(); } @@ -336,36 +336,36 @@ public virtual async Task DeleteAsync(TId id, CancellationToken c /// Removes resources from a to-many relationship. Example: DELETE /articles/1/relationships/revisions HTTP/1.1 /// /// - /// The identifier of the primary resource. + /// Identifies the left side of the relationship. /// /// /// The relationship to remove resources from. /// - /// + /// /// The set of resources to remove from the relationship. /// /// /// Propagates notification that request handling should be canceled. /// - public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public virtual async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { id, relationshipName, - secondaryResourceIds + rightResourceIds }); ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); + ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); if (_removeFromRelationship == null) { throw new RequestMethodNotAllowedException(HttpMethod.Delete); } - await _removeFromRelationship.RemoveFromToManyRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + await _removeFromRelationship.RemoveFromToManyRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); return NoContent(); } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs index cfc9399957..f2ed5c938b 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCommandController.cs @@ -40,10 +40,10 @@ public override async Task PostAsync([FromBody] TResource resourc /// [HttpPost("{id}/relationships/{relationshipName}")] - public override async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public override async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { - return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + return await base.PostRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); } /// @@ -55,10 +55,10 @@ public override async Task PatchAsync(TId id, [FromBody] TResourc /// [HttpPatch("{id}/relationships/{relationshipName}")] - public override async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, + public override async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object rightValue, CancellationToken cancellationToken) { - return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + return await base.PatchRelationshipAsync(id, relationshipName, rightValue, cancellationToken); } /// @@ -70,10 +70,10 @@ public override async Task DeleteAsync(TId id, CancellationToken /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { - return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + return await base.DeleteRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index a88c276a0b..5fa5557ab7 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -81,10 +81,10 @@ public override async Task PostAsync([FromBody] TResource resourc /// [HttpPost("{id}/relationships/{relationshipName}")] - public override async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public override async Task PostRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { - return await base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + return await base.PostRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); } /// @@ -96,10 +96,10 @@ public override async Task PatchAsync(TId id, [FromBody] TResourc /// [HttpPatch("{id}/relationships/{relationshipName}")] - public override async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object secondaryResourceIds, + public override async Task PatchRelationshipAsync(TId id, string relationshipName, [FromBody] object rightValue, CancellationToken cancellationToken) { - return await base.PatchRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + return await base.PatchRelationshipAsync(id, relationshipName, rightValue, cancellationToken); } /// @@ -111,10 +111,10 @@ public override async Task DeleteAsync(TId id, CancellationToken /// [HttpDelete("{id}/relationships/{relationshipName}")] - public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public override async Task DeleteRelationshipAsync(TId id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { - return await base.DeleteRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + return await base.DeleteRelationshipAsync(id, relationshipName, rightResourceIds, cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs index cc255e173d..be9764f1ff 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs @@ -54,13 +54,13 @@ private ResourceContext ParseResourceName() throw new QueryParseException("Resource type expected."); } - private ResourceContext GetResourceContext(string resourceName) + private ResourceContext GetResourceContext(string publicName) { - ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceName); + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(publicName); if (resourceContext == null) { - throw new QueryParseException($"Resource type '{resourceName}' does not exist."); + throw new QueryParseException($"Resource type '{publicName}' does not exist."); } return resourceContext; diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index a74b03233f..d1b0602a59 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -170,12 +170,12 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { - object rightResources = relationship.GetValue(resourceFromRequest); + object rightValue = relationship.GetValue(resourceFromRequest); - object rightResourcesEdited = await VisitSetRelationshipAsync(resourceForDatabase, relationship, rightResources, OperationKind.CreateResource, + object rightValueEvaluated = await VisitSetRelationshipAsync(resourceForDatabase, relationship, rightValue, OperationKind.CreateResource, cancellationToken); - await UpdateRelationshipAsync(relationship, resourceForDatabase, rightResourcesEdited, collector, cancellationToken); + await UpdateRelationshipAsync(relationship, resourceForDatabase, rightValueEvaluated, collector, cancellationToken); } foreach (AttrAttribute attribute in _targetedFields.Attributes) @@ -193,18 +193,18 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken); } - private async Task VisitSetRelationshipAsync(TResource leftResource, RelationshipAttribute relationship, object rightResourceIds, + private async Task VisitSetRelationshipAsync(TResource leftResource, RelationshipAttribute relationship, object rightValue, OperationKind operationKind, CancellationToken cancellationToken) { if (relationship is HasOneAttribute hasOneRelationship) { - return await _resourceDefinitionAccessor.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, (IIdentifiable)rightResourceIds, - operationKind, cancellationToken); + return await _resourceDefinitionAccessor.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, (IIdentifiable)rightValue, operationKind, + cancellationToken); } if (relationship is HasManyAttribute hasManyRelationship) { - HashSet rightResourceIdSet = _collectionConverter.ExtractResources(rightResourceIds).ToHashSet(IdentifiableComparer.Instance); + HashSet rightResourceIdSet = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIdSet, operationKind, cancellationToken); @@ -212,7 +212,7 @@ await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, has return rightResourceIdSet; } - return rightResourceIds; + return rightValue; } /// @@ -238,14 +238,14 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { - object rightResources = relationship.GetValue(resourceFromRequest); + object rightValue = relationship.GetValue(resourceFromRequest); - object rightResourcesEdited = await VisitSetRelationshipAsync(resourceFromDatabase, relationship, rightResources, OperationKind.UpdateResource, + object rightValueEvaluated = await VisitSetRelationshipAsync(resourceFromDatabase, relationship, rightValue, OperationKind.UpdateResource, cancellationToken); - AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightResourcesEdited); + AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightValueEvaluated); - await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightResourcesEdited, collector, cancellationToken); + await UpdateRelationshipAsync(relationship, resourceFromDatabase, rightValueEvaluated, collector, cancellationToken); } foreach (AttrAttribute attribute in _targetedFields.Attributes) @@ -374,94 +374,96 @@ private bool HasForeignKeyAtLeftSide(RelationshipAttribute relationship, INaviga } /// - public virtual async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + public virtual async Task SetRelationshipAsync(TResource leftResource, object rightValue, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { - primaryResource, - secondaryResourceIds + leftResource, + rightValue }); RelationshipAttribute relationship = _targetedFields.Relationships.Single(); - object secondaryResourceIdsEdited = - await VisitSetRelationshipAsync(primaryResource, relationship, secondaryResourceIds, OperationKind.SetRelationship, cancellationToken); + object rightValueEvaluated = + await VisitSetRelationshipAsync(leftResource, relationship, rightValue, OperationKind.SetRelationship, cancellationToken); - AssertIsNotClearingRequiredRelationship(relationship, primaryResource, secondaryResourceIdsEdited); + AssertIsNotClearingRequiredRelationship(relationship, leftResource, rightValueEvaluated); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIdsEdited, collector, cancellationToken); + await UpdateRelationshipAsync(relationship, leftResource, rightValueEvaluated, collector, cancellationToken); - await _resourceDefinitionAccessor.OnWritingAsync(primaryResource, OperationKind.SetRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(leftResource, OperationKind.SetRelationship, cancellationToken); await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(primaryResource, OperationKind.SetRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(leftResource, OperationKind.SetRelationship, cancellationToken); } /// - public virtual async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + public virtual async Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { - primaryId, - secondaryResourceIds + leftId, + rightResourceIds }); - ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); + ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); var relationship = (HasManyAttribute)_targetedFields.Relationships.Single(); - await _resourceDefinitionAccessor.OnAddToRelationshipAsync(primaryId, relationship, secondaryResourceIds, cancellationToken); + await _resourceDefinitionAccessor.OnAddToRelationshipAsync(leftId, relationship, rightResourceIds, cancellationToken); - if (secondaryResourceIds.Any()) + if (rightResourceIds.Any()) { using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - TResource primaryResource = collector.CreateForId(primaryId); + TResource leftResource = collector.CreateForId(leftId); - await UpdateRelationshipAsync(relationship, primaryResource, secondaryResourceIds, collector, cancellationToken); + await UpdateRelationshipAsync(relationship, leftResource, rightResourceIds, collector, cancellationToken); - await _resourceDefinitionAccessor.OnWritingAsync(primaryResource, OperationKind.AddToRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(leftResource, OperationKind.AddToRelationship, cancellationToken); await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(primaryResource, OperationKind.AddToRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(leftResource, OperationKind.AddToRelationship, cancellationToken); } } /// - public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + public virtual async Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { - primaryResource, - secondaryResourceIds + leftResource, + rightResourceIds }); - ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); + ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); var relationship = (HasManyAttribute)_targetedFields.Relationships.Single(); - await _resourceDefinitionAccessor.OnRemoveFromRelationshipAsync(primaryResource, relationship, secondaryResourceIds, cancellationToken); + await _resourceDefinitionAccessor.OnRemoveFromRelationshipAsync(leftResource, relationship, rightResourceIds, cancellationToken); - if (secondaryResourceIds.Any()) + if (rightResourceIds.Any()) { - object rightValue = relationship.GetValue(primaryResource); + object rightValueStored = relationship.GetValue(leftResource); + + HashSet rightResourceIdsToStore = + _collectionConverter.ExtractResources(rightValueStored).ToHashSet(IdentifiableComparer.Instance); - HashSet rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); - rightResourceIds.ExceptWith(secondaryResourceIds); + rightResourceIdsToStore.ExceptWith(rightResourceIds); - AssertIsNotClearingRequiredRelationship(relationship, primaryResource, rightResourceIds); + AssertIsNotClearingRequiredRelationship(relationship, leftResource, rightResourceIdsToStore); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); - await UpdateRelationshipAsync(relationship, primaryResource, rightResourceIds, collector, cancellationToken); + await UpdateRelationshipAsync(relationship, leftResource, rightResourceIdsToStore, collector, cancellationToken); - await _resourceDefinitionAccessor.OnWritingAsync(primaryResource, OperationKind.RemoveFromRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(leftResource, OperationKind.RemoveFromRelationship, cancellationToken); await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(primaryResource, OperationKind.RemoveFromRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(leftResource, OperationKind.RemoveFromRelationship, cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs index 607512c242..eec44078ae 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs @@ -63,19 +63,19 @@ Task DeleteAsync(TId id, CancellationToken cancellationToken) /// /// Invokes . /// - Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + Task SetRelationshipAsync(TResource leftResource, object rightValue, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource type. /// - Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes . /// - Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs index 049e02ce8c..9cdfee02fd 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs @@ -57,16 +57,16 @@ public interface IResourceWriteRepository /// /// Performs a complete replacement of the relationship in the underlying data store. /// - Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken); + Task SetRelationshipAsync(TResource leftResource, object rightValue, CancellationToken cancellationToken); /// /// Adds resources to a to-many relationship in the underlying data store. /// - Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken); + Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, CancellationToken cancellationToken); /// /// Removes resources from a to-many relationship in the underlying data store. /// - Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken); + Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index d9e7c5d33e..f61ac5daa6 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -98,29 +98,29 @@ public async Task DeleteAsync(TId id, CancellationToken cancella } /// - public async Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + public async Task SetRelationshipAsync(TResource leftResource, object rightValue, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.SetRelationshipAsync(primaryResource, secondaryResourceIds, cancellationToken); + await repository.SetRelationshipAsync(leftResource, rightValue, cancellationToken); } /// - public async Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, + public async Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.AddToToManyRelationshipAsync(primaryId, secondaryResourceIds, cancellationToken); + await repository.AddToToManyRelationshipAsync(leftId, rightResourceIds, cancellationToken); } /// - public async Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + public async Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); - await repository.RemoveFromToManyRelationshipAsync(primaryResource, secondaryResourceIds, cancellationToken); + await repository.RemoveFromToManyRelationshipAsync(leftResource, rightResourceIds, cancellationToken); } protected virtual object ResolveReadRepository(Type resourceType) diff --git a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs index 17a84eda4d..07ba0bc152 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs @@ -36,12 +36,12 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute public PropertyInfo InverseNavigationProperty { get; set; } /// - /// The parent resource type. This is the type of the class in which this attribute was used. + /// The containing type in which this relationship is declared. /// public Type LeftType { get; internal set; } /// - /// The child resource type. This does not necessarily match the navigation property type. In the case of a relationship, + /// The type this relationship points to. This does not necessarily match the relationship property type. In the case of a relationship, /// this value will be the collection element type. /// /// diff --git a/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs index 17a492f9b2..86462aee54 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ILinkBuilder.cs @@ -15,13 +15,13 @@ public interface ILinkBuilder TopLevelLinks GetTopLevelLinks(); /// - /// Builds the links object for resources in the primary data. + /// Builds the links object for a returned resource (primary or included). /// ResourceLinks GetResourceLinks(string resourceName, string id); /// - /// Builds the links object that is included in the values of the . + /// Builds the links object for a relationship inside a returned resource. /// - RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable parent); + RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource); } } diff --git a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs index 84ddaf1a9f..3ffcedbdfe 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs @@ -264,39 +264,39 @@ private string GetLinkForResourceSelf(ResourceContext resourceContext, string re } /// - public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable parent) + public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource) { ArgumentGuard.NotNull(relationship, nameof(relationship)); - ArgumentGuard.NotNull(parent, nameof(parent)); + ArgumentGuard.NotNull(leftResource, nameof(leftResource)); var links = new RelationshipLinks(); - ResourceContext leftResourceContext = _resourceContextProvider.GetResourceContext(parent.GetType()); + ResourceContext leftResourceContext = _resourceContextProvider.GetResourceContext(leftResource.GetType()); if (ShouldIncludeRelationshipLink(LinkTypes.Self, relationship, leftResourceContext)) { - links.Self = GetLinkForRelationshipSelf(parent.StringId, relationship); + links.Self = GetLinkForRelationshipSelf(leftResource.StringId, relationship); } if (ShouldIncludeRelationshipLink(LinkTypes.Related, relationship, leftResourceContext)) { - links.Related = GetLinkForRelationshipRelated(parent.StringId, relationship); + links.Related = GetLinkForRelationshipRelated(leftResource.StringId, relationship); } return links.HasValue() ? links : null; } - private string GetLinkForRelationshipSelf(string primaryId, RelationshipAttribute relationship) + private string GetLinkForRelationshipSelf(string leftId, RelationshipAttribute relationship) { string controllerName = _controllerResourceMapping.GetControllerNameForResourceType(relationship.LeftType); - IDictionary routeValues = GetRouteValues(primaryId, relationship.PublicName); + IDictionary routeValues = GetRouteValues(leftId, relationship.PublicName); return RenderLinkForAction(controllerName, GetRelationshipControllerActionName, routeValues); } - private string GetLinkForRelationshipRelated(string primaryId, RelationshipAttribute relationship) + private string GetLinkForRelationshipRelated(string leftId, RelationshipAttribute relationship) { string controllerName = _controllerResourceMapping.GetControllerNameForResourceType(relationship.LeftType); - IDictionary routeValues = GetRouteValues(primaryId, relationship.PublicName); + IDictionary routeValues = GetRouteValues(leftId, relationship.PublicName); return RenderLinkForAction(controllerName, GetSecondaryControllerActionName, routeValues); } diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs index d6633910ba..787ce2a53b 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs @@ -133,11 +133,11 @@ private IList GetRelatedResourceLinkageForHasMany(HasM /// private ResourceIdentifierObject GetResourceIdentifier(IIdentifiable resource) { - string resourceName = ResourceContextProvider.GetResourceContext(resource.GetType()).PublicName; + string publicName = ResourceContextProvider.GetResourceContext(resource.GetType()).PublicName; return new ResourceIdentifierObject { - Type = resourceName, + Type = publicName, Id = resource.StringId }; } diff --git a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs index 5bbc74ae71..9953e1f819 100644 --- a/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs @@ -22,19 +22,19 @@ public interface IAddToRelationshipService /// /// Handles a JSON:API request to add resources to a to-many relationship. /// - /// - /// The identifier of the primary resource. + /// + /// Identifies the left side of the relationship. /// /// /// The relationship to add resources to. /// - /// + /// /// The set of resources to add to the relationship. /// /// /// Propagates notification that request handling should be canceled. /// - Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs index c322732cc7..8dd1114b3a 100644 --- a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs @@ -20,19 +20,19 @@ public interface IRemoveFromRelationshipService /// /// Handles a JSON:API request to remove resources from a to-many relationship. /// - /// - /// The identifier of the primary resource. + /// + /// Identifies the left side of the relationship. /// /// /// The relationship to remove resources from. /// - /// + /// /// The set of resources to remove from the relationship. /// /// /// Propagates notification that request handling should be canceled. /// - Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + Task RemoveFromToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs index 28904f1a28..2afc8a175a 100644 --- a/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/ISetRelationshipService.cs @@ -19,18 +19,18 @@ public interface ISetRelationshipService /// /// Handles a JSON:API request to perform a complete replacement of a relationship on an existing resource. /// - /// - /// The identifier of the primary resource. + /// + /// Identifies the left side of the relationship. /// /// /// The relationship for which to perform a complete replacement. /// - /// + /// /// The resource or set of resources to assign to the relationship. /// /// /// Propagates notification that request handling should be canceled. /// - Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken); + Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 369f9ae175..678df35b88 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -113,14 +113,14 @@ public virtual async Task GetSecondaryAsync(TId id, string relationshipN TResource primaryResource = primaryResources.SingleOrDefault(); AssertPrimaryResourceExists(primaryResource); - object secondaryResourceOrResources = _request.Relationship.GetValue(primaryResource); + object rightValue = _request.Relationship.GetValue(primaryResource); - if (secondaryResourceOrResources is ICollection secondaryResources && secondaryLayer.Pagination?.PageSize?.Value == secondaryResources.Count) + if (rightValue is ICollection rightResources && secondaryLayer.Pagination?.PageSize?.Value == rightResources.Count) { _paginationContext.IsPageFull = true; } - return secondaryResourceOrResources; + return rightValue; } /// @@ -241,64 +241,64 @@ private async IAsyncEnumerable GetMissingRightRes } /// - public virtual async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + public virtual async Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { - primaryId, - secondaryResourceIds + leftId, + rightResourceIds }); ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); + ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); AssertHasRelationship(_request.Relationship, relationshipName); - if (secondaryResourceIds.Any() && _request.Relationship is HasManyAttribute { IsManyToMany: true } manyToManyRelationship) + if (rightResourceIds.Any() && _request.Relationship is HasManyAttribute { IsManyToMany: true } manyToManyRelationship) { // In the case of a many-to-many relationship, creating a duplicate entry in the join table results in a // unique constraint violation. We avoid that by excluding already-existing entries from the set in advance. - await RemoveExistingIdsFromSecondarySetAsync(primaryId, secondaryResourceIds, manyToManyRelationship, cancellationToken); + await RemoveExistingIdsFromRelationshipRightSideAsync(manyToManyRelationship, leftId, rightResourceIds, cancellationToken); } try { - await _repositoryAccessor.AddToToManyRelationshipAsync(primaryId, secondaryResourceIds, cancellationToken); + await _repositoryAccessor.AddToToManyRelationshipAsync(leftId, rightResourceIds, cancellationToken); } catch (DataStoreUpdateException) { - _ = await GetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute, cancellationToken); - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + _ = await GetPrimaryResourceByIdAsync(leftId, TopFieldSelection.OnlyIdAttribute, cancellationToken); + await AssertRightResourcesExistAsync(rightResourceIds, cancellationToken); throw; } } - private async Task RemoveExistingIdsFromSecondarySetAsync(TId primaryId, ISet secondaryResourceIds, HasManyAttribute hasManyRelationship, - CancellationToken cancellationToken) + private async Task RemoveExistingIdsFromRelationshipRightSideAsync(HasManyAttribute hasManyRelationship, TId leftId, + ISet rightResourceIds, CancellationToken cancellationToken) { - QueryLayer queryLayer = _queryLayerComposer.ComposeForHasMany(hasManyRelationship, primaryId, secondaryResourceIds); - IReadOnlyCollection primaryResources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); + QueryLayer queryLayer = _queryLayerComposer.ComposeForHasMany(hasManyRelationship, leftId, rightResourceIds); + IReadOnlyCollection leftResources = await _repositoryAccessor.GetAsync(queryLayer, cancellationToken); - TResource primaryResource = primaryResources.FirstOrDefault(); - AssertPrimaryResourceExists(primaryResource); + TResource leftResource = leftResources.FirstOrDefault(); + AssertPrimaryResourceExists(leftResource); - object rightValue = _request.Relationship.GetValue(primaryResource); + object rightValue = _request.Relationship.GetValue(leftResource); ICollection existingRightResourceIds = _collectionConverter.ExtractResources(rightValue); - secondaryResourceIds.ExceptWith(existingRightResourceIds); + rightResourceIds.ExceptWith(existingRightResourceIds); } - protected async Task AssertRightResourcesExistAsync(object rightResourceIds, CancellationToken cancellationToken) + protected async Task AssertRightResourcesExistAsync(object rightValue, CancellationToken cancellationToken) { - ICollection secondaryResourceIds = _collectionConverter.ExtractResources(rightResourceIds); + ICollection rightResourceIds = _collectionConverter.ExtractResources(rightValue); - if (secondaryResourceIds.Any()) + if (rightResourceIds.Any()) { - QueryLayer queryLayer = _queryLayerComposer.ComposeForGetRelationshipRightIds(_request.Relationship, secondaryResourceIds); + QueryLayer queryLayer = _queryLayerComposer.ComposeForGetRelationshipRightIds(_request.Relationship, rightResourceIds); List missingResources = - await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, secondaryResourceIds, cancellationToken) + await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, rightResourceIds, cancellationToken) .ToListAsync(cancellationToken); if (missingResources.Any()) @@ -347,30 +347,30 @@ public virtual async Task UpdateAsync(TId id, TResource resource, Can } /// - public virtual async Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) + public virtual async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { - primaryId, + leftId, relationshipName, - secondaryResourceIds + rightValue }); ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); AssertHasRelationship(_request.Relationship, relationshipName); - TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(primaryId, cancellationToken); + TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(leftId, cancellationToken); await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, OperationKind.SetRelationship, cancellationToken); try { - await _repositoryAccessor.SetRelationshipAsync(resourceFromDatabase, secondaryResourceIds, cancellationToken); + await _repositoryAccessor.SetRelationshipAsync(resourceFromDatabase, rightValue, cancellationToken); } catch (DataStoreUpdateException) { - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + await AssertRightResourcesExistAsync(rightValue, cancellationToken); throw; } } @@ -395,28 +395,28 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke } /// - public virtual async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + public virtual async Task RemoveFromToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { _traceWriter.LogMethodStart(new { - primaryId, + leftId, relationshipName, - secondaryResourceIds + rightResourceIds }); ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); + ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); AssertHasRelationship(_request.Relationship, relationshipName); - TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(primaryId, cancellationToken); + TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(leftId, cancellationToken); await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, OperationKind.RemoveFromRelationship, cancellationToken); - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + await AssertRightResourcesExistAsync(rightResourceIds, cancellationToken); - await _repositoryAccessor.RemoveFromToManyRelationshipAsync(resourceFromDatabase, secondaryResourceIds, cancellationToken); + await _repositoryAccessor.RemoveFromToManyRelationshipAsync(resourceFromDatabase, rightResourceIds, cancellationToken); } protected async Task GetPrimaryResourceByIdAsync(TId id, TopFieldSelection fieldSelection, CancellationToken cancellationToken) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs index 555c9ba955..6a5f092e28 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs @@ -48,17 +48,17 @@ public Task DeleteAsync(int id, CancellationToken cancellationToken) throw new NotImplementedException(); } - public Task SetRelationshipAsync(Performer primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + public Task SetRelationshipAsync(Performer leftResource, object rightValue, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task AddToToManyRelationshipAsync(int primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task AddToToManyRelationshipAsync(int leftId, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(Performer primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task RemoveFromToManyRelationshipAsync(Performer leftResource, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index d1e44519f9..b531018179 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -54,11 +54,11 @@ public override Task PostAsync([FromBody] TResource resource, Can } [HttpPost("{id}/relationships/{relationshipName}")] - public Task PostRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public Task PostRelationshipAsync(string id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { int idValue = _codec.Decode(id); - return base.PostRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); + return base.PostRelationshipAsync(idValue, relationshipName, rightResourceIds, cancellationToken); } [HttpPatch("{id}")] @@ -69,11 +69,11 @@ public Task PatchAsync(string id, [FromBody] TResource resource, } [HttpPatch("{id}/relationships/{relationshipName}")] - public Task PatchRelationshipAsync(string id, string relationshipName, [FromBody] object secondaryResourceIds, + public Task PatchRelationshipAsync(string id, string relationshipName, [FromBody] object rightValue, CancellationToken cancellationToken) { int idValue = _codec.Decode(id); - return base.PatchRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); + return base.PatchRelationshipAsync(idValue, relationshipName, rightValue, cancellationToken); } [HttpDelete("{id}")] @@ -84,11 +84,11 @@ public Task DeleteAsync(string id, CancellationToken cancellation } [HttpDelete("{id}/relationships/{relationshipName}")] - public Task DeleteRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, + public Task DeleteRelationshipAsync(string id, string relationshipName, [FromBody] ISet rightResourceIds, CancellationToken cancellationToken) { int idValue = _codec.Decode(id); - return base.DeleteRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); + return base.DeleteRelationshipAsync(idValue, relationshipName, rightResourceIds, cancellationToken); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs index dcfeb03e43..527eb97ea5 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs @@ -60,21 +60,21 @@ public override async Task UpdateAsync(TId id, TResource resource, Ca return await base.UpdateAsync(id, resource, cancellationToken); } - public override async Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, + public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, CancellationToken cancellationToken) { - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + await AssertRightResourcesExistAsync(rightValue, cancellationToken); - await base.SetRelationshipAsync(primaryId, relationshipName, secondaryResourceIds, cancellationToken); + await base.SetRelationshipAsync(leftId, relationshipName, rightValue, cancellationToken); } - public override async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + public override async Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { - _ = await GetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute, cancellationToken); - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + _ = await GetPrimaryResourceByIdAsync(leftId, TopFieldSelection.OnlyIdAttribute, cancellationToken); + await AssertRightResourcesExistAsync(rightResourceIds, cancellationToken); - await base.AddToToManyRelationshipAsync(primaryId, relationshipName, secondaryResourceIds, cancellationToken); + await base.AddToToManyRelationshipAsync(leftId, relationshipName, rightResourceIds, cancellationToken); } public override async Task DeleteAsync(TId id, CancellationToken cancellationToken) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs index 001cbf569e..afc9f19ed0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs @@ -59,31 +59,31 @@ public override async Task UpdateAsync(TId id, TResource resource, Ca return await base.UpdateAsync(id, resource, cancellationToken); } - public override async Task SetRelationshipAsync(TId primaryId, string relationshipName, object secondaryResourceIds, + public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, CancellationToken cancellationToken) { if (IsSoftDeletable(_request.Relationship.RightType)) { - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + await AssertRightResourcesExistAsync(rightValue, cancellationToken); } - await base.SetRelationshipAsync(primaryId, relationshipName, secondaryResourceIds, cancellationToken); + await base.SetRelationshipAsync(leftId, relationshipName, rightValue, cancellationToken); } - public override async Task AddToToManyRelationshipAsync(TId primaryId, string relationshipName, ISet secondaryResourceIds, + public override async Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { if (IsSoftDeletable(typeof(TResource))) { - _ = await GetPrimaryResourceByIdAsync(primaryId, TopFieldSelection.OnlyIdAttribute, cancellationToken); + _ = await GetPrimaryResourceByIdAsync(leftId, TopFieldSelection.OnlyIdAttribute, cancellationToken); } if (IsSoftDeletable(_request.Relationship.RightType)) { - await AssertRightResourcesExistAsync(secondaryResourceIds, cancellationToken); + await AssertRightResourcesExistAsync(rightResourceIds, cancellationToken); } - await base.AddToToManyRelationshipAsync(primaryId, relationshipName, secondaryResourceIds, cancellationToken); + await base.AddToToManyRelationshipAsync(leftId, relationshipName, rightResourceIds, cancellationToken); } public override async Task DeleteAsync(TId id, CancellationToken cancellationToken) diff --git a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs index 330269f335..ad08747e3e 100644 --- a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -220,7 +220,7 @@ public Task CreateAsync(IntResource resource, CancellationToken can throw new NotImplementedException(); } - public Task AddToToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, + public Task AddToToManyRelationshipAsync(int leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -231,7 +231,7 @@ public Task UpdateAsync(int id, IntResource resource, CancellationT 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(); } @@ -241,7 +241,7 @@ public Task DeleteAsync(int id, CancellationToken cancellationToken) throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(int primaryId, string relationshipName, ISet secondaryResourceIds, + public Task RemoveFromToManyRelationshipAsync(int leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -276,7 +276,7 @@ public Task CreateAsync(GuidResource resource, CancellationToken c throw new NotImplementedException(); } - public Task AddToToManyRelationshipAsync(Guid primaryId, string relationshipName, ISet secondaryResourceIds, + public Task AddToToManyRelationshipAsync(Guid leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -287,7 +287,7 @@ public Task UpdateAsync(Guid id, GuidResource resource, Cancellati throw new NotImplementedException(); } - public Task SetRelationshipAsync(Guid primaryId, string relationshipName, object secondaryResourceIds, CancellationToken cancellationToken) + public Task SetRelationshipAsync(Guid leftId, string relationshipName, object rightValue, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -297,7 +297,7 @@ public Task DeleteAsync(Guid id, CancellationToken cancellationToken) throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(Guid primaryId, string relationshipName, ISet secondaryResourceIds, + public Task RemoveFromToManyRelationshipAsync(Guid leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -342,17 +342,17 @@ public Task DeleteAsync(int id, CancellationToken cancellationToken) throw new NotImplementedException(); } - public Task SetRelationshipAsync(IntResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + public Task SetRelationshipAsync(IntResource leftResource, object rightValue, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task AddToToManyRelationshipAsync(int primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task AddToToManyRelationshipAsync(int leftId, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(IntResource primaryResource, ISet secondaryResourceIds, + public Task RemoveFromToManyRelationshipAsync(IntResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -397,17 +397,17 @@ public Task DeleteAsync(Guid id, CancellationToken cancellationToken) throw new NotImplementedException(); } - public Task SetRelationshipAsync(GuidResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + public Task SetRelationshipAsync(GuidResource leftResource, object rightValue, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task AddToToManyRelationshipAsync(Guid primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + public Task AddToToManyRelationshipAsync(Guid leftId, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(GuidResource primaryResource, ISet secondaryResourceIds, + public Task RemoveFromToManyRelationshipAsync(GuidResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); From a1bef68df886109e28c4729c1e2a619ed12bbafc Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 12:37:50 +0200 Subject: [PATCH 23/29] Removed obsolete IJsonApiRequest.BasePath --- .../Middleware/IJsonApiRequest.cs | 13 ----- .../Middleware/JsonApiMiddleware.cs | 57 ------------------- .../Middleware/JsonApiRequest.cs | 6 -- .../Serialization/RequestDeserializer.cs | 9 --- .../Middleware/JsonApiRequestTests.cs | 1 - 5 files changed, 86 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs index 081d398b39..c7e3e91499 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs @@ -1,4 +1,3 @@ -using System; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources.Annotations; @@ -14,18 +13,6 @@ public interface IJsonApiRequest /// public EndpointKind Kind { get; } - /// - /// The request URL prefix. This may be an absolute or relative path, depending on . - /// - /// - /// - /// - [Obsolete("This value is calculated for backwards compatibility, but it is no longer used and will be removed in a future version.")] - string BasePath { get; } - /// /// The ID of the primary (top-level) resource for this request. This would be null in "/blogs", "123" in "/blogs/123" or "/blogs/123/author". /// diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 7bb694879e..9e40eedb99 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -13,7 +11,6 @@ using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; @@ -218,7 +215,6 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext request.Kind = EndpointKind.Primary; request.PrimaryResource = primaryResourceContext; request.PrimaryId = GetPrimaryRequestId(routeValues); - request.BasePath = GetBasePath(primaryResourceContext.PublicName, options, httpRequest); string relationshipName = GetRelationshipNameForSecondaryRequest(routeValues); @@ -245,58 +241,6 @@ private static string GetPrimaryRequestId(RouteValueDictionary routeValues) return routeValues.TryGetValue("id", out object id) ? (string)id : null; } - private static string GetBasePath(string resourceName, IJsonApiOptions options, HttpRequest httpRequest) - { - var builder = new StringBuilder(); - - if (!options.UseRelativeLinks) - { - builder.Append(httpRequest.Scheme); - builder.Append("://"); - builder.Append(httpRequest.Host); - } - - if (httpRequest.PathBase.HasValue) - { - builder.Append(httpRequest.PathBase); - } - - string customRoute = GetCustomRoute(resourceName, options.Namespace, httpRequest.HttpContext); - - if (!string.IsNullOrEmpty(customRoute)) - { - builder.Append('/'); - builder.Append(customRoute); - } - else if (!string.IsNullOrEmpty(options.Namespace)) - { - builder.Append('/'); - builder.Append(options.Namespace); - } - - return builder.ToString(); - } - - private static string GetCustomRoute(string resourceName, string apiNamespace, HttpContext httpContext) - { - if (resourceName != null) - { - Endpoint endpoint = httpContext.GetEndpoint(); - var routeAttribute = endpoint?.Metadata.GetMetadata(); - - if (routeAttribute != null && httpContext.Request.Path.Value != null) - { - List trimmedComponents = httpContext.Request.Path.Value.Trim('/').Split('/').ToList(); - int resourceNameIndex = trimmedComponents.FindIndex(component => component == resourceName); - string[] newComponents = trimmedComponents.Take(resourceNameIndex).ToArray(); - string customRoute = string.Join('/', newComponents); - return customRoute == apiNamespace ? null : customRoute; - } - } - - return null; - } - private static string GetRelationshipNameForSecondaryRequest(RouteValueDictionary routeValues) { return routeValues.TryGetValue("relationshipName", out object routeValue) ? (string)routeValue : null; @@ -318,7 +262,6 @@ private static void SetupOperationsRequest(JsonApiRequest request, IJsonApiOptio { request.IsReadOnly = false; request.Kind = EndpointKind.AtomicOperations; - request.BasePath = GetBasePath(null, options, httpRequest); } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs index b732529e2d..3956393814 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs @@ -11,9 +11,6 @@ public sealed class JsonApiRequest : IJsonApiRequest /// public EndpointKind Kind { get; set; } - /// - public string BasePath { get; set; } - /// public string PrimaryId { get; set; } @@ -44,9 +41,6 @@ public void CopyFrom(IJsonApiRequest other) ArgumentGuard.NotNull(other, nameof(other)); Kind = other.Kind; -#pragma warning disable CS0618 // Type or member is obsolete - BasePath = other.BasePath; -#pragma warning restore CS0618 // Type or member is obsolete PrimaryId = other.PrimaryId; PrimaryResource = other.PrimaryResource; SecondaryResource = other.SecondaryResource; diff --git a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs index 9a6697c130..2ba04f728a 100644 --- a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs @@ -203,9 +203,6 @@ private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperati var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, -#pragma warning disable CS0618 // Type or member is obsolete - BasePath = _request.BasePath, -#pragma warning restore CS0618 // Type or member is obsolete PrimaryResource = primaryResourceContext, OperationKind = kind }; @@ -329,9 +326,6 @@ private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, -#pragma warning disable CS0618 // Type or member is obsolete - BasePath = _request.BasePath, -#pragma warning restore CS0618 // Type or member is obsolete PrimaryId = primaryResource.StringId, PrimaryResource = primaryResourceContext, OperationKind = kind @@ -366,9 +360,6 @@ private OperationContainer ParseForRelationshipOperation(AtomicOperationObject o var request = new JsonApiRequest { Kind = EndpointKind.AtomicOperations, -#pragma warning disable CS0618 // Type or member is obsolete - BasePath = _request.BasePath, -#pragma warning restore CS0618 // Type or member is obsolete PrimaryId = primaryResource.StringId, PrimaryResource = primaryResourceContext, SecondaryResource = secondaryResourceContext, diff --git a/test/UnitTests/Middleware/JsonApiRequestTests.cs b/test/UnitTests/Middleware/JsonApiRequestTests.cs index 5c0c010d13..a7a1d10eeb 100644 --- a/test/UnitTests/Middleware/JsonApiRequestTests.cs +++ b/test/UnitTests/Middleware/JsonApiRequestTests.cs @@ -70,7 +70,6 @@ public async Task Sets_request_properties_correctly(string requestMethod, string request.IsCollection.Should().Be(expectIsCollection); request.Kind.Should().Be(expectKind); request.IsReadOnly.Should().Be(expectIsReadOnly); - request.BasePath.Should().BeEmpty(); request.PrimaryResource.Should().NotBeNull(); request.PrimaryResource.PublicName.Should().Be("todoItems"); } From d6b1f9de542c928e26d9d0b6c2ea03d957095bfd Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 12:41:50 +0200 Subject: [PATCH 24/29] Removed EntityFrameworkCoreSupport (no longer needed for EF Core 5+) --- .../QueryableBuilding/SelectClauseBuilder.cs | 19 ------ .../EntityFrameworkCoreRepository.cs | 10 +-- .../EntityFrameworkCoreSupport.cs | 12 ---- .../MemoryLeakDetectionBugRewriter.cs | 62 ------------------- .../Resources/ResourceFactory.cs | 2 +- 5 files changed, 2 insertions(+), 103 deletions(-) delete mode 100644 src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs delete mode 100644 src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs index d8fb26f15a..75798f573a 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs @@ -186,25 +186,6 @@ private Expression CreateCollectionInitializer(LambdaScope lambdaScope, Property Expression layerExpression = builder.ApplyQuery(layer); - // Earlier versions of EF Core 3.x failed to understand `query.ToHashSet()`, so we emit `new HashSet(query)` instead. - // Interestingly, EF Core 5 RC1 fails to understand `new HashSet(query)`, so we emit `query.ToHashSet()` instead. - // https://github.com/dotnet/efcore/issues/22902 - - if (EntityFrameworkCoreSupport.Version.Major < 5) - { - Type enumerableOfElementType = typeof(IEnumerable<>).MakeGenericType(elementType); - Type typedCollection = CollectionConverter.ToConcreteCollectionType(collectionProperty.PropertyType); - - ConstructorInfo typedCollectionConstructor = typedCollection.GetConstructor(enumerableOfElementType.AsArray()); - - if (typedCollectionConstructor == null) - { - throw new InvalidOperationException($"Constructor on '{typedCollection.Name}' that accepts '{enumerableOfElementType.Name}' not found."); - } - - return Expression.New(typedCollectionConstructor, layerExpression); - } - string operationName = CollectionConverter.TypeCanContainHashSet(collectionProperty.PropertyType) ? "ToHashSet" : "ToList"; return CopyCollectionExtensionMethodCall(layerExpression, operationName, elementType); } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index d1b0602a59..c1261c899a 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -103,14 +103,6 @@ protected virtual IQueryable ApplyQueryLayer(QueryLayer layer) ArgumentGuard.NotNull(layer, nameof(layer)); - QueryLayer rewrittenLayer = layer; - - if (EntityFrameworkCoreSupport.Version.Major < 5) - { - var writer = new MemoryLeakDetectionBugRewriter(); - rewrittenLayer = writer.Rewrite(layer); - } - IQueryable source = GetAll(); // @formatter:wrap_chained_method_calls chop_always @@ -136,7 +128,7 @@ protected virtual IQueryable ApplyQueryLayer(QueryLayer layer) var builder = new QueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, _resourceGraph, _dbContext.Model); - Expression expression = builder.ApplyQuery(rewrittenLayer); + Expression expression = builder.ApplyQuery(layer); return source.Provider.CreateQuery(expression); } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs deleted file mode 100644 index 042c4613ce..0000000000 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; - -#pragma warning disable AV1008 // Class should not be static - -namespace JsonApiDotNetCore.Repositories -{ - internal static class EntityFrameworkCoreSupport - { - public static Version Version { get; } = typeof(DbContext).Assembly.GetName().Version; - } -} diff --git a/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs b/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs deleted file mode 100644 index 557f330791..0000000000 --- a/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCore.Repositories -{ - /// - /// Removes projections from a when its resource type uses injected parameters, as a workaround for EF Core bug - /// https://github.com/dotnet/efcore/issues/20502, which exists in versions below v5. - /// - /// - /// Note that by using this workaround, nested filtering, paging and sorting all remain broken in EF Core 3.1 when using injected parameters in - /// resources. But at least it enables simple top-level queries to succeed without an exception. - /// - [PublicAPI] - public sealed class MemoryLeakDetectionBugRewriter - { - public QueryLayer Rewrite(QueryLayer queryLayer) - { - ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); - - return RewriteLayer(queryLayer); - } - - private QueryLayer RewriteLayer(QueryLayer queryLayer) - { - if (queryLayer != null) - { - queryLayer.Projection = RewriteProjection(queryLayer.Projection, queryLayer.ResourceContext); - } - - return queryLayer; - } - - private IDictionary RewriteProjection(IDictionary projection, - ResourceContext resourceContext) - { - if (projection.IsNullOrEmpty()) - { - return projection; - } - - var newProjection = new Dictionary(); - - foreach ((ResourceFieldAttribute field, QueryLayer layer) in projection) - { - QueryLayer newLayer = RewriteLayer(layer); - newProjection.Add(field, newLayer); - } - - if (!ResourceFactory.HasSingleConstructorWithoutParameters(resourceContext.ResourceType)) - { - return null; - } - - return newProjection; - } - } -} diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 4b98b8e985..6ab75ded5b 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -105,7 +105,7 @@ private static Expression CreateTupleAccessExpressionForConstant(object value, T return Expression.Property(tupleCreateCall, "Item1"); } - internal static bool HasSingleConstructorWithoutParameters(Type type) + private static bool HasSingleConstructorWithoutParameters(Type type) { ConstructorInfo[] constructors = type.GetConstructors().Where(constructor => !constructor.IsStatic).ToArray(); From f23ab45e6f4475505fda222feb21a884a4d2316f Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 14:03:29 +0200 Subject: [PATCH 25/29] cibuild fixes --- src/JsonApiDotNetCore/CollectionConverter.cs | 2 +- src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs | 8 ++++---- .../Internal/QueryableBuilding/SelectClauseBuilder.cs | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/JsonApiDotNetCore/CollectionConverter.cs b/src/JsonApiDotNetCore/CollectionConverter.cs index c6f148a26e..1f403b4ccd 100644 --- a/src/JsonApiDotNetCore/CollectionConverter.cs +++ b/src/JsonApiDotNetCore/CollectionConverter.cs @@ -46,7 +46,7 @@ public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType /// /// Returns a compatible collection type that can be instantiated, for example IList{Article} -> List{Article} or ISet{Article} -> HashSet{Article} /// - public Type ToConcreteCollectionType(Type collectionType) + private Type ToConcreteCollectionType(Type collectionType) { if (collectionType.IsInterface && collectionType.IsGenericType) { diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 9e40eedb99..18c8cecbd7 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -59,11 +59,11 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin return; } - SetupResourceRequest((JsonApiRequest)request, primaryResourceContext, routeValues, options, resourceContextProvider, httpContext.Request); + SetupResourceRequest((JsonApiRequest)request, primaryResourceContext, routeValues, resourceContextProvider, httpContext.Request); httpContext.RegisterJsonApiRequest(); } - else if (IsOperationsRequest(routeValues)) + else if (IsRouteForOperations(routeValues)) { if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerSettings) || !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializerSettings)) @@ -209,7 +209,7 @@ private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSeri } private static void SetupResourceRequest(JsonApiRequest request, ResourceContext primaryResourceContext, RouteValueDictionary routeValues, - IJsonApiOptions options, IResourceContextProvider resourceContextProvider, HttpRequest httpRequest) + IResourceContextProvider resourceContextProvider, HttpRequest httpRequest) { request.IsReadOnly = httpRequest.Method == HttpMethod.Get.Method || httpRequest.Method == HttpMethod.Head.Method; request.Kind = EndpointKind.Primary; @@ -252,7 +252,7 @@ private static bool IsRouteForRelationship(RouteValueDictionary routeValues) return actionName.EndsWith("Relationship", StringComparison.Ordinal); } - private static bool IsOperationsRequest(RouteValueDictionary routeValues) + private static bool IsRouteForOperations(RouteValueDictionary routeValues) { string actionName = (string)routeValues["action"]; return actionName == "PostOperations"; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs index 75798f573a..815c96d256 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs @@ -6,7 +6,6 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries.Expressions; -using JsonApiDotNetCore.Repositories; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; using Microsoft.EntityFrameworkCore; From b277e1f52d2960ed12e2b7492a4b8f8b6557f98d Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 14:38:01 +0200 Subject: [PATCH 26/29] Always set IJsonApiRequest.OperationKind from middleware --- .../Middleware/IJsonApiRequest.cs | 2 +- .../Middleware/JsonApiMiddleware.cs | 27 +++++++++++- .../Middleware/JsonApiRequestTests.cs | 41 ++++++++++--------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs index c7e3e91499..215ecbd2f3 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs @@ -46,7 +46,7 @@ public interface IJsonApiRequest bool IsReadOnly { get; } /// - /// In case of an atomic:operations request, this indicates the kind of operation currently being processed. + /// In case of a non-readonly request, this indicates the kind of write operation currently being processed. /// OperationKind? OperationKind { get; } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 18c8cecbd7..d825f3c74a 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -212,7 +212,6 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext IResourceContextProvider resourceContextProvider, HttpRequest httpRequest) { request.IsReadOnly = httpRequest.Method == HttpMethod.Get.Method || httpRequest.Method == HttpMethod.Head.Method; - request.Kind = EndpointKind.Primary; request.PrimaryResource = primaryResourceContext; request.PrimaryId = GetPrimaryRequestId(routeValues); @@ -222,6 +221,17 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext { request.Kind = IsRouteForRelationship(routeValues) ? EndpointKind.Relationship : EndpointKind.Secondary; + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true + + request.OperationKind = + httpRequest.Method == HttpMethod.Post.Method ? OperationKind.AddToRelationship : + httpRequest.Method == HttpMethod.Patch.Method ? OperationKind.SetRelationship : + httpRequest.Method == HttpMethod.Delete.Method ? OperationKind.RemoveFromRelationship : null; + + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore + RelationshipAttribute requestRelationship = primaryResourceContext.Relationships.SingleOrDefault(relationship => relationship.PublicName == relationshipName); @@ -231,6 +241,21 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext request.SecondaryResource = resourceContextProvider.GetResourceContext(requestRelationship.RightType); } } + else + { + request.Kind = EndpointKind.Primary; + + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true + + request.OperationKind = + httpRequest.Method == HttpMethod.Post.Method ? OperationKind.CreateResource : + httpRequest.Method == HttpMethod.Patch.Method ? OperationKind.UpdateResource : + httpRequest.Method == HttpMethod.Delete.Method ? OperationKind.DeleteResource : null; + + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore + } bool isGetAll = request.PrimaryId == null && request.IsReadOnly; request.IsCollection = isGetAll || request.Relationship is HasManyAttribute; diff --git a/test/UnitTests/Middleware/JsonApiRequestTests.cs b/test/UnitTests/Middleware/JsonApiRequestTests.cs index a7a1d10eeb..b64b4a4638 100644 --- a/test/UnitTests/Middleware/JsonApiRequestTests.cs +++ b/test/UnitTests/Middleware/JsonApiRequestTests.cs @@ -18,27 +18,27 @@ namespace UnitTests.Middleware public sealed class JsonApiRequestTests { [Theory] - [InlineData("HEAD", "/todoItems", true, EndpointKind.Primary, true)] - [InlineData("HEAD", "/todoItems/1", false, EndpointKind.Primary, true)] - [InlineData("HEAD", "/todoItems/1/owner", false, EndpointKind.Secondary, true)] - [InlineData("HEAD", "/todoItems/1/tags", true, EndpointKind.Secondary, true)] - [InlineData("HEAD", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, true)] - [InlineData("HEAD", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, true)] - [InlineData("GET", "/todoItems", true, EndpointKind.Primary, true)] - [InlineData("GET", "/todoItems/1", false, EndpointKind.Primary, true)] - [InlineData("GET", "/todoItems/1/owner", false, EndpointKind.Secondary, true)] - [InlineData("GET", "/todoItems/1/tags", true, EndpointKind.Secondary, true)] - [InlineData("GET", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, true)] - [InlineData("GET", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, true)] - [InlineData("POST", "/todoItems", false, EndpointKind.Primary, false)] - [InlineData("POST", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, false)] - [InlineData("PATCH", "/todoItems/1", false, EndpointKind.Primary, false)] - [InlineData("PATCH", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, false)] - [InlineData("PATCH", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, false)] - [InlineData("DELETE", "/todoItems/1", false, EndpointKind.Primary, false)] - [InlineData("DELETE", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, false)] + [InlineData("HEAD", "/todoItems", true, EndpointKind.Primary, null, true)] + [InlineData("HEAD", "/todoItems/1", false, EndpointKind.Primary, null, true)] + [InlineData("HEAD", "/todoItems/1/owner", false, EndpointKind.Secondary, null, true)] + [InlineData("HEAD", "/todoItems/1/tags", true, EndpointKind.Secondary, null, true)] + [InlineData("HEAD", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, null, true)] + [InlineData("HEAD", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, null, true)] + [InlineData("GET", "/todoItems", true, EndpointKind.Primary, null, true)] + [InlineData("GET", "/todoItems/1", false, EndpointKind.Primary, null, true)] + [InlineData("GET", "/todoItems/1/owner", false, EndpointKind.Secondary, null, true)] + [InlineData("GET", "/todoItems/1/tags", true, EndpointKind.Secondary, null, true)] + [InlineData("GET", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, null, true)] + [InlineData("GET", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, null, true)] + [InlineData("POST", "/todoItems", false, EndpointKind.Primary, OperationKind.CreateResource, false)] + [InlineData("POST", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, OperationKind.AddToRelationship, false)] + [InlineData("PATCH", "/todoItems/1", false, EndpointKind.Primary, OperationKind.UpdateResource, false)] + [InlineData("PATCH", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, OperationKind.SetRelationship, false)] + [InlineData("PATCH", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, OperationKind.SetRelationship, false)] + [InlineData("DELETE", "/todoItems/1", false, EndpointKind.Primary, OperationKind.DeleteResource, false)] + [InlineData("DELETE", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, OperationKind.RemoveFromRelationship, false)] public async Task Sets_request_properties_correctly(string requestMethod, string requestPath, bool expectIsCollection, EndpointKind expectKind, - bool expectIsReadOnly) + OperationKind? expectOperationKind, bool expectIsReadOnly) { // Arrange var options = new JsonApiOptions @@ -69,6 +69,7 @@ public async Task Sets_request_properties_correctly(string requestMethod, string // Assert request.IsCollection.Should().Be(expectIsCollection); request.Kind.Should().Be(expectKind); + request.OperationKind.Should().Be(expectOperationKind); request.IsReadOnly.Should().Be(expectIsReadOnly); request.PrimaryResource.Should().NotBeNull(); request.PrimaryResource.PublicName.Should().Be("todoItems"); From 0b15d4534d74c810c7919ee4d5155a4c618ff0ab Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 15:02:00 +0200 Subject: [PATCH 27/29] Renamed OperationKind to WriteOperationKind, exposed as IJsonApiRequest.WriteOperation --- .../Definitions/TodoItemDefinition.cs | 6 +-- .../AtomicOperations/LocalIdValidator.cs | 4 +- .../OperationProcessorAccessor.cs | 18 +++---- .../AtomicOperations/OperationsProcessor.cs | 2 +- .../Configuration/JsonApiValidationFilter.cs | 2 +- .../BaseJsonApiOperationsController.cs | 4 +- .../Middleware/IJsonApiRequest.cs | 2 +- .../Middleware/JsonApiMiddleware.cs | 16 +++--- .../Middleware/JsonApiRequest.cs | 4 +- ...OperationKind.cs => WriteOperationKind.cs} | 5 +- .../EntityFrameworkCoreRepository.cs | 38 ++++++------- .../Resources/IResourceDefinition.cs | 54 +++++++++---------- .../Resources/IResourceDefinitionAccessor.cs | 10 ++-- .../Resources/JsonApiResourceDefinition.cs | 10 ++-- .../Resources/OperationContainer.cs | 4 +- .../Resources/ResourceDefinitionAccessor.cs | 20 +++---- .../Serialization/RequestDeserializer.cs | 51 +++++++++--------- .../Services/JsonApiResourceService.cs | 11 ++-- .../TelevisionBroadcastDefinition.cs | 16 +++--- .../CreateMusicTrackOperationsController.cs | 2 +- .../RequestBody/WorkflowDefinition.cs | 10 ++-- .../FireForgetGroupDefinition.cs | 8 +-- .../FireForgetUserDefinition.cs | 8 +-- .../Microservices/MessagingGroupDefinition.cs | 16 +++--- .../Microservices/MessagingUserDefinition.cs | 16 +++--- .../OutboxGroupDefinition.cs | 4 +- .../OutboxUserDefinition.cs | 4 +- .../ServiceCollectionExtensionsTests.cs | 26 +++++---- .../Middleware/JsonApiRequestTests.cs | 18 +++---- .../NeverResourceDefinitionAccessor.cs | 10 ++-- 30 files changed, 199 insertions(+), 200 deletions(-) rename src/JsonApiDotNetCore/Middleware/{OperationKind.cs => WriteOperationKind.cs} (82%) diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs index 43b57b98e5..927bed7f57 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemDefinition.cs @@ -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; } diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs index 79ea1bf6ba..6bd8e7a57e 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs @@ -56,7 +56,7 @@ public void Validate(IEnumerable operations) private void ValidateOperation(OperationContainer operation) { - if (operation.Kind == OperationKind.CreateResource) + if (operation.Kind == WriteOperationKind.CreateResource) { DeclareLocalId(operation.Resource); } @@ -70,7 +70,7 @@ private void ValidateOperation(OperationContainer operation) AssertLocalIdIsAssigned(secondaryResource); } - if (operation.Kind == OperationKind.CreateResource) + if (operation.Kind == WriteOperationKind.CreateResource) { AssignLocalId(operation); } diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs index f387316720..b6d7bae21d 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs @@ -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}'."); } } } diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs index 1fb60d985e..ffb2e19efc 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs @@ -118,7 +118,7 @@ protected virtual async Task ProcessOperationAsync(Operation protected void TrackLocalIdsForOperation(OperationContainer operation) { - if (operation.Kind == OperationKind.CreateResource) + if (operation.Kind == WriteOperationKind.CreateResource) { DeclareLocalId(operation.Resource); } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs index 7ccc306716..f4d57f135c 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs @@ -41,7 +41,7 @@ public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEnt var httpContextAccessor = _serviceProvider.GetRequiredService(); - if (httpContextAccessor.HttpContext!.Request.Method == HttpMethods.Patch || request.OperationKind == OperationKind.UpdateResource) + if (httpContextAccessor.HttpContext!.Request.Method == HttpMethods.Patch || request.WriteOperation == WriteOperationKind.UpdateResource) { var targetedFields = _serviceProvider.GetRequiredService(); return IsFieldTargeted(entry, targetedFields); diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs index 3c648dd7b9..64feb432ee 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs @@ -132,7 +132,7 @@ protected virtual void ValidateClientGeneratedIds(IEnumerable operat foreach (OperationContainer operation in operations) { - if (operation.Kind == OperationKind.CreateResource || operation.Kind == OperationKind.UpdateResource) + if (operation.Kind == WriteOperationKind.CreateResource || operation.Kind == WriteOperationKind.UpdateResource) { _targetedFields.Attributes = operation.TargetedFields.Attributes; _targetedFields.Relationships = operation.TargetedFields.Relationships; diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs index 215ecbd2f3..888c01544a 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs @@ -48,7 +48,7 @@ public interface IJsonApiRequest /// /// In case of a non-readonly request, this indicates the kind of write operation currently being processed. /// - OperationKind? OperationKind { get; } + WriteOperationKind? WriteOperation { get; } /// /// In case of an atomic:operations request, identifies the overarching transaction. diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index d825f3c74a..3ad6355d2e 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -224,10 +224,10 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - request.OperationKind = - httpRequest.Method == HttpMethod.Post.Method ? OperationKind.AddToRelationship : - httpRequest.Method == HttpMethod.Patch.Method ? OperationKind.SetRelationship : - httpRequest.Method == HttpMethod.Delete.Method ? OperationKind.RemoveFromRelationship : null; + request.WriteOperation = + httpRequest.Method == HttpMethod.Post.Method ? WriteOperationKind.AddToRelationship : + httpRequest.Method == HttpMethod.Patch.Method ? WriteOperationKind.SetRelationship : + httpRequest.Method == HttpMethod.Delete.Method ? WriteOperationKind.RemoveFromRelationship : null; // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore @@ -248,10 +248,10 @@ private static void SetupResourceRequest(JsonApiRequest request, ResourceContext // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true - request.OperationKind = - httpRequest.Method == HttpMethod.Post.Method ? OperationKind.CreateResource : - httpRequest.Method == HttpMethod.Patch.Method ? OperationKind.UpdateResource : - httpRequest.Method == HttpMethod.Delete.Method ? OperationKind.DeleteResource : null; + request.WriteOperation = + httpRequest.Method == HttpMethod.Post.Method ? WriteOperationKind.CreateResource : + httpRequest.Method == HttpMethod.Patch.Method ? WriteOperationKind.UpdateResource : + httpRequest.Method == HttpMethod.Delete.Method ? WriteOperationKind.DeleteResource : null; // @formatter:keep_existing_linebreaks restore // @formatter:wrap_chained_method_calls restore diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs index 3956393814..89bd6fa722 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs @@ -30,7 +30,7 @@ public sealed class JsonApiRequest : IJsonApiRequest public bool IsReadOnly { get; set; } /// - public OperationKind? OperationKind { get; set; } + public WriteOperationKind? WriteOperation { get; set; } /// public string TransactionId { get; set; } @@ -47,7 +47,7 @@ public void CopyFrom(IJsonApiRequest other) Relationship = other.Relationship; IsCollection = other.IsCollection; IsReadOnly = other.IsReadOnly; - OperationKind = other.OperationKind; + WriteOperation = other.WriteOperation; TransactionId = other.TransactionId; } } diff --git a/src/JsonApiDotNetCore/Middleware/OperationKind.cs b/src/JsonApiDotNetCore/Middleware/WriteOperationKind.cs similarity index 82% rename from src/JsonApiDotNetCore/Middleware/OperationKind.cs rename to src/JsonApiDotNetCore/Middleware/WriteOperationKind.cs index e3a528f2b0..aeec6c1cab 100644 --- a/src/JsonApiDotNetCore/Middleware/OperationKind.cs +++ b/src/JsonApiDotNetCore/Middleware/WriteOperationKind.cs @@ -1,9 +1,10 @@ namespace JsonApiDotNetCore.Middleware { /// - /// Lists the functional operation kinds of a resource request or an atomic:operations request. + /// Lists the functional write operations, originating from a POST/PATCH/DELETE request against a single resource/relationship or a POST request against + /// a list of operations. /// - public enum OperationKind + public enum WriteOperationKind { /// /// Create a new resource with attributes, relationships or both. diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index c1261c899a..2daa011d82 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -164,7 +164,7 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r { object rightValue = relationship.GetValue(resourceFromRequest); - object rightValueEvaluated = await VisitSetRelationshipAsync(resourceForDatabase, relationship, rightValue, OperationKind.CreateResource, + object rightValueEvaluated = await VisitSetRelationshipAsync(resourceForDatabase, relationship, rightValue, WriteOperationKind.CreateResource, cancellationToken); await UpdateRelationshipAsync(relationship, resourceForDatabase, rightValueEvaluated, collector, cancellationToken); @@ -175,30 +175,30 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest)); } - await _resourceDefinitionAccessor.OnWritingAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken); DbSet dbSet = _dbContext.Set(); await dbSet.AddAsync(resourceForDatabase, cancellationToken); await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken); } private async Task VisitSetRelationshipAsync(TResource leftResource, RelationshipAttribute relationship, object rightValue, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { if (relationship is HasOneAttribute hasOneRelationship) { - return await _resourceDefinitionAccessor.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, (IIdentifiable)rightValue, operationKind, - cancellationToken); + return await _resourceDefinitionAccessor.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, (IIdentifiable)rightValue, + writeOperation, cancellationToken); } if (relationship is HasManyAttribute hasManyRelationship) { HashSet rightResourceIdSet = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); - await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIdSet, operationKind, + await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIdSet, writeOperation, cancellationToken); return rightResourceIdSet; @@ -232,7 +232,7 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r { object rightValue = relationship.GetValue(resourceFromRequest); - object rightValueEvaluated = await VisitSetRelationshipAsync(resourceFromDatabase, relationship, rightValue, OperationKind.UpdateResource, + object rightValueEvaluated = await VisitSetRelationshipAsync(resourceFromDatabase, relationship, rightValue, WriteOperationKind.UpdateResource, cancellationToken); AssertIsNotClearingRequiredRelationship(relationship, resourceFromDatabase, rightValueEvaluated); @@ -245,11 +245,11 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r attribute.SetValue(resourceFromDatabase, attribute.GetValue(resourceFromRequest)); } - await _resourceDefinitionAccessor.OnWritingAsync(resourceFromDatabase, OperationKind.UpdateResource, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(resourceFromDatabase, WriteOperationKind.UpdateResource, cancellationToken); await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceFromDatabase, OperationKind.UpdateResource, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceFromDatabase, WriteOperationKind.UpdateResource, cancellationToken); } protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute relationship, TResource leftResource, object rightValue) @@ -300,7 +300,7 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke var emptyResource = _resourceFactory.CreateInstance(); emptyResource.Id = id; - await _resourceDefinitionAccessor.OnWritingAsync(emptyResource, OperationKind.DeleteResource, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(emptyResource, WriteOperationKind.DeleteResource, cancellationToken); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); TResource resource = collector.CreateForId(id); @@ -320,7 +320,7 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(resource, OperationKind.DeleteResource, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(resource, WriteOperationKind.DeleteResource, cancellationToken); } private NavigationEntry GetNavigationEntry(TResource resource, RelationshipAttribute relationship) @@ -377,18 +377,18 @@ public virtual async Task SetRelationshipAsync(TResource leftResource, object ri RelationshipAttribute relationship = _targetedFields.Relationships.Single(); object rightValueEvaluated = - await VisitSetRelationshipAsync(leftResource, relationship, rightValue, OperationKind.SetRelationship, cancellationToken); + await VisitSetRelationshipAsync(leftResource, relationship, rightValue, WriteOperationKind.SetRelationship, cancellationToken); AssertIsNotClearingRequiredRelationship(relationship, leftResource, rightValueEvaluated); using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext); await UpdateRelationshipAsync(relationship, leftResource, rightValueEvaluated, collector, cancellationToken); - await _resourceDefinitionAccessor.OnWritingAsync(leftResource, OperationKind.SetRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(leftResource, WriteOperationKind.SetRelationship, cancellationToken); await SaveChangesAsync(cancellationToken); - await _resourceDefinitionAccessor.OnWriteSucceededAsync(leftResource, OperationKind.SetRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnWriteSucceededAsync(leftResource, WriteOperationKind.SetRelationship, cancellationToken); } /// @@ -413,11 +413,11 @@ public virtual async Task AddToToManyRelationshipAsync(TId leftId, ISet /// /// The original resource retrieved from the underlying data store, or a freshly instantiated resource in case of a POST resource request. /// - /// - /// Identifies from which endpoint this method was called. Possible values: , - /// , and . - /// Note this intentionally excludes and , because for those - /// endpoints no resource is retrieved upfront. + /// + /// Identifies the logical write operation for which this method was called. Possible values: , + /// , and + /// . Note this intentionally excludes and + /// , because for those endpoints no resource is retrieved upfront. /// /// /// Propagates notification that request handling should be canceled. /// - Task OnPrepareWriteAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken); + Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken); /// /// Executes before setting (or clearing) the resource at the right side of a to-one relationship. @@ -174,9 +174,9 @@ public interface IResourceDefinition /// /// The new resource identifier (or null to clear the relationship), coming from the request. /// - /// - /// Identifies from which endpoint this method was called. Possible values: , - /// and . + /// + /// Identifies the logical write operation for which this method was called. Possible values: , + /// and . /// /// /// Propagates notification that request handling should be canceled. @@ -185,7 +185,7 @@ public interface IResourceDefinition /// The replacement resource identifier, or null to clear the relationship. Returns by default. /// Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, IIdentifiable rightResourceId, - OperationKind operationKind, CancellationToken cancellationToken); + WriteOperationKind writeOperation, CancellationToken cancellationToken); /// /// Executes before setting the resources at the right side of a to-many relationship. This replaces on existing set. @@ -203,15 +203,15 @@ Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAt /// /// The set of resource identifiers to replace any existing set with, coming from the request. /// - /// - /// Identifies from which endpoint this method was called. Possible values: , - /// and . + /// + /// Identifies the logical write operation for which this method was called. Possible values: , + /// and . /// /// /// Propagates notification that request handling should be canceled. /// Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken); + WriteOperationKind writeOperation, CancellationToken cancellationToken); /// /// Executes before adding resources to the right side of a to-many relationship, as part of a POST relationship request. @@ -273,19 +273,19 @@ Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasM /// /// /// The original resource retrieved from the underlying data store (or a freshly instantiated resource in case of a POST resource request), updated with - /// the changes from the incoming request. Exception: In case is or - /// , this is an empty object with only the property set, because for - /// those endpoints no resource is retrieved upfront. + /// the changes from the incoming request. Exception: In case is or + /// , this is an empty object with only the property set, because + /// for those endpoints no resource is retrieved upfront. /// - /// - /// Identifies from which endpoint this method was called. Possible values: , - /// , , , - /// and . + /// + /// Identifies the logical write operation for which this method was called. Possible values: , + /// , , + /// , and . /// /// /// Propagates notification that request handling should be canceled. /// - Task OnWritingAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken); + Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken); /// /// Executes after successfully writing the changed resource to the underlying data store, as part of a write request. @@ -296,15 +296,15 @@ Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasM /// /// The resource as written to the underlying data store. /// - /// - /// Identifies from which endpoint this method was called. Possible values: , - /// , , , - /// and . + /// + /// Identifies the logical write operation for which this method was called. Possible values: , + /// , , + /// , and . /// /// /// Propagates notification that request handling should be canceled. /// - Task OnWriteSucceededAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken); + Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken); /// /// Executes after a resource has been deserialized from an incoming request body. diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs index f19de8543e..8c804d3602 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinitionAccessor.cs @@ -54,21 +54,21 @@ public interface IResourceDefinitionAccessor /// /// Invokes for the specified resource. /// - Task OnPrepareWriteAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource. /// public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, - IIdentifiable rightResourceId, OperationKind operationKind, CancellationToken cancellationToken) + IIdentifiable rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource. /// public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// @@ -88,13 +88,13 @@ public Task OnRemoveFromRelationshipAsync(TResource leftResource, Has /// /// Invokes for the specified resource. /// - Task OnWritingAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// /// Invokes for the specified resource. /// - Task OnWriteSucceededAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable; /// diff --git a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs index 722315bb5e..755ce781cb 100644 --- a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs @@ -124,21 +124,21 @@ public virtual IDictionary GetMeta(TResource resource) } /// - public virtual Task OnPrepareWriteAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public virtual Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { return Task.CompletedTask; } /// public virtual Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, - IIdentifiable rightResourceId, OperationKind operationKind, CancellationToken cancellationToken) + IIdentifiable rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) { return Task.FromResult(rightResourceId); } /// public virtual Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { return Task.CompletedTask; } @@ -158,13 +158,13 @@ public virtual Task OnRemoveFromRelationshipAsync(TResource leftResource, HasMan } /// - public virtual Task OnWritingAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public virtual Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { return Task.CompletedTask; } /// - public virtual Task OnWriteSucceededAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public virtual Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { return Task.CompletedTask; } diff --git a/src/JsonApiDotNetCore/Resources/OperationContainer.cs b/src/JsonApiDotNetCore/Resources/OperationContainer.cs index 122556d816..11e6636ccf 100644 --- a/src/JsonApiDotNetCore/Resources/OperationContainer.cs +++ b/src/JsonApiDotNetCore/Resources/OperationContainer.cs @@ -13,12 +13,12 @@ public sealed class OperationContainer { private static readonly CollectionConverter CollectionConverter = new(); - public OperationKind Kind { get; } + public WriteOperationKind Kind { get; } public IIdentifiable Resource { get; } public ITargetedFields TargetedFields { get; } public IJsonApiRequest Request { get; } - public OperationContainer(OperationKind kind, IIdentifiable resource, ITargetedFields targetedFields, IJsonApiRequest request) + public OperationContainer(WriteOperationKind kind, IIdentifiable resource, ITargetedFields targetedFields, IJsonApiRequest request) { ArgumentGuard.NotNull(resource, nameof(resource)); ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); diff --git a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs index 695c0b40e5..271f14ad5b 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs @@ -95,30 +95,30 @@ public IDictionary GetMeta(Type resourceType, IIdentifiable reso } /// - public async Task OnPrepareWriteAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public async Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { ArgumentGuard.NotNull(resource, nameof(resource)); dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource)); - await resourceDefinition.OnPrepareWriteAsync(resource, operationKind, cancellationToken); + await resourceDefinition.OnPrepareWriteAsync(resource, writeOperation, cancellationToken); } /// public async Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, - IIdentifiable rightResourceId, OperationKind operationKind, CancellationToken cancellationToken) + IIdentifiable rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { ArgumentGuard.NotNull(leftResource, nameof(leftResource)); ArgumentGuard.NotNull(hasOneRelationship, nameof(hasOneRelationship)); dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource)); - return await resourceDefinition.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, rightResourceId, operationKind, cancellationToken); + return await resourceDefinition.OnSetToOneRelationshipAsync(leftResource, hasOneRelationship, rightResourceId, writeOperation, cancellationToken); } /// public async Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, - ISet rightResourceIds, OperationKind operationKind, CancellationToken cancellationToken) + ISet rightResourceIds, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { ArgumentGuard.NotNull(leftResource, nameof(leftResource)); @@ -126,7 +126,7 @@ public async Task OnSetToManyRelationshipAsync(TResource leftResource ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource)); - await resourceDefinition.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIds, operationKind, cancellationToken); + await resourceDefinition.OnSetToManyRelationshipAsync(leftResource, hasManyRelationship, rightResourceIds, writeOperation, cancellationToken); } /// @@ -155,23 +155,23 @@ public async Task OnRemoveFromRelationshipAsync(TResource leftResourc } /// - public async Task OnWritingAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public async Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { ArgumentGuard.NotNull(resource, nameof(resource)); dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource)); - await resourceDefinition.OnWritingAsync(resource, operationKind, cancellationToken); + await resourceDefinition.OnWritingAsync(resource, writeOperation, cancellationToken); } /// - public async Task OnWriteSucceededAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public async Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { ArgumentGuard.NotNull(resource, nameof(resource)); dynamic resourceDefinition = ResolveResourceDefinition(typeof(TResource)); - await resourceDefinition.OnWriteSucceededAsync(resource, operationKind, cancellationToken); + await resourceDefinition.OnWriteSucceededAsync(resource, writeOperation, cancellationToken); } /// diff --git a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs index 2ba04f728a..ab372fea3f 100644 --- a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs @@ -109,24 +109,25 @@ private OperationContainer DeserializeOperation(AtomicOperationObject operation) AssertHasNoHref(operation); - OperationKind kind = GetOperationKind(operation); + WriteOperationKind writeOperation = GetWriteOperationKind(operation); - switch (kind) + switch (writeOperation) { - case OperationKind.CreateResource: - case OperationKind.UpdateResource: + case WriteOperationKind.CreateResource: + case WriteOperationKind.UpdateResource: { - return ParseForCreateOrUpdateResourceOperation(operation, kind); + return ParseForCreateOrUpdateResourceOperation(operation, writeOperation); } - case OperationKind.DeleteResource: + case WriteOperationKind.DeleteResource: { - return ParseForDeleteResourceOperation(operation, kind); + return ParseForDeleteResourceOperation(operation, writeOperation); } } - bool requireToManyRelationship = kind == OperationKind.AddToRelationship || kind == OperationKind.RemoveFromRelationship; + bool requireToManyRelationship = + writeOperation == WriteOperationKind.AddToRelationship || writeOperation == WriteOperationKind.RemoveFromRelationship; - return ParseForRelationshipOperation(operation, kind, requireToManyRelationship); + return ParseForRelationshipOperation(operation, writeOperation, requireToManyRelationship); } [AssertionMethod] @@ -138,7 +139,7 @@ private void AssertHasNoHref(AtomicOperationObject operation) } } - private OperationKind GetOperationKind(AtomicOperationObject operation) + private WriteOperationKind GetWriteOperationKind(AtomicOperationObject operation) { switch (operation.Code) { @@ -150,11 +151,11 @@ private OperationKind GetOperationKind(AtomicOperationObject operation) atomicOperationIndex: AtomicOperationIndex); } - return operation.Ref == null ? OperationKind.CreateResource : OperationKind.AddToRelationship; + return operation.Ref == null ? WriteOperationKind.CreateResource : WriteOperationKind.AddToRelationship; } case AtomicOperationCode.Update: { - return operation.Ref?.Relationship != null ? OperationKind.SetRelationship : OperationKind.UpdateResource; + return operation.Ref?.Relationship != null ? WriteOperationKind.SetRelationship : WriteOperationKind.UpdateResource; } case AtomicOperationCode.Remove: { @@ -163,19 +164,19 @@ private OperationKind GetOperationKind(AtomicOperationObject operation) throw new JsonApiSerializationException("The 'ref' element is required.", null, atomicOperationIndex: AtomicOperationIndex); } - return operation.Ref.Relationship != null ? OperationKind.RemoveFromRelationship : OperationKind.DeleteResource; + return operation.Ref.Relationship != null ? WriteOperationKind.RemoveFromRelationship : WriteOperationKind.DeleteResource; } } throw new NotSupportedException($"Unknown operation code '{operation.Code}'."); } - private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperationObject operation, OperationKind kind) + private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperationObject operation, WriteOperationKind writeOperation) { ResourceObject resourceObject = GetRequiredSingleDataForResourceOperation(operation); AssertElementHasType(resourceObject, "data"); - AssertElementHasIdOrLid(resourceObject, "data", kind != OperationKind.CreateResource); + AssertElementHasIdOrLid(resourceObject, "data", writeOperation != WriteOperationKind.CreateResource); ResourceContext primaryResourceContext = GetExistingResourceContext(resourceObject.Type); @@ -204,7 +205,7 @@ private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperati { Kind = EndpointKind.AtomicOperations, PrimaryResource = primaryResourceContext, - OperationKind = kind + WriteOperation = writeOperation }; _request.CopyFrom(request); @@ -224,7 +225,7 @@ private OperationContainer ParseForCreateOrUpdateResourceOperation(AtomicOperati AssertResourceIdIsNotTargeted(targetedFields); - return new OperationContainer(kind, primaryResource, targetedFields, request); + return new OperationContainer(writeOperation, primaryResource, targetedFields, request); } private ResourceObject GetRequiredSingleDataForResourceOperation(AtomicOperationObject operation) @@ -310,7 +311,7 @@ private void AssertSameIdentityInRefData(AtomicOperationObject operation, Resour } } - private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject operation, OperationKind kind) + private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject operation, WriteOperationKind writeOperation) { AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); @@ -328,13 +329,13 @@ private OperationContainer ParseForDeleteResourceOperation(AtomicOperationObject Kind = EndpointKind.AtomicOperations, PrimaryId = primaryResource.StringId, PrimaryResource = primaryResourceContext, - OperationKind = kind + WriteOperation = writeOperation }; - return new OperationContainer(kind, primaryResource, new TargetedFields(), request); + return new OperationContainer(writeOperation, primaryResource, new TargetedFields(), request); } - private OperationContainer ParseForRelationshipOperation(AtomicOperationObject operation, OperationKind kind, bool requireToMany) + private OperationContainer ParseForRelationshipOperation(AtomicOperationObject operation, WriteOperationKind writeOperation, bool requireToMany) { AssertElementHasType(operation.Ref, "ref"); AssertElementHasIdOrLid(operation.Ref, "ref", true); @@ -365,7 +366,7 @@ private OperationContainer ParseForRelationshipOperation(AtomicOperationObject o SecondaryResource = secondaryResourceContext, Relationship = relationship, IsCollection = relationship is HasManyAttribute, - OperationKind = kind + WriteOperation = writeOperation }; _request.CopyFrom(request); @@ -380,7 +381,7 @@ private OperationContainer ParseForRelationshipOperation(AtomicOperationObject o Relationships = _targetedFields.Relationships.ToHashSet() }; - return new OperationContainer(kind, primaryResource, targetedFields, request); + return new OperationContainer(writeOperation, primaryResource, targetedFields, request); } private RelationshipAttribute GetExistingRelationship(AtomicReference reference, ResourceContext resourceContext) @@ -511,14 +512,14 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA private bool IsCreatingResource() { return _request.Kind == EndpointKind.AtomicOperations - ? _request.OperationKind == OperationKind.CreateResource + ? _request.WriteOperation == WriteOperationKind.CreateResource : _request.Kind == EndpointKind.Primary && _httpContextAccessor.HttpContext!.Request.Method == HttpMethod.Post.Method; } private bool IsUpdatingResource() { return _request.Kind == EndpointKind.AtomicOperations - ? _request.OperationKind == OperationKind.UpdateResource + ? _request.WriteOperation == WriteOperationKind.UpdateResource : _request.Kind == EndpointKind.Primary && _httpContextAccessor.HttpContext!.Request.Method == HttpMethod.Patch.Method; } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 678df35b88..8ae06cd907 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -197,7 +197,7 @@ public virtual async Task CreateAsync(TResource resource, Cancellatio protected virtual async Task InitializeResourceAsync(TResource resourceForDatabase, CancellationToken cancellationToken) { - await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceForDatabase, OperationKind.CreateResource, cancellationToken); + await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken); } protected async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource primaryResource, CancellationToken cancellationToken) @@ -298,8 +298,7 @@ protected async Task AssertRightResourcesExistAsync(object rightValue, Cancellat QueryLayer queryLayer = _queryLayerComposer.ComposeForGetRelationshipRightIds(_request.Relationship, rightResourceIds); List missingResources = - await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, rightResourceIds, cancellationToken) - .ToListAsync(cancellationToken); + await GetMissingRightResourcesAsync(queryLayer, _request.Relationship, rightResourceIds, cancellationToken).ToListAsync(cancellationToken); if (missingResources.Any()) { @@ -326,7 +325,7 @@ public virtual async Task UpdateAsync(TId id, TResource resource, Can _resourceChangeTracker.SetInitiallyStoredAttributeValues(resourceFromDatabase); - await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, OperationKind.UpdateResource, cancellationToken); + await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, WriteOperationKind.UpdateResource, cancellationToken); try { @@ -362,7 +361,7 @@ public virtual async Task SetRelationshipAsync(TId leftId, string relationshipNa TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(leftId, cancellationToken); - await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, OperationKind.SetRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, WriteOperationKind.SetRelationship, cancellationToken); try { @@ -412,7 +411,7 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TId leftId, string r TResource resourceFromDatabase = await GetPrimaryResourceForUpdateAsync(leftId, cancellationToken); - await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, OperationKind.RemoveFromRelationship, cancellationToken); + await _resourceDefinitionAccessor.OnPrepareWriteAsync(resourceFromDatabase, WriteOperationKind.RemoveFromRelationship, cancellationToken); await AssertRightResourcesExistAsync(rightResourceIds, cancellationToken); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs index 10e2e0e6ff..4e1b02cb3f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Archiving/TelevisionBroadcastDefinition.cs @@ -112,27 +112,27 @@ private bool HasFilterOnArchivedAt(FilterExpression existingFilter) return walker.HasFilterOnArchivedAt; } - public override Task OnPrepareWriteAsync(TelevisionBroadcast broadcast, OperationKind operationKind, CancellationToken cancellationToken) + public override Task OnPrepareWriteAsync(TelevisionBroadcast broadcast, WriteOperationKind writeOperation, CancellationToken cancellationToken) { - if (operationKind == OperationKind.UpdateResource) + if (writeOperation == WriteOperationKind.UpdateResource) { _storedArchivedAt = broadcast.ArchivedAt; } - return base.OnPrepareWriteAsync(broadcast, operationKind, cancellationToken); + return base.OnPrepareWriteAsync(broadcast, writeOperation, cancellationToken); } - public override async Task OnWritingAsync(TelevisionBroadcast broadcast, OperationKind operationKind, CancellationToken cancellationToken) + public override async Task OnWritingAsync(TelevisionBroadcast broadcast, WriteOperationKind writeOperation, CancellationToken cancellationToken) { - if (operationKind == OperationKind.CreateResource) + if (writeOperation == WriteOperationKind.CreateResource) { AssertIsNotArchived(broadcast); } - else if (operationKind == OperationKind.UpdateResource) + else if (writeOperation == WriteOperationKind.UpdateResource) { AssertIsNotShiftingArchiveDate(broadcast); } - else if (operationKind == OperationKind.DeleteResource) + else if (writeOperation == WriteOperationKind.DeleteResource) { TelevisionBroadcast broadcastToDelete = await _dbContext.Broadcasts.FirstOrDefaultAsync(resource => resource.Id == broadcast.Id, cancellationToken); @@ -143,7 +143,7 @@ public override async Task OnWritingAsync(TelevisionBroadcast broadcast, Operati } } - await base.OnWritingAsync(broadcast, operationKind, cancellationToken); + await base.OnWritingAsync(broadcast, writeOperation, cancellationToken); } [AssertionMethod] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs index 21b4fd4ac5..55268c88c5 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/CreateMusicTrackOperationsController.cs @@ -38,7 +38,7 @@ private static void AssertOnlyCreatingMusicTracks(IEnumerable(ResourceDefinitionHitCounter.ExtensibilityPoint.OnWritingAsync); - if (operationKind == OperationKind.DeleteResource) + if (writeOperation == WriteOperationKind.DeleteResource) { _groupToDelete = await base.GetGroupToDeleteAsync(group.Id, cancellationToken); } } - public override Task OnWriteSucceededAsync(DomainGroup group, OperationKind operationKind, CancellationToken cancellationToken) + public override Task OnWriteSucceededAsync(DomainGroup group, WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnWriteSucceededAsync); - return FinishWriteAsync(group, operationKind, cancellationToken); + return FinishWriteAsync(group, writeOperation, cancellationToken); } protected override Task FlushMessageAsync(OutgoingMessage message, CancellationToken cancellationToken) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs index 72df77a2e2..34fd1504b5 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetUserDefinition.cs @@ -23,21 +23,21 @@ public FireForgetUserDefinition(IResourceGraph resourceGraph, FireForgetDbContex _hitCounter = hitCounter; } - public override async Task OnWritingAsync(DomainUser user, OperationKind operationKind, CancellationToken cancellationToken) + public override async Task OnWritingAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnWritingAsync); - if (operationKind == OperationKind.DeleteResource) + if (writeOperation == WriteOperationKind.DeleteResource) { _userToDelete = await base.GetUserToDeleteAsync(user.Id, cancellationToken); } } - public override Task OnWriteSucceededAsync(DomainUser user, OperationKind operationKind, CancellationToken cancellationToken) + public override Task OnWriteSucceededAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnWriteSucceededAsync); - return FinishWriteAsync(user, operationKind, cancellationToken); + return FinishWriteAsync(user, writeOperation, cancellationToken); } protected override Task FlushMessageAsync(OutgoingMessage message, CancellationToken cancellationToken) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs index bac42f8d6f..f08d66ae5f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingGroupDefinition.cs @@ -30,15 +30,15 @@ protected MessagingGroupDefinition(IResourceGraph resourceGraph, DbSet(ResourceDefinitionHitCounter.ExtensibilityPoint.OnPrepareWriteAsync); - if (operationKind == OperationKind.CreateResource) + if (writeOperation == WriteOperationKind.CreateResource) { group.Id = Guid.NewGuid(); } - else if (operationKind == OperationKind.UpdateResource) + else if (writeOperation == WriteOperationKind.UpdateResource) { _beforeGroupName = group.Name; } @@ -47,7 +47,7 @@ public override Task OnPrepareWriteAsync(DomainGroup group, OperationKind operat } public override async Task OnSetToManyRelationshipAsync(DomainGroup group, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnSetToManyRelationshipAsync); @@ -168,9 +168,9 @@ public override Task OnRemoveFromRelationshipAsync(DomainGroup group, HasManyAtt return Task.CompletedTask; } - protected async Task FinishWriteAsync(DomainGroup group, OperationKind operationKind, CancellationToken cancellationToken) + protected async Task FinishWriteAsync(DomainGroup group, WriteOperationKind writeOperation, CancellationToken cancellationToken) { - if (operationKind == OperationKind.CreateResource) + if (writeOperation == WriteOperationKind.CreateResource) { var message = OutgoingMessage.CreateFromContent(new GroupCreatedContent { @@ -180,7 +180,7 @@ protected async Task FinishWriteAsync(DomainGroup group, OperationKind operation await FlushMessageAsync(message, cancellationToken); } - else if (operationKind == OperationKind.UpdateResource) + else if (writeOperation == WriteOperationKind.UpdateResource) { if (_beforeGroupName != group.Name) { @@ -194,7 +194,7 @@ protected async Task FinishWriteAsync(DomainGroup group, OperationKind operation await FlushMessageAsync(message, cancellationToken); } } - else if (operationKind == OperationKind.DeleteResource) + else if (writeOperation == WriteOperationKind.DeleteResource) { DomainGroup groupToDelete = await GetGroupToDeleteAsync(group.Id, cancellationToken); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingUserDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingUserDefinition.cs index d7294f3884..f7f9ca81cf 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingUserDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/MessagingUserDefinition.cs @@ -27,15 +27,15 @@ protected MessagingUserDefinition(IResourceGraph resourceGraph, DbSet(ResourceDefinitionHitCounter.ExtensibilityPoint.OnPrepareWriteAsync); - if (operationKind == OperationKind.CreateResource) + if (writeOperation == WriteOperationKind.CreateResource) { user.Id = Guid.NewGuid(); } - else if (operationKind == OperationKind.UpdateResource) + else if (writeOperation == WriteOperationKind.UpdateResource) { _beforeLoginName = user.LoginName; _beforeDisplayName = user.DisplayName; @@ -45,7 +45,7 @@ public override Task OnPrepareWriteAsync(DomainUser user, OperationKind operatio } public override Task OnSetToOneRelationshipAsync(DomainUser user, HasOneAttribute hasOneRelationship, IIdentifiable rightResourceId, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnSetToOneRelationshipAsync); @@ -90,9 +90,9 @@ public override Task OnSetToOneRelationshipAsync(DomainUser user, return Task.FromResult(rightResourceId); } - protected async Task FinishWriteAsync(DomainUser user, OperationKind operationKind, CancellationToken cancellationToken) + protected async Task FinishWriteAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { - if (operationKind == OperationKind.CreateResource) + if (writeOperation == WriteOperationKind.CreateResource) { var message = OutgoingMessage.CreateFromContent(new UserCreatedContent { @@ -103,7 +103,7 @@ protected async Task FinishWriteAsync(DomainUser user, OperationKind operationKi await FlushMessageAsync(message, cancellationToken); } - else if (operationKind == OperationKind.UpdateResource) + else if (writeOperation == WriteOperationKind.UpdateResource) { if (_beforeLoginName != user.LoginName) { @@ -129,7 +129,7 @@ protected async Task FinishWriteAsync(DomainUser user, OperationKind operationKi await FlushMessageAsync(message, cancellationToken); } } - else if (operationKind == OperationKind.DeleteResource) + else if (writeOperation == WriteOperationKind.DeleteResource) { DomainUser userToDelete = await GetUserToDeleteAsync(user.Id, cancellationToken); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs index 61e685859b..480585b63e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxGroupDefinition.cs @@ -21,11 +21,11 @@ public OutboxGroupDefinition(IResourceGraph resourceGraph, OutboxDbContext dbCon _outboxMessageSet = dbContext.OutboxMessages; } - public override Task OnWritingAsync(DomainGroup group, OperationKind operationKind, CancellationToken cancellationToken) + public override Task OnWritingAsync(DomainGroup group, WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnWritingAsync); - return FinishWriteAsync(group, operationKind, cancellationToken); + return FinishWriteAsync(group, writeOperation, cancellationToken); } protected override async Task FlushMessageAsync(OutgoingMessage message, CancellationToken cancellationToken) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs index 88b8a2e865..a4b4731a22 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxUserDefinition.cs @@ -21,11 +21,11 @@ public OutboxUserDefinition(IResourceGraph resourceGraph, OutboxDbContext dbCont _outboxMessageSet = dbContext.OutboxMessages; } - public override Task OnWritingAsync(DomainUser user, OperationKind operationKind, CancellationToken cancellationToken) + public override Task OnWritingAsync(DomainUser user, WriteOperationKind writeOperation, CancellationToken cancellationToken) { _hitCounter.TrackInvocation(ResourceDefinitionHitCounter.ExtensibilityPoint.OnWritingAsync); - return FinishWriteAsync(user, operationKind, cancellationToken); + return FinishWriteAsync(user, writeOperation, cancellationToken); } protected override async Task FlushMessageAsync(OutgoingMessage message, CancellationToken cancellationToken) diff --git a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs index ad08747e3e..5cb26ff134 100644 --- a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -352,8 +352,7 @@ public Task AddToToManyRelationshipAsync(int leftId, ISet rightRe throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(IntResource leftResource, ISet rightResourceIds, - CancellationToken cancellationToken) + public Task RemoveFromToManyRelationshipAsync(IntResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -407,8 +406,7 @@ public Task AddToToManyRelationshipAsync(Guid leftId, ISet rightR throw new NotImplementedException(); } - public Task RemoveFromToManyRelationshipAsync(GuidResource leftResource, ISet rightResourceIds, - CancellationToken cancellationToken) + public Task RemoveFromToManyRelationshipAsync(GuidResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -452,19 +450,19 @@ public IDictionary GetMeta(IntResource resource) throw new NotImplementedException(); } - public Task OnPrepareWriteAsync(IntResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnPrepareWriteAsync(IntResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task OnSetToOneRelationshipAsync(IntResource leftResource, HasOneAttribute hasOneRelationship, IIdentifiable rightResourceId, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task OnSetToManyRelationshipAsync(IntResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -481,12 +479,12 @@ public Task OnRemoveFromRelationshipAsync(IntResource leftResource, HasManyAttri throw new NotImplementedException(); } - public Task OnWritingAsync(IntResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnWritingAsync(IntResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task OnWriteSucceededAsync(IntResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnWriteSucceededAsync(IntResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -540,19 +538,19 @@ public IDictionary GetMeta(GuidResource resource) throw new NotImplementedException(); } - public Task OnPrepareWriteAsync(GuidResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnPrepareWriteAsync(GuidResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task OnSetToOneRelationshipAsync(GuidResource leftResource, HasOneAttribute hasOneRelationship, IIdentifiable rightResourceId, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task OnSetToManyRelationshipAsync(GuidResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -569,12 +567,12 @@ public Task OnRemoveFromRelationshipAsync(GuidResource leftResource, HasManyAttr throw new NotImplementedException(); } - public Task OnWritingAsync(GuidResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnWritingAsync(GuidResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task OnWriteSucceededAsync(GuidResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnWriteSucceededAsync(GuidResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/test/UnitTests/Middleware/JsonApiRequestTests.cs b/test/UnitTests/Middleware/JsonApiRequestTests.cs index b64b4a4638..7950291a9d 100644 --- a/test/UnitTests/Middleware/JsonApiRequestTests.cs +++ b/test/UnitTests/Middleware/JsonApiRequestTests.cs @@ -30,15 +30,15 @@ public sealed class JsonApiRequestTests [InlineData("GET", "/todoItems/1/tags", true, EndpointKind.Secondary, null, true)] [InlineData("GET", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, null, true)] [InlineData("GET", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, null, true)] - [InlineData("POST", "/todoItems", false, EndpointKind.Primary, OperationKind.CreateResource, false)] - [InlineData("POST", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, OperationKind.AddToRelationship, false)] - [InlineData("PATCH", "/todoItems/1", false, EndpointKind.Primary, OperationKind.UpdateResource, false)] - [InlineData("PATCH", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, OperationKind.SetRelationship, false)] - [InlineData("PATCH", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, OperationKind.SetRelationship, false)] - [InlineData("DELETE", "/todoItems/1", false, EndpointKind.Primary, OperationKind.DeleteResource, false)] - [InlineData("DELETE", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, OperationKind.RemoveFromRelationship, false)] + [InlineData("POST", "/todoItems", false, EndpointKind.Primary, WriteOperationKind.CreateResource, false)] + [InlineData("POST", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, WriteOperationKind.AddToRelationship, false)] + [InlineData("PATCH", "/todoItems/1", false, EndpointKind.Primary, WriteOperationKind.UpdateResource, false)] + [InlineData("PATCH", "/todoItems/1/relationships/owner", false, EndpointKind.Relationship, WriteOperationKind.SetRelationship, false)] + [InlineData("PATCH", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, WriteOperationKind.SetRelationship, false)] + [InlineData("DELETE", "/todoItems/1", false, EndpointKind.Primary, WriteOperationKind.DeleteResource, false)] + [InlineData("DELETE", "/todoItems/1/relationships/tags", true, EndpointKind.Relationship, WriteOperationKind.RemoveFromRelationship, false)] public async Task Sets_request_properties_correctly(string requestMethod, string requestPath, bool expectIsCollection, EndpointKind expectKind, - OperationKind? expectOperationKind, bool expectIsReadOnly) + WriteOperationKind? expectWriteOperation, bool expectIsReadOnly) { // Arrange var options = new JsonApiOptions @@ -69,7 +69,7 @@ public async Task Sets_request_properties_correctly(string requestMethod, string // Assert request.IsCollection.Should().Be(expectIsCollection); request.Kind.Should().Be(expectKind); - request.OperationKind.Should().Be(expectOperationKind); + request.WriteOperation.Should().Be(expectWriteOperation); request.IsReadOnly.Should().Be(expectIsReadOnly); request.PrimaryResource.Should().NotBeNull(); request.PrimaryResource.PublicName.Should().Be("todoItems"); diff --git a/test/UnitTests/NeverResourceDefinitionAccessor.cs b/test/UnitTests/NeverResourceDefinitionAccessor.cs index 35e02e9023..f4d0f308bf 100644 --- a/test/UnitTests/NeverResourceDefinitionAccessor.cs +++ b/test/UnitTests/NeverResourceDefinitionAccessor.cs @@ -47,21 +47,21 @@ public IDictionary GetMeta(Type resourceType, IIdentifiable reso return null; } - public Task OnPrepareWriteAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { return Task.CompletedTask; } public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, - IIdentifiable rightResourceId, OperationKind operationKind, CancellationToken cancellationToken) + IIdentifiable rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { return Task.FromResult(rightResourceId); } public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - OperationKind operationKind, CancellationToken cancellationToken) + WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { return Task.CompletedTask; @@ -81,13 +81,13 @@ public Task OnRemoveFromRelationshipAsync(TResource leftResource, Has return Task.CompletedTask; } - public Task OnWritingAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { return Task.CompletedTask; } - public Task OnWriteSucceededAsync(TResource resource, OperationKind operationKind, CancellationToken cancellationToken) + public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { return Task.CompletedTask; From 76dbf36bc66b6c731c5cd4e3cced9509c94d3e11 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 15:26:23 +0200 Subject: [PATCH 28/29] Automated Code cleanup --- .../NoEntityFrameworkExample/Services/WorkItemService.cs | 3 +-- .../Repositories/IResourceRepositoryAccessor.cs | 3 +-- .../Repositories/ResourceRepositoryAccessor.cs | 3 +-- .../Resources/Annotations/RelationshipAttribute.cs | 4 ++-- src/JsonApiDotNetCore/Services/IAddToRelationshipService.cs | 3 +-- .../Services/IRemoveFromRelationshipService.cs | 3 +-- .../IdObfuscation/ObfuscatedIdentifiableController.cs | 3 +-- .../MultiTenancy/MultiTenantResourceService.cs | 3 +-- .../SoftDeletion/SoftDeletionAwareResourceService.cs | 3 +-- 9 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs index 6d0d81ffc8..df227d12d9 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs @@ -73,8 +73,7 @@ public async Task CreateAsync(WorkItem resource, CancellationToken can return workItems.Single(); } - public Task AddToToManyRelationshipAsync(int leftId, string relationshipName, ISet rightResourceIds, - CancellationToken cancellationToken) + public Task AddToToManyRelationshipAsync(int leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs index eec44078ae..4925697112 100644 --- a/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/IResourceRepositoryAccessor.cs @@ -75,8 +75,7 @@ Task AddToToManyRelationshipAsync(TId leftId, ISet /// Invokes . /// - Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, - CancellationToken cancellationToken) + Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable; } } diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index f61ac5daa6..c23ca04fd1 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -106,8 +106,7 @@ public async Task SetRelationshipAsync(TResource leftResource, object } /// - public async Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, - CancellationToken cancellationToken) + public async Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { dynamic repository = GetWriteRepository(typeof(TResource)); diff --git a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs index 07ba0bc152..eaeb10360d 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs @@ -41,8 +41,8 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute public Type LeftType { get; internal set; } /// - /// The type this relationship points to. This does not necessarily match the relationship property type. In the case of a relationship, - /// this value will be the collection element type. + /// The type this relationship points to. This does not necessarily match the relationship property type. In the case of a + /// relationship, this value will be the collection element type. /// /// /// /// /// Propagates notification that request handling should be canceled. /// - Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, - CancellationToken cancellationToken); + Task AddToToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken); } } diff --git a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs index 8dd1114b3a..12d0889ce1 100644 --- a/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs +++ b/src/JsonApiDotNetCore/Services/IRemoveFromRelationshipService.cs @@ -32,7 +32,6 @@ public interface IRemoveFromRelationshipService /// /// Propagates notification that request handling should be canceled. /// - Task RemoveFromToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, - CancellationToken cancellationToken); + Task RemoveFromToManyRelationshipAsync(TId leftId, string relationshipName, ISet rightResourceIds, CancellationToken cancellationToken); } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index b531018179..b2d89011cf 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -69,8 +69,7 @@ public Task PatchAsync(string id, [FromBody] TResource resource, } [HttpPatch("{id}/relationships/{relationshipName}")] - public Task PatchRelationshipAsync(string id, string relationshipName, [FromBody] object rightValue, - CancellationToken cancellationToken) + public Task PatchRelationshipAsync(string id, string relationshipName, [FromBody] object rightValue, CancellationToken cancellationToken) { int idValue = _codec.Decode(id); return base.PatchRelationshipAsync(idValue, relationshipName, rightValue, cancellationToken); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs index 527eb97ea5..138bf96cac 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/MultiTenancy/MultiTenantResourceService.cs @@ -60,8 +60,7 @@ public override async Task UpdateAsync(TId id, TResource resource, Ca return await base.UpdateAsync(id, resource, cancellationToken); } - public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, - CancellationToken cancellationToken) + public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, CancellationToken cancellationToken) { await AssertRightResourcesExistAsync(rightValue, cancellationToken); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs index afc9f19ed0..b09bbff935 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/SoftDeletionAwareResourceService.cs @@ -59,8 +59,7 @@ public override async Task UpdateAsync(TId id, TResource resource, Ca return await base.UpdateAsync(id, resource, cancellationToken); } - public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, - CancellationToken cancellationToken) + public override async Task SetRelationshipAsync(TId leftId, string relationshipName, object rightValue, CancellationToken cancellationToken) { if (IsSoftDeletable(_request.Relationship.RightType)) { From 0c576be27eefbd8c23b6adc72eb5519ed9338db1 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Wed, 4 Aug 2021 15:30:34 +0200 Subject: [PATCH 29/29] Add HTTP Method to trace logging --- src/JsonApiDotNetCore/Serialization/JsonApiReader.cs | 2 +- .../IntegrationTests/Logging/LoggingTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs index ced4ffbd26..d0dac14349 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs @@ -50,7 +50,7 @@ public async Task ReadAsync(InputFormatterContext context) string body = await GetRequestBodyAsync(context.HttpContext.Request.Body); string url = context.HttpContext.Request.GetEncodedUrl(); - _traceWriter.LogMessage(() => $"Received request at '{url}' with body: <<{body}>>"); + _traceWriter.LogMessage(() => $"Received {context.HttpContext.Request.Method} request at '{url}' with body: <<{body}>>"); object model = null; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs index c4c176a7c5..f234be59fc 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/LoggingTests.cs @@ -77,7 +77,7 @@ public async Task Logs_request_body_at_Trace_level() loggerFactory.Logger.Messages.Should().NotBeEmpty(); loggerFactory.Logger.Messages.Should().ContainSingle(message => message.LogLevel == LogLevel.Trace && - message.Text.StartsWith("Received request at 'http://localhost/auditEntries' with body: <<", StringComparison.Ordinal)); + message.Text.StartsWith("Received POST request at 'http://localhost/auditEntries' with body: <<", StringComparison.Ordinal)); } [Fact]