diff --git a/Build.ps1 b/Build.ps1 index e614f84c29..9798b5ee6e 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -19,7 +19,7 @@ function CheckLastExitCode { function RunInspectCode { $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') - dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal + dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --profile=JsonApiDotNetCore-WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal CheckLastExitCode [xml]$xml = Get-Content "$outputPath" diff --git a/CSharpGuidelinesAnalyzer.config b/CSharpGuidelinesAnalyzer.config new file mode 100644 index 0000000000..acd0856299 --- /dev/null +++ b/CSharpGuidelinesAnalyzer.config @@ -0,0 +1,5 @@ + + + + + diff --git a/CodingGuidelines.ruleset b/CodingGuidelines.ruleset new file mode 100644 index 0000000000..9447b105b1 --- /dev/null +++ b/CodingGuidelines.ruleset @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 80b7734180..3e7fe88452 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,8 +4,15 @@ 3.1.* 3.1.* 3.1.* + $(SolutionDir)CodingGuidelines.ruleset + + + CSharpGuidelinesAnalyzer.config + + + $(NoWarn);1591 true diff --git a/JsonApiDotNetCore-WarningSeverities.DotSettings b/JsonApiDotNetCore-WarningSeverities.DotSettings new file mode 100644 index 0000000000..f4a9ae32e8 --- /dev/null +++ b/JsonApiDotNetCore-WarningSeverities.DotSettings @@ -0,0 +1,254 @@ + + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + \ No newline at end of file diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index 0404af6c5b..a32ac146aa 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -72,6 +72,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); WARNING WARNING WARNING + WARNING SUGGESTION WARNING HINT @@ -554,7 +555,6 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); UseExplicitType UseVarWhenEvident UseVarWhenEvident - True False False False @@ -590,7 +590,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$)); $left$ = $right$; $left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$)); - SUGGESTION + WARNING True Replace classic argument null check with Guard clause True @@ -604,7 +604,7 @@ $left$ = $right$; Replace argument null check with Guard clause JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$)); if ($argument$ == null) throw new ArgumentNullException(nameof($argument$)); - SUGGESTION + WARNING True Replace collection null/empty check with extension method True @@ -617,7 +617,7 @@ $left$ = $right$; Replace collection null/empty check with extension method $collection$.IsNullOrEmpty() $collection$ == null || !$collection$.Any() - SUGGESTION + WARNING True True True diff --git a/benchmarks/BenchmarkResourcePublicNames.cs b/benchmarks/BenchmarkResourcePublicNames.cs index b8d6fdae12..84b63e7668 100644 --- a/benchmarks/BenchmarkResourcePublicNames.cs +++ b/benchmarks/BenchmarkResourcePublicNames.cs @@ -1,3 +1,5 @@ +#pragma warning disable AV1008 // Class should not be static + namespace Benchmarks { internal static class BenchmarkResourcePublicNames diff --git a/benchmarks/DependencyFactory.cs b/benchmarks/DependencyFactory.cs index d5ca4af6b6..7ecbafffbc 100644 --- a/benchmarks/DependencyFactory.cs +++ b/benchmarks/DependencyFactory.cs @@ -3,9 +3,9 @@ namespace Benchmarks { - internal static class DependencyFactory + internal sealed class DependencyFactory { - public static IResourceGraph CreateResourceGraph(IJsonApiOptions options) + public IResourceGraph CreateResourceGraph(IJsonApiOptions options) { var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance); builder.Add(BenchmarkResourcePublicNames.Type); diff --git a/benchmarks/Query/QueryParserBenchmarks.cs b/benchmarks/Query/QueryParserBenchmarks.cs index 8ba23d56f0..553d9fa7ba 100644 --- a/benchmarks/Query/QueryParserBenchmarks.cs +++ b/benchmarks/Query/QueryParserBenchmarks.cs @@ -20,6 +20,7 @@ namespace Benchmarks.Query [MemoryDiagnoser] public class QueryParserBenchmarks { + private readonly DependencyFactory _dependencyFactory = new DependencyFactory(); private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor(); private readonly QueryStringReader _queryStringReaderForSort; private readonly QueryStringReader _queryStringReaderForAll; @@ -31,7 +32,7 @@ public QueryParserBenchmarks() EnableLegacyFilterNotation = true }; - IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options); + IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options); var request = new JsonApiRequest { @@ -107,7 +108,7 @@ public void ComplexQuery() private void Run(int iterations, Action action) { - for (int i = 0; i < iterations; i++) + for (int index = 0; index < iterations; index++) { action(); } diff --git a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs index 9ecc308e7b..963c59cbcc 100644 --- a/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs @@ -29,12 +29,13 @@ public class JsonApiDeserializerBenchmarks } }); + private readonly DependencyFactory _dependencyFactory = new DependencyFactory(); private readonly IJsonApiDeserializer _jsonApiDeserializer; public JsonApiDeserializerBenchmarks() { var options = new JsonApiOptions(); - IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options); + IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options); var targetedFields = new TargetedFields(); var request = new JsonApiRequest(); var resourceFactory = new ResourceFactory(new ServiceContainer()); diff --git a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs index 088be638c4..7c42e26685 100644 --- a/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializerBenchmarks.cs @@ -21,12 +21,13 @@ public class JsonApiSerializerBenchmarks Name = Guid.NewGuid().ToString() }; + private readonly DependencyFactory _dependencyFactory = new DependencyFactory(); private readonly IJsonApiSerializer _jsonApiSerializer; public JsonApiSerializerBenchmarks() { var options = new JsonApiOptions(); - IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options); + IResourceGraph resourceGraph = _dependencyFactory.CreateResourceGraph(options); IFieldsToSerialize fieldsToSerialize = CreateFieldsToSerialize(resourceGraph); IMetaBuilder metaBuilder = new Mock().Object; diff --git a/inspectcode.ps1 b/inspectcode.ps1 index 83987d5e69..355b8f20dd 100644 --- a/inspectcode.ps1 +++ b/inspectcode.ps1 @@ -16,7 +16,7 @@ if ($LASTEXITCODE -ne 0) { $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') $resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html') -dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal +dotnet jb inspectcode JsonApiDotNetCore.sln --output="$outputPath" --profile=JsonApiDotNetCore-WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal if ($LASTEXITCODE -ne 0) { throw "Code inspection failed with exit code $LASTEXITCODE" diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index 9d13b17493..ae3d13d553 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -23,12 +23,12 @@ public AppDbContext(DbContextOptions options) protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() - .HasOne(t => t.Assignee) - .WithMany(p => p.AssignedTodoItems); + .HasOne(todoItem => todoItem.Assignee) + .WithMany(person => person.AssignedTodoItems); builder.Entity() - .HasOne(t => t.Owner) - .WithMany(p => p.TodoItems); + .HasOne(todoItem => todoItem.Owner) + .WithMany(person => person.TodoItems); builder.Entity() .HasKey(bc => new @@ -45,23 +45,23 @@ protected override void OnModelCreating(ModelBuilder builder) }); builder.Entity() - .HasOne(t => t.StakeHolderTodoItem) - .WithMany(t => t.StakeHolders) + .HasOne(person => person.StakeHolderTodoItem) + .WithMany(todoItem => todoItem.StakeHolders) .OnDelete(DeleteBehavior.Cascade); builder.Entity() - .HasMany(t => t.ChildTodoItems) - .WithOne(t => t.ParentTodo); + .HasMany(todoItem => todoItem.ChildTodoItems) + .WithOne(todoItem => todoItem.ParentTodo); builder.Entity() - .HasOne(p => p.Person) - .WithOne(p => p.Passport) + .HasOne(passport => passport.Person) + .WithOne(person => person.Passport) .HasForeignKey("PassportKey") .OnDelete(DeleteBehavior.SetNull); builder.Entity() - .HasOne(p => p.OneToOnePerson) - .WithOne(p => p.OneToOneTodoItem) + .HasOne(todoItem => todoItem.OneToOnePerson) + .WithOne(person => person.OneToOneTodoItem) .HasForeignKey("OneToOnePersonKey"); } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs index 5ea4353ecd..cef9af0a74 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs @@ -21,7 +21,7 @@ public ArticleHooksDefinition(IResourceGraph resourceGraph) public override IEnumerable
OnReturn(HashSet
resources, ResourcePipeline pipeline) { - if (pipeline == ResourcePipeline.GetSingle && resources.Any(r => r.Caption == "Classified")) + if (pipeline == ResourcePipeline.GetSingle && resources.Any(article => article.Caption == "Classified")) { throw new JsonApiException(new Error(HttpStatusCode.Forbidden) { @@ -29,7 +29,7 @@ public override IEnumerable
OnReturn(HashSet
resources, Resour }); } - return resources.Where(article => article.Caption != "This should not be included"); + return resources.Where(article => article.Caption != "This should not be included").ToArray(); } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs index a7a025bede..b2a5eb7775 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs @@ -31,12 +31,12 @@ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = fal public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { - resourcesByRelationship.GetByRelationship().ToList().ForEach(kvp => DisallowLocked(kvp.Value)); + resourcesByRelationship.GetByRelationship().ToList().ForEach(pair => DisallowLocked(pair.Value)); } public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline) { - return resources.Where(passport => !passport.IsLocked); + return resources.Where(passport => !passport.IsLocked).ToArray(); } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs index 1d70f5892b..473817b0e8 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs @@ -24,7 +24,7 @@ public override IEnumerable BeforeUpdateRelationship(HashSet ids public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { - resourcesByRelationship.GetByRelationship().ToList().ForEach(kvp => DisallowLocked(kvp.Value)); + resourcesByRelationship.GetByRelationship().ToList().ForEach(pair => DisallowLocked(pair.Value)); } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs index b170715e95..891959e00c 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs @@ -18,7 +18,7 @@ public TagHooksDefinition(IResourceGraph resourceGraph) public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline) { - return resources.Where(tag => tag.Name != "This should not be included"); + return resources.Where(tag => tag.Name != "This should not be included").ToArray(); } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs index 9e59aa6569..bb912e3a82 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooksDefinition.cs @@ -31,13 +31,13 @@ public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = fal public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { - List todoItems = resourcesByRelationship.GetByRelationship().SelectMany(kvp => kvp.Value).ToList(); + List todoItems = resourcesByRelationship.GetByRelationship().SelectMany(pair => pair.Value).ToList(); DisallowLocked(todoItems); } public override IEnumerable OnReturn(HashSet resources, ResourcePipeline pipeline) { - return resources.Where(todoItem => todoItem.Description != "This should not be included"); + return resources.Where(todoItem => todoItem.Description != "This should not be included").ToArray(); } } } diff --git a/src/JsonApiDotNetCore/ArgumentGuard.cs b/src/JsonApiDotNetCore/ArgumentGuard.cs index 28640848a2..c9f9e2d6a7 100644 --- a/src/JsonApiDotNetCore/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore/ArgumentGuard.cs @@ -3,6 +3,8 @@ using System.Linq; using JetBrains.Annotations; +#pragma warning disable AV1008 // Class should not be static + namespace JsonApiDotNetCore { internal static class ArgumentGuard @@ -20,13 +22,26 @@ public static void NotNull([CanBeNull] [NoEnumeration] T value, [NotNull] [In [AssertionMethod] [ContractAnnotation("value: null => halt")] - public static void NotNullNorEmpty([CanBeNull] IEnumerable value, [NotNull] [InvokerParameterName] string name) + public static void NotNullNorEmpty([CanBeNull] IEnumerable value, [NotNull] [InvokerParameterName] string name, + [CanBeNull] string collectionName = null) { NotNull(value, name); if (!value.Any()) { - throw new ArgumentException("Collection cannot be empty.", name); + throw new ArgumentException($"Must have one or more {collectionName ?? name}.", name); + } + } + + [AssertionMethod] + [ContractAnnotation("value: null => halt")] + public static void NotNullNorEmpty([CanBeNull] string value, [NotNull] [InvokerParameterName] string name) + { + NotNull(value, name); + + if (value == string.Empty) + { + throw new ArgumentException("String cannot be null or empty.", name); } } } diff --git a/src/JsonApiDotNetCore/ArrayFactory.cs b/src/JsonApiDotNetCore/ArrayFactory.cs index 44eb3ba3f6..a33102cbdd 100644 --- a/src/JsonApiDotNetCore/ArrayFactory.cs +++ b/src/JsonApiDotNetCore/ArrayFactory.cs @@ -1,3 +1,6 @@ +#pragma warning disable AV1008 // Class should not be static +#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type + namespace JsonApiDotNetCore { internal static class ArrayFactory diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs index 56faba2b66..453267d828 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs @@ -20,8 +20,8 @@ public void Reset() /// public void Declare(string localId, string resourceType) { - ArgumentGuard.NotNull(localId, nameof(localId)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNullNorEmpty(localId, nameof(localId)); + ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType)); AssertIsNotDeclared(localId); @@ -43,9 +43,9 @@ private void AssertIsNotDeclared(string localId) /// public void Assign(string localId, string resourceType, string stringId) { - ArgumentGuard.NotNull(localId, nameof(localId)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - ArgumentGuard.NotNull(stringId, nameof(stringId)); + ArgumentGuard.NotNullNorEmpty(localId, nameof(localId)); + ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType)); + ArgumentGuard.NotNullNorEmpty(stringId, nameof(stringId)); AssertIsDeclared(localId); @@ -64,8 +64,8 @@ public void Assign(string localId, string resourceType, string stringId) /// public string GetValue(string localId, string resourceType) { - ArgumentGuard.NotNull(localId, nameof(localId)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNullNorEmpty(localId, nameof(localId)); + ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType)); AssertIsDeclared(localId); diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs index 47ab571cb3..79ea1bf6ba 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs @@ -91,7 +91,7 @@ private void AssignLocalId(OperationContainer operation) { ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(operation.Resource.GetType()); - _localIdTracker.Assign(operation.Resource.LocalId, resourceContext.PublicName, string.Empty); + _localIdTracker.Assign(operation.Resource.LocalId, resourceContext.PublicName, "placeholder"); } } diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs index e522dabe51..1fb60d985e 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs @@ -84,7 +84,9 @@ public virtual async Task> ProcessAsync(IList : ISetRelationshipProcessor where TResource : class, IIdentifiable { + private readonly CollectionConverter _collectionConverter = new CollectionConverter(); private readonly ISetRelationshipService _service; public SetRelationshipProcessor(ISetRelationshipService service) @@ -36,14 +37,14 @@ public virtual async Task ProcessAsync(OperationContainer op return null; } - private static object GetRelationshipRightValue(OperationContainer operation) + private object GetRelationshipRightValue(OperationContainer operation) { RelationshipAttribute relationship = operation.Request.Relationship; object rightValue = relationship.GetValue(operation.Resource); if (relationship is HasManyAttribute) { - ICollection rightResources = TypeHelper.ExtractResources(rightValue); + ICollection rightResources = _collectionConverter.ExtractResources(rightValue); return rightResources.ToHashSet(IdentifiableComparer.Instance); } diff --git a/src/JsonApiDotNetCore/CollectionConverter.cs b/src/JsonApiDotNetCore/CollectionConverter.cs new file mode 100644 index 0000000000..a79757ceac --- /dev/null +++ b/src/JsonApiDotNetCore/CollectionConverter.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCore +{ + internal class CollectionConverter + { + private static readonly Type[] HashSetCompatibleCollectionTypes = + { + typeof(HashSet<>), + typeof(ICollection<>), + typeof(ISet<>), + typeof(IEnumerable<>), + typeof(IReadOnlyCollection<>) + }; + + /// + /// Creates a collection instance based on the specified collection type and copies the specified elements into it. + /// + /// + /// Source to copy from. + /// + /// + /// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}). + /// + public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType) + { + ArgumentGuard.NotNull(source, nameof(source)); + ArgumentGuard.NotNull(collectionType, nameof(collectionType)); + + Type concreteCollectionType = ToConcreteCollectionType(collectionType); + dynamic concreteCollectionInstance = Activator.CreateInstance(concreteCollectionType); + + foreach (object item in source) + { + concreteCollectionInstance!.Add((dynamic)item); + } + + return concreteCollectionInstance; + } + + /// + /// 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) + { + if (collectionType.IsInterface && collectionType.IsGenericType) + { + Type genericTypeDefinition = collectionType.GetGenericTypeDefinition(); + + if (genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(ISet<>) || + genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(IReadOnlyCollection<>)) + { + return typeof(HashSet<>).MakeGenericType(collectionType.GenericTypeArguments[0]); + } + + if (genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyList<>)) + { + return typeof(List<>).MakeGenericType(collectionType.GenericTypeArguments[0]); + } + } + + return collectionType; + } + + /// + /// Returns a collection that contains zero, one or multiple resources, depending on the specified value. + /// + public ICollection ExtractResources(object value) + { + if (value is ICollection resourceCollection) + { + return resourceCollection; + } + + if (value is IEnumerable resources) + { + return resources.ToList(); + } + + if (value is IIdentifiable resource) + { + return resource.AsArray(); + } + + return Array.Empty(); + } + + /// + /// Returns the element type if the specified type is a generic collection, for example: IList{string} -> string or IList -> null. + /// + public Type TryGetCollectionElementType(Type type) + { + if (type != null) + { + if (type.IsGenericType && type.GenericTypeArguments.Length == 1) + { + if (type.IsOrImplementsInterface(typeof(IEnumerable))) + { + return type.GenericTypeArguments[0]; + } + } + } + + return null; + } + + /// + /// Indicates whether a instance can be assigned to the specified type, for example IList{Article} -> false or ISet{Article} -> + /// true. + /// + public bool TypeCanContainHashSet(Type collectionType) + { + if (collectionType.IsGenericType) + { + Type openCollectionType = collectionType.GetGenericTypeDefinition(); + return HashSetCompatibleCollectionTypes.Contains(openCollectionType); + } + + return false; + } + } +} diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs index be9962dc2d..68997f60b3 100644 --- a/src/JsonApiDotNetCore/CollectionExtensions.cs +++ b/src/JsonApiDotNetCore/CollectionExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; @@ -17,5 +18,21 @@ public static bool IsNullOrEmpty(this IEnumerable source) return !source.Any(); } + + public static int FindIndex(this IList source, Predicate match) + { + ArgumentGuard.NotNull(source, nameof(source)); + ArgumentGuard.NotNull(match, nameof(match)); + + for (int index = 0; index < source.Count; index++) + { + if (match(source[index])) + { + return index; + } + } + + return -1; + } } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 9f04418849..b5ce25aa82 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -261,8 +261,8 @@ private void AddResourceHooks() _services.AddSingleton(typeof(IHooksDiscovery<>), typeof(HooksDiscovery<>)); _services.AddScoped(typeof(IResourceHookContainer<>), typeof(ResourceHooksDefinition<>)); _services.AddTransient(); - _services.AddTransient(); - _services.AddScoped(); + _services.AddTransient(); + _services.AddScoped(); _services.AddScoped(); } else diff --git a/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs b/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs index 667d6680fa..7bf545b997 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceDescriptorAssemblyCache.cs @@ -10,6 +10,8 @@ namespace JsonApiDotNetCore.Configuration /// internal sealed class ResourceDescriptorAssemblyCache { + private readonly TypeLocator _typeLocator = new TypeLocator(); + private readonly Dictionary> _resourceDescriptorsPerAssembly = new Dictionary>(); @@ -25,7 +27,7 @@ public void RegisterAssembly(Assembly assembly) { EnsureAssembliesScanned(); - return _resourceDescriptorsPerAssembly.Select(pair => (pair.Key, pair.Value)); + return _resourceDescriptorsPerAssembly.Select(pair => (pair.Key, pair.Value)).ToArray(); } private void EnsureAssembliesScanned() @@ -36,11 +38,11 @@ private void EnsureAssembliesScanned() } } - private static IEnumerable ScanForResourceDescriptors(Assembly assembly) + private IEnumerable ScanForResourceDescriptors(Assembly assembly) { foreach (Type type in assembly.GetTypes()) { - ResourceDescriptor resourceDescriptor = TypeLocator.TryGetResourceDescriptor(type); + ResourceDescriptor resourceDescriptor = _typeLocator.TryGetResourceDescriptor(type); if (resourceDescriptor != null) { diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs index 3f56e3fffe..1fc36ca4a2 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs @@ -32,9 +32,9 @@ public IReadOnlyCollection GetResourceContexts() /// public ResourceContext GetResourceContext(string resourceName) { - ArgumentGuard.NotNull(resourceName, nameof(resourceName)); + ArgumentGuard.NotNullNorEmpty(resourceName, nameof(resourceName)); - return _resources.SingleOrDefault(e => e.PublicName == resourceName); + return _resources.SingleOrDefault(resourceContext => resourceContext.PublicName == resourceName); } /// @@ -43,8 +43,8 @@ public ResourceContext GetResourceContext(Type resourceType) ArgumentGuard.NotNull(resourceType, nameof(resourceType)); return IsLazyLoadingProxyForResourceType(resourceType) - ? _resources.SingleOrDefault(e => e.ResourceType == resourceType.BaseType) - : _resources.SingleOrDefault(e => e.ResourceType == resourceType); + ? _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType.BaseType) + : _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType); } /// @@ -109,7 +109,8 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati return null; } - return GetResourceContext(relationship.RightType).Relationships.SingleOrDefault(r => r.Property == relationship.InverseNavigationProperty); + return GetResourceContext(relationship.RightType).Relationships + .SingleOrDefault(nextRelationship => nextRelationship.Property == relationship.InverseNavigationProperty); } private IReadOnlyCollection Getter(Expression> selector = null, @@ -145,7 +146,7 @@ private IReadOnlyCollection Getter(Expression // model => model.Field1 try { - targeted.Add(available.Single(f => f.Property.Name == memberExpression.Member.Name)); + targeted.Add(available.Single(field => field.Property.Name == memberExpression.Member.Name)); return targeted; } catch (InvalidOperationException) @@ -169,7 +170,7 @@ private IReadOnlyCollection Getter(Expression foreach (MemberInfo member in newExpression.Members) { memberName = member.Name; - targeted.Add(available.Single(f => f.Property.Name == memberName)); + targeted.Add(available.Single(field => field.Property.Name == memberName)); } return targeted; diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index a6bb501ffd..0451c83643 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -19,6 +19,7 @@ public class ResourceGraphBuilder private readonly IJsonApiOptions _options; private readonly ILogger _logger; private readonly List _resources = new List(); + private readonly TypeLocator _typeLocator = new TypeLocator(); public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactory) { @@ -102,15 +103,15 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu { ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - if (_resources.Any(e => e.ResourceType == resourceType)) + if (_resources.Any(resourceContext => resourceContext.ResourceType == resourceType)) { return this; } - if (TypeHelper.IsOrImplementsInterface(resourceType, typeof(IIdentifiable))) + if (resourceType.IsOrImplementsInterface(typeof(IIdentifiable))) { string effectivePublicName = publicName ?? FormatResourceName(resourceType); - Type effectiveIdType = idType ?? TypeLocator.TryGetIdType(resourceType); + Type effectiveIdType = idType ?? _typeLocator.TryGetIdType(resourceType); ResourceContext resourceContext = CreateResourceContext(effectivePublicName, resourceType, effectiveIdType); _resources.Add(resourceContext); @@ -138,8 +139,6 @@ private ResourceContext CreateResourceContext(string publicName, Type resourceTy private IReadOnlyCollection GetAttributes(Type resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - var attributes = new List(); foreach (PropertyInfo property in resourceType.GetProperties()) @@ -183,8 +182,6 @@ private IReadOnlyCollection GetAttributes(Type resourceType) private IReadOnlyCollection GetRelationships(Type resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - var attributes = new List(); PropertyInfo[] properties = resourceType.GetProperties(); @@ -205,7 +202,7 @@ private IReadOnlyCollection GetRelationships(Type resourc if (attribute is HasManyThroughAttribute hasManyThroughAttribute) { - PropertyInfo throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.ThroughPropertyName); + PropertyInfo throughProperty = properties.SingleOrDefault(property => property.Name == hasManyThroughAttribute.ThroughPropertyName); if (throughProperty == null) { @@ -240,14 +237,15 @@ private IReadOnlyCollection GetRelationships(Type resourc else { // In case of a non-self-referencing many-to-many relationship, we just pick the single compatible type. - hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType.IsAssignableFrom(resourceType)) ?? + hasManyThroughAttribute.LeftProperty = + throughProperties.SingleOrDefault(property => property.PropertyType.IsAssignableFrom(resourceType)) ?? throw new InvalidConfigurationException($"'{throughType}' does not contain a navigation property to type '{resourceType}'."); } // ArticleTag.ArticleId string leftIdPropertyName = hasManyThroughAttribute.LeftIdPropertyName ?? hasManyThroughAttribute.LeftProperty.Name + "Id"; - hasManyThroughAttribute.LeftIdProperty = throughProperties.SingleOrDefault(x => x.Name == leftIdPropertyName) ?? + hasManyThroughAttribute.LeftIdProperty = throughProperties.SingleOrDefault(property => property.Name == leftIdPropertyName) ?? throw new InvalidConfigurationException( $"'{throughType}' does not contain a relationship ID property to type '{resourceType}' with name '{leftIdPropertyName}'."); @@ -262,7 +260,8 @@ private IReadOnlyCollection GetRelationships(Type resourc else { // In case of a non-self-referencing many-to-many relationship, we just pick the single compatible type. - hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.RightType) ?? + hasManyThroughAttribute.RightProperty = + throughProperties.SingleOrDefault(property => property.PropertyType == hasManyThroughAttribute.RightType) ?? throw new InvalidConfigurationException( $"'{throughType}' does not contain a navigation property to type '{hasManyThroughAttribute.RightType}'."); } @@ -270,7 +269,7 @@ private IReadOnlyCollection GetRelationships(Type resourc // ArticleTag.TagId string rightIdPropertyName = hasManyThroughAttribute.RightIdPropertyName ?? hasManyThroughAttribute.RightProperty.Name + "Id"; - hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName) ?? + hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(property => property.Name == rightIdPropertyName) ?? throw new InvalidConfigurationException( $"'{throughType}' does not contain a relationship ID property to type '{hasManyThroughAttribute.RightType}' with name '{rightIdPropertyName}'."); } @@ -289,7 +288,7 @@ private Type TryGetThroughType(PropertyInfo throughProperty) { Type constructedThroughType = typeof(ICollection<>).MakeGenericType(typeArguments[0]); - if (TypeHelper.IsOrImplementsInterface(throughProperty.PropertyType, constructedThroughType)) + if (throughProperty.PropertyType.IsOrImplementsInterface(constructedThroughType)) { return typeArguments[0]; } @@ -307,13 +306,9 @@ private Type GetRelationshipType(RelationshipAttribute relationship, PropertyInf return relationship is HasOneAttribute ? property.PropertyType : property.PropertyType.GetGenericArguments()[0]; } - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local private IReadOnlyCollection GetEagerLoads(Type resourceType, int recursionDepth = 0) { - if (recursionDepth >= 500) - { - throw new InvalidOperationException("Infinite recursion detected in eager-load chain."); - } + AssertNoInfiniteRecursion(recursionDepth); var attributes = new List(); PropertyInfo[] properties = resourceType.GetProperties(); @@ -337,9 +332,19 @@ private IReadOnlyCollection GetEagerLoads(Type resourceType, return attributes; } + [AssertionMethod] + private static void AssertNoInfiniteRecursion(int recursionDepth) + { + if (recursionDepth >= 500) + { + throw new InvalidOperationException("Infinite recursion detected in eager-load chain."); + } + } + private Type TypeOrElementType(Type type) { - Type[] interfaces = type.GetInterfaces().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)).ToArray(); + Type[] interfaces = type.GetInterfaces() + .Where(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IEnumerable<>)).ToArray(); return interfaces.Length == 1 ? interfaces.Single().GenericTypeArguments[0] : type; } diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs index a63d734fe4..3192ddd85f 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs @@ -16,6 +16,8 @@ namespace JsonApiDotNetCore.Configuration [PublicAPI] public static class ServiceCollectionExtensions { + private static readonly TypeLocator TypeLocator = new TypeLocator(); + /// /// Configures JsonApiDotNetCore by registering resources manually. /// diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs index 41e3a390bc..b18c8826f6 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs @@ -70,6 +70,7 @@ public class ServiceDiscoveryFacade private readonly ResourceGraphBuilder _resourceGraphBuilder; private readonly IJsonApiOptions _options; private readonly ResourceDescriptorAssemblyCache _assemblyCache = new ResourceDescriptorAssemblyCache(); + private readonly TypeLocator _typeLocator = new TypeLocator(); public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, IJsonApiOptions options, ILoggerFactory loggerFactory) @@ -140,7 +141,7 @@ private void AddInjectables(IReadOnlyCollection resourceDesc private void AddDbContextResolvers(Assembly assembly) { - IEnumerable dbContextTypes = TypeLocator.GetDerivedTypes(assembly, typeof(DbContext)); + IEnumerable dbContextTypes = _typeLocator.GetDerivedTypes(assembly, typeof(DbContext)); foreach (Type dbContextType in dbContextTypes) { @@ -158,7 +159,7 @@ private void AddResourceHookDefinitions(Assembly assembly, ResourceDescriptor id { try { - Type resourceDefinition = TypeLocator.GetDerivedGenericTypes(assembly, typeof(ResourceHooksDefinition<>), identifiable.ResourceType) + Type resourceDefinition = _typeLocator.GetDerivedGenericTypes(assembly, typeof(ResourceHooksDefinition<>), identifiable.ResourceType) .SingleOrDefault(); if (resourceDefinition != null) @@ -166,10 +167,10 @@ private void AddResourceHookDefinitions(Assembly assembly, ResourceDescriptor id _services.AddScoped(typeof(ResourceHooksDefinition<>).MakeGenericType(identifiable.ResourceType), resourceDefinition); } } - catch (InvalidOperationException e) + catch (InvalidOperationException exception) { throw new InvalidConfigurationException($"Cannot define multiple ResourceHooksDefinition<> implementations for '{identifiable.ResourceType}'", - e); + exception); } } @@ -203,8 +204,8 @@ private void RegisterImplementations(Assembly assembly, Type interfaceType, Reso ? ArrayFactory.Create(resourceDescriptor.ResourceType, resourceDescriptor.IdType) : ArrayFactory.Create(resourceDescriptor.ResourceType); - (Type implementation, Type registrationInterface)? - result = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + (Type implementation, Type registrationInterface)? result = + _typeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); if (result != null) { diff --git a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs index 303cb32c43..e0b0634efe 100644 --- a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs @@ -9,15 +9,15 @@ namespace JsonApiDotNetCore.Configuration /// /// Used to locate types and facilitate resource auto-discovery. /// - internal static class TypeLocator + internal sealed class TypeLocator { /// /// Attempts to lookup the ID type of the specified resource type. Returns null if it does not implement . /// - public static Type TryGetIdType(Type resourceType) + public Type TryGetIdType(Type resourceType) { - Type identifiableInterface = resourceType.GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>)); + Type identifiableInterface = resourceType.GetInterfaces().FirstOrDefault(@interface => + @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IIdentifiable<>)); return identifiableInterface?.GetGenericArguments()[0]; } @@ -25,9 +25,9 @@ public static Type TryGetIdType(Type resourceType) /// /// Attempts to get a descriptor for the specified resource type. /// - public static ResourceDescriptor TryGetResourceDescriptor(Type type) + public ResourceDescriptor TryGetResourceDescriptor(Type type) { - if (TypeHelper.IsOrImplementsInterface(type, typeof(IIdentifiable))) + if (type.IsOrImplementsInterface(typeof(IIdentifiable))) { Type idType = TryGetIdType(type); @@ -57,7 +57,7 @@ public static ResourceDescriptor TryGetResourceDescriptor(Type type) /// GetGenericInterfaceImplementation(assembly, typeof(IResourceService<,>), typeof(Article), typeof(Guid)); /// ]]> /// - public static (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface, + public (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface, params Type[] interfaceGenericTypeArguments) { ArgumentGuard.NotNull(assembly, nameof(assembly)); @@ -121,7 +121,7 @@ private static (Type implementation, Type registrationInterface)? FindGenericInt /// GetDerivedGenericTypes(assembly, typeof(ResourceDefinition<>), typeof(Article)) /// ]]> /// - public static IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly, Type openGenericType, params Type[] genericArguments) + public IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly, Type openGenericType, params Type[] genericArguments) { Type genericType = openGenericType.MakeGenericType(genericArguments); return GetDerivedTypes(assembly, genericType).ToArray(); @@ -141,7 +141,7 @@ public static IReadOnlyCollection GetDerivedGenericTypes(Assembly assembly /// GetDerivedGenericTypes(assembly, typeof(DbContext)) /// /// - public static IEnumerable GetDerivedTypes(Assembly assembly, Type inheritedType) + public IEnumerable GetDerivedTypes(Assembly assembly, Type inheritedType) { foreach (Type type in assembly.GetTypes()) { diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs index e363b1aabb..4bf10ec976 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs @@ -48,7 +48,7 @@ public DisableQueryStringAttribute(StandardQueryStringParameters parameters) /// public DisableQueryStringAttribute(string parameterNames) { - ArgumentGuard.NotNull(parameterNames, nameof(parameterNames)); + ArgumentGuard.NotNullNorEmpty(parameterNames, nameof(parameterNames)); ParameterNames = parameterNames.Split(",").ToList(); } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index d625abbdec..978fe828f5 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -130,7 +130,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relati relationshipName }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); if (_getSecondary == null) { @@ -153,7 +153,7 @@ public virtual async Task GetRelationshipAsync(TId id, string rel relationshipName }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); if (_getRelationship == null) { @@ -232,7 +232,7 @@ public virtual async Task PostRelationshipAsync(TId id, string re secondaryResourceIds }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); if (_addToRelationship == null) @@ -300,7 +300,7 @@ public virtual async Task PatchRelationshipAsync(TId id, string r secondaryResourceIds }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); if (_setRelationship == null) { @@ -357,7 +357,7 @@ public virtual async Task DeleteRelationshipAsync(TId id, string secondaryResourceIds }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); if (_removeFromRelationship == null) diff --git a/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs b/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs index 8bda54c806..2a4c8cfb84 100644 --- a/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs +++ b/src/JsonApiDotNetCore/Controllers/ModelStateViolation.cs @@ -17,8 +17,8 @@ public sealed class ModelStateViolation public ModelStateViolation(string prefix, string propertyName, Type resourceType, ModelError error) { - ArgumentGuard.NotNull(prefix, nameof(prefix)); - ArgumentGuard.NotNull(propertyName, nameof(propertyName)); + ArgumentGuard.NotNullNorEmpty(prefix, nameof(prefix)); + ArgumentGuard.NotNullNorEmpty(propertyName, nameof(propertyName)); ArgumentGuard.NotNull(resourceType, nameof(resourceType)); ArgumentGuard.NotNull(error, nameof(error)); diff --git a/src/JsonApiDotNetCore/Errors/JsonApiException.cs b/src/JsonApiDotNetCore/Errors/JsonApiException.cs index 55a88e287a..93ba6fe6cb 100644 --- a/src/JsonApiDotNetCore/Errors/JsonApiException.cs +++ b/src/JsonApiDotNetCore/Errors/JsonApiException.cs @@ -34,14 +34,10 @@ public JsonApiException(Error error, Exception innerException = null) public JsonApiException(IEnumerable errors, Exception innerException = null) : base(null, innerException) { - ArgumentGuard.NotNull(errors, nameof(errors)); + List errorList = errors?.ToList(); + ArgumentGuard.NotNullNorEmpty(errorList, nameof(errors)); - Errors = errors.ToList(); - - if (!Errors.Any()) - { - throw new ArgumentException("At least one error is required.", nameof(errors)); - } + Errors = errorList; } } } diff --git a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs index 95ef199932..7afe5b04cc 100644 --- a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs +++ b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs @@ -11,9 +11,9 @@ public sealed class MissingResourceInRelationship public MissingResourceInRelationship(string relationshipName, string resourceType, string resourceId) { - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - ArgumentGuard.NotNull(resourceId, nameof(resourceId)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType)); + ArgumentGuard.NotNullNorEmpty(resourceId, nameof(resourceId)); RelationshipName = relationshipName; ResourceType = resourceType; diff --git a/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs b/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs similarity index 83% rename from src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs rename to src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs index 5245c4344f..9da29c521b 100644 --- a/src/JsonApiDotNetCore/Errors/NonSharedTransactionException.cs +++ b/src/JsonApiDotNetCore/Errors/NonParticipatingTransactionException.cs @@ -8,9 +8,9 @@ namespace JsonApiDotNetCore.Errors /// The error that is thrown when a repository does not participate in the overarching transaction during an atomic:operations request. /// [PublicAPI] - public sealed class NonSharedTransactionException : JsonApiException + public sealed class NonParticipatingTransactionException : JsonApiException { - public NonSharedTransactionException() + public NonParticipatingTransactionException() : base(new Error(HttpStatusCode.UnprocessableEntity) { Title = "Unsupported combination of resource types in atomic:operations request.", diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs index e09d327fbc..b707e2569f 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Discovery/HooksDiscovery.cs @@ -35,7 +35,7 @@ public class HooksDiscovery : IHooksDiscovery public HooksDiscovery(IServiceProvider provider) { - _allHooks = Enum.GetValues(typeof(ResourceHook)).Cast().Where(h => h != ResourceHook.None).ToArray(); + _allHooks = Enum.GetValues(typeof(ResourceHook)).Cast().Where(hook => hook != ResourceHook.None).ToArray(); Type containerType; diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs index a61fb4b6d5..91e51a9660 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/DiffableResourceHashSet.cs @@ -15,12 +15,15 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution public sealed class DiffableResourceHashSet : ResourceHashSet, IDiffableResourceHashSet where TResource : class, IIdentifiable { + // ReSharper disable once StaticMemberInGenericType + private static readonly CollectionConverter CollectionConverter = new CollectionConverter(); + private readonly HashSet _databaseValues; private readonly bool _databaseValuesLoaded; - private readonly Dictionary> _updatedAttributes; + private readonly IDictionary> _updatedAttributes; public DiffableResourceHashSet(HashSet requestResources, HashSet databaseResources, - Dictionary> relationships, Dictionary> updatedAttributes) + IDictionary> relationships, IDictionary> updatedAttributes) : base(requestResources, relationships) { _databaseValues = databaseResources; @@ -32,12 +35,10 @@ public DiffableResourceHashSet(HashSet requestResources, HashSet internal DiffableResourceHashSet(IEnumerable requestResources, IEnumerable databaseResources, - Dictionary relationships, ITargetedFields targetedFields) + IDictionary relationships, ITargetedFields targetedFields) : this((HashSet)requestResources, (HashSet)databaseResources, - TypeHelper.ConvertRelationshipDictionary(relationships), - targetedFields.Attributes == null - ? null - : TypeHelper.ConvertAttributeDictionary(targetedFields.Attributes, (HashSet)requestResources)) + relationships.ToDictionary(pair => pair.Key, pair => (HashSet)pair.Value), + targetedFields.Attributes?.ToDictionary(attr => attr.Property, _ => (HashSet)requestResources)) { } @@ -51,7 +52,7 @@ public IEnumerable> GetDiffs() foreach (TResource resource in this) { - TResource currentValueInDatabase = _databaseValues.Single(e => resource.StringId == e.StringId); + TResource currentValueInDatabase = _databaseValues.Single(databaseResource => resource.StringId == databaseResource.StringId); yield return new ResourceDiffPair(resource, currentValueInDatabase); } } @@ -61,17 +62,17 @@ public override HashSet GetAffected(Expression - internal sealed class HookExecutorHelper : IHookExecutorHelper + internal sealed class HookContainerProvider : IHookContainerProvider { + private static readonly HooksCollectionConverter CollectionConverter = new HooksCollectionConverter(); + private static readonly HooksObjectFactory ObjectFactory = new HooksObjectFactory(); + private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter(); + private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; private readonly IJsonApiOptions _options; - private readonly IGenericServiceFactory _genericProcessorFactory; + private readonly IGenericServiceFactory _genericServiceFactory; private readonly IResourceContextProvider _resourceContextProvider; private readonly Dictionary _hookContainers; private readonly Dictionary _hookDiscoveries; private readonly List _targetedHooksForRelatedResources; - public HookExecutorHelper(IGenericServiceFactory genericProcessorFactory, IResourceContextProvider resourceContextProvider, IJsonApiOptions options) + public HookContainerProvider(IGenericServiceFactory genericServiceFactory, IResourceContextProvider resourceContextProvider, IJsonApiOptions options) { _options = options; - _genericProcessorFactory = genericProcessorFactory; + _genericServiceFactory = genericServiceFactory; _resourceContextProvider = resourceContextProvider; _hookContainers = new Dictionary(); _hookDiscoveries = new Dictionary(); @@ -40,13 +44,13 @@ public HookExecutorHelper(IGenericServiceFactory genericProcessorFactory, IResou /// public IResourceHookContainer GetResourceHookContainer(RightType targetResource, ResourceHook hook = ResourceHook.None) { - // checking the cache if we have a reference for the requested container, - // regardless of the hook we will use it for. If the value is null, - // it means there was no implementation IResourceHookContainer at all, + // checking the cache if we have a reference for the requested container, + // regardless of the hook we will use it for. If the value is null, + // it means there was no implementation IResourceHookContainer at all, // so we need not even bother. if (!_hookContainers.TryGetValue(targetResource, out IResourceHookContainer container)) { - container = _genericProcessorFactory.Get(typeof(ResourceHooksDefinition<>), targetResource); + container = _genericServiceFactory.Get(typeof(ResourceHooksDefinition<>), targetResource); _hookContainers[targetResource] = container; } @@ -55,7 +59,7 @@ public IResourceHookContainer GetResourceHookContainer(RightType targetResource, return null; } - // if there was a container, first check if it implements the hook we + // if there was a container, first check if it implements the hook we // want to use it for. IEnumerable targetHooks; @@ -87,40 +91,19 @@ public IResourceHookContainer GetResourceHookContainer(Res return (IResourceHookContainer)GetResourceHookContainer(typeof(TResource), hook); } - public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, ResourceHook hook, - params RelationshipAttribute[] relationshipsToNextLayer) + public IEnumerable LoadDbValues(LeftType resourceTypeForRepository, IEnumerable resources, params RelationshipAttribute[] relationshipsToNextLayer) { - LeftType idType = TypeHelper.GetIdType(resourceTypeForRepository); + LeftType idType = ObjectFactory.GetIdType(resourceTypeForRepository); MethodInfo parameterizedGetWhere = - GetType().GetMethod(nameof(GetWhereAndInclude), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(resourceTypeForRepository, + GetType().GetMethod(nameof(GetWhereWithInclude), BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(resourceTypeForRepository, idType); - IEnumerable cast = ((IEnumerable)resources).Cast(); - IList ids = TypeHelper.CopyToList(cast.Select(i => i.GetTypedId()), idType); - var values = (IEnumerable)parameterizedGetWhere.Invoke(this, ArrayFactory.Create(ids, relationshipsToNextLayer)); - - if (values == null) - { - return null; - } - - return (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(resourceTypeForRepository), - TypeHelper.CopyToList(values, resourceTypeForRepository)); - } - - public HashSet LoadDbValues(IEnumerable resources, ResourceHook hook, params RelationshipAttribute[] relationships) - where TResource : class, IIdentifiable - { - Type resourceType = typeof(TResource); - IEnumerable dbValues = LoadDbValues(resourceType, resources, hook, relationships)?.Cast(); - - if (dbValues == null) - { - return null; - } + IEnumerable resourceIds = ((IEnumerable)resources).Cast().Select(resource => resource.GetTypedId()); + IList idsAsList = CollectionConverter.CopyToList(resourceIds, idType); + var values = (IEnumerable)parameterizedGetWhere.Invoke(this, ArrayFactory.Create(idsAsList, relationshipsToNextLayer)); - return new HashSet(dbValues); + return values == null ? null : CollectionConverter.CopyToHashSet(values, resourceTypeForRepository); } public bool ShouldLoadDbValues(LeftType resourceType, ResourceHook hook) @@ -159,14 +142,14 @@ private IHooksDiscovery GetHookDiscovery(LeftType resourceType) { if (!_hookDiscoveries.TryGetValue(resourceType, out IHooksDiscovery discovery)) { - discovery = _genericProcessorFactory.Get(typeof(IHooksDiscovery<>), resourceType); + discovery = _genericServiceFactory.Get(typeof(IHooksDiscovery<>), resourceType); _hookDiscoveries[resourceType] = discovery; } return discovery; } - private IEnumerable GetWhereAndInclude(IReadOnlyCollection ids, RelationshipAttribute[] relationshipsToNextLayer) + private IEnumerable GetWhereWithInclude(IReadOnlyCollection ids, RelationshipAttribute[] relationshipsToNextLayer) where TResource : class, IIdentifiable { if (!ids.Any()) @@ -212,30 +195,33 @@ private static FilterExpression CreateFilterByIds(IReadOnlyCollection private IResourceReadRepository GetRepository() where TResource : class, IIdentifiable { - return _genericProcessorFactory.Get>(typeof(IResourceReadRepository<,>), typeof(TResource), typeof(TId)); + return _genericServiceFactory.Get>(typeof(IResourceReadRepository<,>), typeof(TResource), typeof(TId)); } - public Dictionary LoadImplicitlyAffected(Dictionary leftResourcesByRelation, + public IDictionary LoadImplicitlyAffected(IDictionary leftResourcesByRelation, IEnumerable existingRightResources = null) { List existingRightResourceList = existingRightResources?.Cast().ToList(); var implicitlyAffected = new Dictionary(); - foreach (KeyValuePair kvp in leftResourcesByRelation) + foreach (KeyValuePair pair in leftResourcesByRelation) { - if (IsHasManyThrough(kvp, out IEnumerable lefts, out RelationshipAttribute relationship)) + RelationshipAttribute relationship = pair.Key; + IEnumerable lefts = pair.Value; + + if (relationship is HasManyThroughAttribute) { continue; } // note that we don't have to check if BeforeImplicitUpdate hook is implemented. If not, it wont ever get here. - IEnumerable includedLefts = LoadDbValues(relationship.LeftType, lefts, ResourceHook.BeforeImplicitUpdateRelationship, relationship); + IEnumerable includedLefts = LoadDbValues(relationship.LeftType, lefts, relationship); AddToImplicitlyAffected(includedLefts, relationship, existingRightResourceList, implicitlyAffected); } - return implicitlyAffected.ToDictionary(kvp => kvp.Key, kvp => TypeHelper.CreateHashSetFor(kvp.Key.RightType, kvp.Value)); + return implicitlyAffected.ToDictionary(pair => pair.Key, pair => CollectionConverter.CopyToHashSet(pair.Value, pair.Key.RightType)); } private void AddToImplicitlyAffected(IEnumerable includedLefts, RelationshipAttribute relationship, List existingRightResourceList, @@ -243,37 +229,23 @@ private void AddToImplicitlyAffected(IEnumerable includedLefts, RelationshipAttr { foreach (IIdentifiable ip in includedLefts) { - IList dbRightResourceList = TypeHelper.CreateListFor(relationship.RightType); object relationshipValue = relationship.GetValue(ip); - - if (!(relationshipValue is IEnumerable)) - { - if (relationshipValue != null) - { - dbRightResourceList.Add(relationshipValue); - } - } - else - { - AddToList(dbRightResourceList, (IEnumerable)relationshipValue); - } - - List dbRightResourceListCast = dbRightResourceList.Cast().ToList(); + ICollection dbRightResources = CollectionConverter.ExtractResources(relationshipValue); if (existingRightResourceList != null) { - dbRightResourceListCast = dbRightResourceListCast.Except(existingRightResourceList, _comparer).ToList(); + dbRightResources = dbRightResources.Except(existingRightResourceList, _comparer).ToList(); } - if (dbRightResourceListCast.Any()) + if (dbRightResources.Any()) { if (!implicitlyAffected.TryGetValue(relationship, out IEnumerable affected)) { - affected = TypeHelper.CreateListFor(relationship.RightType); + affected = CollectionConverter.CopyToList(Array.Empty(), relationship.RightType); implicitlyAffected[relationship] = affected; } - AddToList((IList)affected, dbRightResourceListCast); + AddToList((IList)affected, dbRightResources); } } } @@ -285,12 +257,5 @@ private static void AddToList(IList list, IEnumerable itemsToAdd) list.Add(item); } } - - private bool IsHasManyThrough(KeyValuePair kvp, out IEnumerable resources, out RelationshipAttribute attr) - { - attr = kvp.Key; - resources = kvp.Value; - return kvp.Key is HasManyThroughAttribute; - } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs index 2d77f065b0..a70b643675 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IByAffectedRelationships.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using JetBrains.Annotations; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -7,12 +8,13 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution /// /// An interface that is implemented to expose a relationship dictionary on another class. /// + [PublicAPI] public interface IByAffectedRelationships : IRelationshipGetters where TRightResource : class, IIdentifiable { /// /// Gets a dictionary of affected resources grouped by affected relationships. /// - Dictionary> AffectedRelationships { get; } + IDictionary> AffectedRelationships { get; } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookContainerProvider.cs similarity index 88% rename from src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs rename to src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookContainerProvider.cs index 56a7f635f9..bde89d870a 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IHookContainerProvider.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution /// A helper class for retrieving meta data about hooks, fetching database values and performing other recurring internal operations. Used internally by /// /// - internal interface IHookExecutorHelper + internal interface IHookContainerProvider { /// /// For a particular ResourceHook and for a given model type, checks if the ResourceHooksDefinition has an implementation for the hook and if so, return @@ -31,7 +31,7 @@ IResourceHookContainer GetResourceHookContainer(ResourceHo /// /// The implicitly affected resources by relationship /// - Dictionary LoadImplicitlyAffected(Dictionary leftResourcesByRelation, + IDictionary LoadImplicitlyAffected(IDictionary leftResourcesByRelation, IEnumerable existingRightResources = null); /// @@ -43,13 +43,10 @@ Dictionary LoadImplicitlyAffected(Dictionary /// /// The set of resources to load the db values for /// - /// - /// The hook in which the db values will be displayed. - /// /// /// Relationships that need to be included on resources. /// - IEnumerable LoadDbValues(Type resourceTypeForRepository, IEnumerable resources, ResourceHook hook, params RelationshipAttribute[] relationships); + IEnumerable LoadDbValues(Type resourceTypeForRepository, IEnumerable resources, params RelationshipAttribute[] relationships); /// /// Checks if the display database values option is allowed for the targeted hook, and for a given resource of type diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs index ab3d80eca9..37e300151c 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/IRelationshipGetters.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using JetBrains.Annotations; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -9,23 +10,26 @@ namespace JsonApiDotNetCore.Hooks.Internal.Execution /// /// A helper class that provides insights in which relationships have been updated for which resources. /// + [PublicAPI] public interface IRelationshipGetters where TLeftResource : class, IIdentifiable { /// /// Gets a dictionary of all resources that have an affected relationship to type /// - Dictionary> GetByRelationship() + IDictionary> GetByRelationship() where TRightResource : class, IIdentifiable; /// /// Gets a dictionary of all resources that have an affected relationship to type /// - Dictionary> GetByRelationship(Type resourceType); + IDictionary> GetByRelationship(Type resourceType); /// /// Gets a collection of all the resources for the property within has been affected by the request /// +#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type HashSet GetAffected(Expression> navigationAction); +#pragma warning restore AV1130 // Return type in method signature should be a collection interface instead of a concrete type } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs index d738a6b162..140b0cdd07 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/RelationshipsDictionary.cs @@ -24,7 +24,7 @@ public class RelationshipsDictionary : Dictionary /// Relationships. /// - public RelationshipsDictionary(Dictionary> relationships) + public RelationshipsDictionary(IDictionary> relationships) : base(relationships) { } @@ -32,22 +32,22 @@ public RelationshipsDictionary(Dictionary /// Used internally by the ResourceHookExecutor to make life a bit easier with generics /// - internal RelationshipsDictionary(Dictionary relationships) - : this(TypeHelper.ConvertRelationshipDictionary(relationships)) + internal RelationshipsDictionary(IDictionary relationships) + : this(relationships.ToDictionary(pair => pair.Key, pair => (HashSet)pair.Value)) { } /// - public Dictionary> GetByRelationship() + public IDictionary> GetByRelationship() where TRelatedResource : class, IIdentifiable { return GetByRelationship(typeof(TRelatedResource)); } /// - public Dictionary> GetByRelationship(Type resourceType) + public IDictionary> GetByRelationship(Type resourceType) { - return this.Where(p => p.Key.RightType == resourceType).ToDictionary(p => p.Key, p => p.Value); + return this.Where(pair => pair.Key.RightType == resourceType).ToDictionary(pair => pair.Key, pair => pair.Value); } /// @@ -55,8 +55,8 @@ public HashSet GetAffected(Expression> naviga { ArgumentGuard.NotNull(navigationAction, nameof(navigationAction)); - PropertyInfo property = TypeHelper.ParseNavigationExpression(navigationAction); - return this.Where(p => p.Key.Property.Name == property.Name).Select(p => p.Value).SingleOrDefault(); + PropertyInfo property = HooksNavigationParser.ParseNavigationExpression(navigationAction); + return this.Where(pair => pair.Key.Property.Name == property.Name).Select(pair => pair.Value).SingleOrDefault(); } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs index 40734b2168..ac6c24bf51 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Execution/ResourceHashSet.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using JsonApiDotNetCore.Resources; @@ -19,9 +20,9 @@ public class ResourceHashSet : HashSet, IResourceHashSet _relationships; /// - public Dictionary> AffectedRelationships => _relationships; + public IDictionary> AffectedRelationships => _relationships; - public ResourceHashSet(HashSet resources, Dictionary> relationships) + public ResourceHashSet(HashSet resources, IDictionary> relationships) : base(resources) { _relationships = new RelationshipsDictionary(relationships); @@ -30,19 +31,19 @@ public ResourceHashSet(HashSet resources, Dictionary /// Used internally by the ResourceHookExecutor to make live a bit easier with generics /// - internal ResourceHashSet(IEnumerable resources, Dictionary relationships) - : this((HashSet)resources, TypeHelper.ConvertRelationshipDictionary(relationships)) + internal ResourceHashSet(IEnumerable resources, IDictionary relationships) + : this((HashSet)resources, relationships.ToDictionary(pair => pair.Key, pair => (HashSet)pair.Value)) { } /// - public Dictionary> GetByRelationship(Type resourceType) + public IDictionary> GetByRelationship(Type resourceType) { return _relationships.GetByRelationship(resourceType); } /// - public Dictionary> GetByRelationship() + public IDictionary> GetByRelationship() where TRightResource : class, IIdentifiable { return GetByRelationship(typeof(TRightResource)); diff --git a/src/JsonApiDotNetCore/Hooks/Internal/HooksCollectionConverter.cs b/src/JsonApiDotNetCore/Hooks/Internal/HooksCollectionConverter.cs new file mode 100644 index 0000000000..8ccf97ad21 --- /dev/null +++ b/src/JsonApiDotNetCore/Hooks/Internal/HooksCollectionConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace JsonApiDotNetCore.Hooks.Internal +{ + internal sealed class HooksCollectionConverter : CollectionConverter + { + public IList CopyToList(IEnumerable elements, Type elementType) + { + Type collectionType = typeof(List<>).MakeGenericType(elementType); + return (IList)CopyToTypedCollection(elements, collectionType); + } + + public IEnumerable CopyToHashSet(IEnumerable elements, Type elementType) + { + Type collectionType = typeof(HashSet<>).MakeGenericType(elementType); + return CopyToTypedCollection(elements, collectionType); + } + } +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/HooksNavigationParser.cs b/src/JsonApiDotNetCore/Hooks/Internal/HooksNavigationParser.cs new file mode 100644 index 0000000000..30f4c34e1c --- /dev/null +++ b/src/JsonApiDotNetCore/Hooks/Internal/HooksNavigationParser.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +#pragma warning disable AV1008 // Class should not be static + +namespace JsonApiDotNetCore.Hooks.Internal +{ + internal static class HooksNavigationParser + { + /// + /// Gets the property info that is referenced in the NavigationAction expression. Credits: https://stackoverflow.com/a/17116267/4441216 + /// + public static PropertyInfo ParseNavigationExpression(Expression> navigationExpression) + { + ArgumentGuard.NotNull(navigationExpression, nameof(navigationExpression)); + + MemberExpression exp; + + // this line is necessary, because sometimes the expression comes in as Convert(originalExpression) + if (navigationExpression.Body is UnaryExpression unaryExpression) + { + if (unaryExpression.Operand is MemberExpression memberExpression) + { + exp = memberExpression; + } + else + { + throw new ArgumentException("Invalid expression.", nameof(navigationExpression)); + } + } + else if (navigationExpression.Body is MemberExpression memberExpression) + { + exp = memberExpression; + } + else + { + throw new ArgumentException("Invalid expression.", nameof(navigationExpression)); + } + + return (PropertyInfo)exp.Member; + } + } +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/HooksObjectFactory.cs b/src/JsonApiDotNetCore/Hooks/Internal/HooksObjectFactory.cs new file mode 100644 index 0000000000..d333d00514 --- /dev/null +++ b/src/JsonApiDotNetCore/Hooks/Internal/HooksObjectFactory.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection; +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCore.Hooks.Internal +{ + internal sealed class HooksObjectFactory + { + /// + /// Creates an instance of the specified generic type + /// + /// + /// The instance of the parameterized generic type + /// + /// + /// Generic type parameter to be used in open type. + /// + /// + /// Constructor arguments to be provided in instantiation. + /// + /// + /// Open generic type + /// + public object CreateInstanceOfOpenType(Type openType, Type parameter, params object[] constructorArguments) + { + return CreateInstanceOfOpenType(openType, parameter.AsArray(), constructorArguments); + } + + /// + /// Use this overload if you need to instantiate a type that has an internal constructor + /// + public object CreateInstanceOfInternalOpenType(Type openType, Type parameter, params object[] constructorArguments) + { + Type[] parameters = + { + parameter + }; + + Type closedType = openType.MakeGenericType(parameters); + return Activator.CreateInstance(closedType, BindingFlags.NonPublic | BindingFlags.Instance, null, constructorArguments, null); + } + + /// + /// Creates an instance of the specified generic type + /// + /// + /// The instance of the parameterized generic type + /// + /// + /// Generic type parameters to be used in open type. + /// + /// + /// Constructor arguments to be provided in instantiation. + /// + /// + /// Open generic type + /// + private object CreateInstanceOfOpenType(Type openType, Type[] parameters, params object[] constructorArguments) + { + Type closedType = openType.MakeGenericType(parameters); + return Activator.CreateInstance(closedType, constructorArguments); + } + + /// + /// Gets the type (such as Guid or int) of the Id property on a type that implements . + /// + public Type GetIdType(Type resourceType) + { + PropertyInfo property = resourceType.GetProperty(nameof(Identifiable.Id)); + + if (property == null) + { + throw new ArgumentException($"Type '{resourceType.Name}' does not have 'Id' property."); + } + + return property.PropertyType; + } + } +} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs index e3a62558f3..b3baceaf88 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/IResourceHookExecutor.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Hooks.Internal /// /// Transient service responsible for executing Resource Hooks as defined in . See methods in /// , and for more information. Uses - /// for traversal of nested resource data structures. Uses for retrieving meta data + /// for traversal of nested resource data structures. Uses for retrieving meta data /// about hooks, fetching database values and performing other recurring internal operations. /// public interface IResourceHookExecutor : IReadHookExecutor, IUpdateHookExecutor, ICreateHookExecutor, IDeleteHookExecutor, IOnReturnHookExecutor diff --git a/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs index dcb3517027..e8f51e551d 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/ResourceHookExecutor.cs @@ -21,17 +21,21 @@ namespace JsonApiDotNetCore.Hooks.Internal /// internal sealed class ResourceHookExecutor : IResourceHookExecutor { - private readonly IHookExecutorHelper _executorHelper; - private readonly ITraversalHelper _traversalHelper; + private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter(); + private static readonly HooksObjectFactory ObjectFactory = new HooksObjectFactory(); + private static readonly HooksCollectionConverter CollectionConverter = new HooksCollectionConverter(); + + private readonly IHookContainerProvider _containerProvider; + private readonly INodeNavigator _nodeNavigator; private readonly IEnumerable _constraintProviders; private readonly ITargetedFields _targetedFields; private readonly IResourceGraph _resourceGraph; - public ResourceHookExecutor(IHookExecutorHelper executorHelper, ITraversalHelper traversalHelper, ITargetedFields targetedFields, + public ResourceHookExecutor(IHookContainerProvider containerProvider, INodeNavigator nodeNavigator, ITargetedFields targetedFields, IEnumerable constraintProviders, IResourceGraph resourceGraph) { - _executorHelper = executorHelper; - _traversalHelper = traversalHelper; + _containerProvider = containerProvider; + _nodeNavigator = nodeNavigator; _targetedFields = targetedFields; _constraintProviders = constraintProviders; _resourceGraph = resourceGraph; @@ -41,7 +45,7 @@ public ResourceHookExecutor(IHookExecutorHelper executorHelper, ITraversalHelper public void BeforeRead(ResourcePipeline pipeline, string stringId = null) where TResource : class, IIdentifiable { - IResourceHookContainer hookContainer = _executorHelper.GetResourceHookContainer(ResourceHook.BeforeRead); + IResourceHookContainer hookContainer = _containerProvider.GetResourceHookContainer(ResourceHook.BeforeRead); hookContainer?.BeforeRead(pipeline, false, stringId); List calledContainers = typeof(TResource).AsList(); @@ -67,17 +71,22 @@ public void BeforeRead(ResourcePipeline pipeline, string stringId = n public IEnumerable BeforeUpdate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.BeforeUpdate, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.BeforeUpdate, resources); + + if (result.Succeeded) { - RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); - IEnumerable dbValues = LoadDbValues(typeof(TResource), (IEnumerable)node.UniqueResources, ResourceHook.BeforeUpdate, relationships); - var diff = new DiffableResourceHashSet(node.UniqueResources, dbValues, node.LeftsToNextLayer(), _targetedFields); - IEnumerable updated = container.BeforeUpdate(diff, pipeline); - node.UpdateUnique(updated); - node.Reassign(resources); + RelationshipAttribute[] relationships = result.Node.RelationshipsToNextLayer.Select(proxy => proxy.Attribute).ToArray(); + + IEnumerable dbValues = LoadDbValues(typeof(TResource), (IEnumerable)result.Node.UniqueResources, ResourceHook.BeforeUpdate, + relationships); + + var diff = new DiffableResourceHashSet(result.Node.UniqueResources, dbValues, result.Node.LeftsToNextLayer(), _targetedFields); + IEnumerable updated = result.Container.BeforeUpdate(diff, pipeline); + result.Node.UpdateUnique(updated); + result.Node.Reassign(resources); } - FireNestedBeforeUpdateHooks(pipeline, _traversalHelper.CreateNextLayer(node)); + FireNestedBeforeUpdateHooks(pipeline, _nodeNavigator.CreateNextLayer(result.Node)); return resources; } @@ -85,15 +94,17 @@ public IEnumerable BeforeUpdate(IEnumerable res public IEnumerable BeforeCreate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.BeforeCreate, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.BeforeCreate, resources); + + if (result.Succeeded) { - var affected = new ResourceHashSet((HashSet)node.UniqueResources, node.LeftsToNextLayer()); - IEnumerable updated = container.BeforeCreate(affected, pipeline); - node.UpdateUnique(updated); - node.Reassign(resources); + var affected = new ResourceHashSet((HashSet)result.Node.UniqueResources, result.Node.LeftsToNextLayer()); + IEnumerable updated = result.Container.BeforeCreate(affected, pipeline); + result.Node.UpdateUnique(updated); + result.Node.Reassign(resources); } - FireNestedBeforeUpdateHooks(pipeline, _traversalHelper.CreateNextLayer(node)); + FireNestedBeforeUpdateHooks(pipeline, _nodeNavigator.CreateNextLayer(result.Node)); return resources; } @@ -101,26 +112,28 @@ public IEnumerable BeforeCreate(IEnumerable res public IEnumerable BeforeDelete(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.BeforeDelete, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.BeforeDelete, resources); + + if (result.Succeeded) { - RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); + RelationshipAttribute[] relationships = result.Node.RelationshipsToNextLayer.Select(proxy => proxy.Attribute).ToArray(); IEnumerable targetResources = - LoadDbValues(typeof(TResource), (IEnumerable)node.UniqueResources, ResourceHook.BeforeDelete, relationships) ?? - node.UniqueResources; + LoadDbValues(typeof(TResource), (IEnumerable)result.Node.UniqueResources, ResourceHook.BeforeDelete, relationships) ?? + result.Node.UniqueResources; - var affected = new ResourceHashSet(targetResources, node.LeftsToNextLayer()); + var affected = new ResourceHashSet(targetResources, result.Node.LeftsToNextLayer()); - IEnumerable updated = container.BeforeDelete(affected, pipeline); - node.UpdateUnique(updated); - node.Reassign(resources); + IEnumerable updated = result.Container.BeforeDelete(affected, pipeline); + result.Node.UpdateUnique(updated); + result.Node.Reassign(resources); } // If we're deleting an article, we're implicitly affected any owners related to it. // Here we're loading all relations onto the to-be-deleted article // if for that relation the BeforeImplicitUpdateHook is implemented, // and this hook is then executed - foreach (KeyValuePair> entry in node.LeftsToNextLayerByRelationships()) + foreach (KeyValuePair> entry in result.Node.LeftsToNextLayerByRelationships()) { Type rightType = entry.Key; Dictionary implicitTargets = entry.Value; @@ -134,15 +147,17 @@ public IEnumerable BeforeDelete(IEnumerable res public IEnumerable OnReturn(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.OnReturn, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.OnReturn, resources); + + if (result.Succeeded) { - IEnumerable updated = container.OnReturn((HashSet)node.UniqueResources, pipeline); + IEnumerable updated = result.Container.OnReturn((HashSet)result.Node.UniqueResources, pipeline); ValidateHookResponse(updated); - node.UpdateUnique(updated); - node.Reassign(resources); + result.Node.UpdateUnique(updated); + result.Node.Reassign(resources); } - Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.OnReturn, (nextContainer, nextNode) => + TraverseNodesInLayer(_nodeNavigator.CreateNextLayer(result.Node), ResourceHook.OnReturn, (nextContainer, nextNode) => { IEnumerable filteredUniqueSet = CallHook(nextContainer, ResourceHook.OnReturn, ArrayFactory.Create(nextNode.UniqueResources, pipeline)); nextNode.UpdateUnique(filteredUniqueSet); @@ -156,12 +171,14 @@ public IEnumerable OnReturn(IEnumerable resourc public void AfterRead(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterRead, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.AfterRead, resources); + + if (result.Succeeded) { - container.AfterRead((HashSet)node.UniqueResources, pipeline); + result.Container.AfterRead((HashSet)result.Node.UniqueResources, pipeline); } - Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.AfterRead, (nextContainer, nextNode) => + TraverseNodesInLayer(_nodeNavigator.CreateNextLayer(result.Node), ResourceHook.AfterRead, (nextContainer, nextNode) => { CallHook(nextContainer, ResourceHook.AfterRead, ArrayFactory.Create(nextNode.UniqueResources, pipeline, true)); }); @@ -171,12 +188,14 @@ public void AfterRead(IEnumerable resources, ResourcePipel public void AfterCreate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterCreate, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.AfterCreate, resources); + + if (result.Succeeded) { - container.AfterCreate((HashSet)node.UniqueResources, pipeline); + result.Container.AfterCreate((HashSet)result.Node.UniqueResources, pipeline); } - Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.AfterUpdateRelationship, + TraverseNodesInLayer(_nodeNavigator.CreateNextLayer(result.Node), ResourceHook.AfterUpdateRelationship, (nextContainer, nextNode) => FireAfterUpdateRelationship(nextContainer, nextNode, pipeline)); } @@ -184,12 +203,14 @@ public void AfterCreate(IEnumerable resources, ResourcePip public void AfterUpdate(IEnumerable resources, ResourcePipeline pipeline) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterUpdate, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.AfterUpdate, resources); + + if (result.Succeeded) { - container.AfterUpdate((HashSet)node.UniqueResources, pipeline); + result.Container.AfterUpdate((HashSet)result.Node.UniqueResources, pipeline); } - Traverse(_traversalHelper.CreateNextLayer(node), ResourceHook.AfterUpdateRelationship, + TraverseNodesInLayer(_nodeNavigator.CreateNextLayer(result.Node), ResourceHook.AfterUpdateRelationship, (nextContainer, nextNode) => FireAfterUpdateRelationship(nextContainer, nextNode, pipeline)); } @@ -197,9 +218,11 @@ public void AfterUpdate(IEnumerable resources, ResourcePip public void AfterDelete(IEnumerable resources, ResourcePipeline pipeline, bool succeeded) where TResource : class, IIdentifiable { - if (GetHook(ResourceHook.AfterDelete, resources, out IResourceHookContainer container, out RootNode node)) + GetHookResult result = GetHook(ResourceHook.AfterDelete, resources); + + if (result.Succeeded) { - container.AfterDelete((HashSet)node.UniqueResources, pipeline, succeeded); + result.Container.AfterDelete((HashSet)result.Node.UniqueResources, pipeline, succeeded); } } @@ -212,40 +235,42 @@ public void AfterDelete(IEnumerable resources, ResourcePip /// /// true, if hook was implemented, false otherwise. /// - private bool GetHook(ResourceHook target, IEnumerable resources, out IResourceHookContainer container, - out RootNode node) + private GetHookResult GetHook(ResourceHook target, IEnumerable resources) where TResource : class, IIdentifiable { - node = _traversalHelper.CreateRootNode(resources); - container = _executorHelper.GetResourceHookContainer(target); - return container != null; + RootNode node = _nodeNavigator.CreateRootNode(resources); + IResourceHookContainer container = _containerProvider.GetResourceHookContainer(target); + + return new GetHookResult(container, node); } - /// - /// Traverses the nodes in a . - /// - private void Traverse(NodeLayer currentLayer, ResourceHook target, Action action) + private void TraverseNodesInLayer(IEnumerable currentLayer, ResourceHook target, Action action) { - NodeLayer nextLayer = currentLayer; + IEnumerable nextLayer = currentLayer; while (true) { - if (!nextLayer.AnyResources()) + if (!HasResources(nextLayer)) { return; } TraverseNextLayer(nextLayer, action, target); - nextLayer = _traversalHelper.CreateNextLayer(nextLayer.ToList()); + nextLayer = _nodeNavigator.CreateNextLayer(nextLayer.ToList()); } } - private void TraverseNextLayer(NodeLayer nextLayer, Action action, ResourceHook target) + private static bool HasResources(IEnumerable layer) + { + return layer.Any(node => node.UniqueResources.Cast().Any()); + } + + private void TraverseNextLayer(IEnumerable nextLayer, Action action, ResourceHook target) { foreach (IResourceNode node in nextLayer) { - IResourceHookContainer hookContainer = _executorHelper.GetResourceHookContainer(node.ResourceType, target); + IResourceHookContainer hookContainer = _containerProvider.GetResourceHookContainer(node.ResourceType, target); if (hookContainer != null) { @@ -267,7 +292,7 @@ private void RecursiveBeforeRead(List relationshipChain, if (!calledContainers.Contains(relationship.RightType)) { calledContainers.Add(relationship.RightType); - IResourceHookContainer container = _executorHelper.GetResourceHookContainer(relationship.RightType, ResourceHook.BeforeRead); + IResourceHookContainer container = _containerProvider.GetResourceHookContainer(relationship.RightType, ResourceHook.BeforeRead); if (container != null) { @@ -297,31 +322,33 @@ private void RecursiveBeforeRead(List relationshipChain, /// already related to article2. Then, the following nested hooks need to be fired in the following order. First the BeforeUpdateRelationship should be /// for owner1, then the BeforeImplicitUpdateRelationship hook should be fired for owner2, and lastly the BeforeImplicitUpdateRelationship for article2. /// - private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer layer) + private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, IEnumerable layer) { foreach (IResourceNode node in layer) { - IResourceHookContainer nestedHookContainer = _executorHelper.GetResourceHookContainer(node.ResourceType, ResourceHook.BeforeUpdateRelationship); + IResourceHookContainer nestedHookContainer = + _containerProvider.GetResourceHookContainer(node.ResourceType, ResourceHook.BeforeUpdateRelationship); + IEnumerable uniqueResources = node.UniqueResources; RightType resourceType = node.ResourceType; - Dictionary currentResourcesGrouped; - Dictionary currentResourcesGroupedInverse; + IDictionary currentResourcesGrouped; + IDictionary currentResourcesGroupedInverse; // fire the BeforeUpdateRelationship hook for owner_new if (nestedHookContainer != null) { if (uniqueResources.Cast().Any()) { - RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); + RelationshipAttribute[] relationships = node.RelationshipsToNextLayer.Select(proxy => proxy.Attribute).ToArray(); IEnumerable dbValues = LoadDbValues(resourceType, uniqueResources, ResourceHook.BeforeUpdateRelationship, relationships); - // these are the resources of the current node grouped by + // these are the resources of the current node grouped by // RelationshipAttributes that occurred in the previous layer // so it looks like { HasOneAttribute:owner => owner_new }. - // Note that in the BeforeUpdateRelationship hook of Person, + // Note that in the BeforeUpdateRelationship hook of Person, // we want want inverse relationship attribute: // we now have the one pointing from article -> person, ] - // but we require the the one that points from person -> article + // but we require the the one that points from person -> article currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); currentResourcesGroupedInverse = ReplaceKeysWithInverseRelationships(currentResourcesGrouped); @@ -330,7 +357,7 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la IEnumerable allowedIds = CallHook(nestedHookContainer, ResourceHook.BeforeUpdateRelationship, ArrayFactory.Create(GetIds(uniqueResources), resourcesByRelationship, pipeline)).Cast(); - HashSet updated = GetAllowedResources(uniqueResources, allowedIds); + ISet updated = GetAllowedResources(uniqueResources, allowedIds); node.UpdateUnique(updated); node.Reassign(); } @@ -342,10 +369,10 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la if (pipeline != ResourcePipeline.Post) { // To fire a hook for owner_old, we need to first get a reference to it. - // For this, we need to query the database for the HasOneAttribute:owner - // relationship of article1, which is referred to as the + // For this, we need to query the database for the HasOneAttribute:owner + // relationship of article1, which is referred to as the // left side of the HasOneAttribute:owner relationship. - Dictionary leftResources = node.RelationshipsFromPreviousLayer.GetLeftResources(); + IDictionary leftResources = node.RelationshipsFromPreviousLayer.GetLeftResources(); if (leftResources.Any()) { @@ -355,21 +382,21 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la } // Fire the BeforeImplicitUpdateRelationship hook for article2 - // For this, we need to query the database for the current owner + // For this, we need to query the database for the current owner // relationship value of owner_new. currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); if (currentResourcesGrouped.Any()) { - // rightResources is grouped by relationships from previous - // layer, ie { HasOneAttribute:owner => owner_new }. But - // to load article2 onto owner_new, we need to have the + // rightResources is grouped by relationships from previous + // layer, ie { HasOneAttribute:owner => owner_new }. But + // to load article2 onto owner_new, we need to have the // RelationshipAttribute from owner to article, which is the // inverse of HasOneAttribute:owner currentResourcesGroupedInverse = ReplaceKeysWithInverseRelationships(currentResourcesGrouped); - // Note that currently in the JsonApiDotNetCore implementation of hooks, - // the root layer is ALWAYS homogenous, so we safely assume - // that for every relationship to the previous layer, the + // Note that currently in the JsonApiDotNetCore implementation of hooks, + // the root layer is ALWAYS homogenous, so we safely assume + // that for every relationship to the previous layer, the // left type is the same. LeftType leftType = currentResourcesGrouped.First().Key.LeftType; FireForAffectedImplicits(leftType, currentResourcesGroupedInverse, pipeline); @@ -383,34 +410,35 @@ private void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer la /// /// Resources grouped by relationship attribute /// - private Dictionary ReplaceKeysWithInverseRelationships( - Dictionary resourcesByRelationship) + private IDictionary ReplaceKeysWithInverseRelationships( + IDictionary resourcesByRelationship) { // when Article has one Owner (HasOneAttribute:owner) is set, there is no guarantee // that the inverse attribute was also set (Owner has one Article: HasOneAttr:article). - // If it isn't, JsonApiDotNetCore currently knows nothing about this relationship pointing back, and it + // If it isn't, JsonApiDotNetCore currently knows nothing about this relationship pointing back, and it // currently cannot fire hooks for resources resolved through inverse relationships. IEnumerable> inversableRelationshipAttributes = - resourcesByRelationship.Where(kvp => kvp.Key.InverseNavigationProperty != null); + resourcesByRelationship.Where(pair => pair.Key.InverseNavigationProperty != null); - return inversableRelationshipAttributes.ToDictionary(kvp => _resourceGraph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); + return inversableRelationshipAttributes.ToDictionary(pair => _resourceGraph.GetInverseRelationship(pair.Key), pair => pair.Value); } /// /// Given a source of resources, gets the implicitly affected resources from the database and calls the BeforeImplicitUpdateRelationship hook. /// - private void FireForAffectedImplicits(Type resourceTypeToInclude, Dictionary implicitsTarget, + private void FireForAffectedImplicits(Type resourceTypeToInclude, IDictionary implicitsTarget, ResourcePipeline pipeline, IEnumerable existingImplicitResources = null) { - IResourceHookContainer container = _executorHelper.GetResourceHookContainer(resourceTypeToInclude, ResourceHook.BeforeImplicitUpdateRelationship); + IResourceHookContainer container = + _containerProvider.GetResourceHookContainer(resourceTypeToInclude, ResourceHook.BeforeImplicitUpdateRelationship); if (container == null) { return; } - Dictionary - implicitAffected = _executorHelper.LoadImplicitlyAffected(implicitsTarget, existingImplicitResources); + IDictionary implicitAffected = + _containerProvider.LoadImplicitlyAffected(implicitsTarget, existingImplicitResources); if (!implicitAffected.Any()) { @@ -418,7 +446,7 @@ private void FireForAffectedImplicits(Type resourceTypeToInclude, Dictionary inverse = - implicitAffected.ToDictionary(kvp => _resourceGraph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); + implicitAffected.ToDictionary(pair => _resourceGraph.GetInverseRelationship(pair.Key), pair => pair.Value); IRelationshipsDictionary resourcesByRelationship = CreateRelationshipHelper(resourceTypeToInclude, inverse); CallHook(container, ResourceHook.BeforeImplicitUpdateRelationship, ArrayFactory.Create(resourcesByRelationship, pipeline)); @@ -449,7 +477,7 @@ private void ValidateHookResponse(IEnumerable returnedList, ResourcePipeli private IEnumerable CallHook(IResourceHookContainer container, ResourceHook hook, object[] arguments) { MethodInfo method = container.GetType().GetMethod(hook.ToString("G")); - // note that some of the hooks return "void". When these hooks, the + // note that some of the hooks return "void". When these hooks, the // are called reflectively with Invoke like here, the return value // is just null, so we don't have to worry about casting issues here. return (IEnumerable)ThrowJsonApiExceptionOnError(() => method?.Invoke(container, arguments)); @@ -477,24 +505,24 @@ private object ThrowJsonApiExceptionOnError(Func action) /// /// The relationship helper. /// - private IRelationshipsDictionary CreateRelationshipHelper(RightType resourceType, Dictionary prevLayerRelationships, - IEnumerable dbValues = null) + private IRelationshipsDictionary CreateRelationshipHelper(RightType resourceType, + IDictionary prevLayerRelationships, IEnumerable dbValues = null) { - Dictionary prevLayerRelationshipsWithDbValues = prevLayerRelationships; + IDictionary prevLayerRelationshipsWithDbValues = prevLayerRelationships; if (dbValues != null) { prevLayerRelationshipsWithDbValues = ReplaceWithDbValues(prevLayerRelationshipsWithDbValues, dbValues.Cast()); } - return (IRelationshipsDictionary)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipsDictionary<>), resourceType, true, + return (IRelationshipsDictionary)ObjectFactory.CreateInstanceOfInternalOpenType(typeof(RelationshipsDictionary<>), resourceType, prevLayerRelationshipsWithDbValues); } /// /// Replaces the resources in the values of the prevLayerRelationships dictionary with the corresponding resources loaded from the db. /// - private Dictionary ReplaceWithDbValues(Dictionary prevLayerRelationships, + private IDictionary ReplaceWithDbValues(IDictionary prevLayerRelationships, IEnumerable dbValues) { foreach (RelationshipAttribute key in prevLayerRelationships.Keys.ToList()) @@ -502,8 +530,7 @@ private Dictionary ReplaceWithDbValues(Dicti IEnumerable source = prevLayerRelationships[key].Cast().Select(resource => dbValues.Single(dbResource => dbResource.StringId == resource.StringId)); - IList replaced = TypeHelper.CopyToList(source, key.LeftType); - prevLayerRelationships[key] = TypeHelper.CreateHashSetFor(key.LeftType, replaced); + prevLayerRelationships[key] = CollectionConverter.CopyToHashSet(source, key.LeftType); } return prevLayerRelationships; @@ -512,7 +539,7 @@ private Dictionary ReplaceWithDbValues(Dicti /// /// Filter the source set by removing the resources with ID that are not in . /// - private HashSet GetAllowedResources(IEnumerable source, IEnumerable allowedIds) + private ISet GetAllowedResources(IEnumerable source, IEnumerable allowedIds) { return new HashSet(source.Cast().Where(ue => allowedIds.Contains(ue.StringId))); } @@ -541,12 +568,12 @@ private IEnumerable LoadDbValues(Type resourceType, IEnumerable uniqueResources, { // We only need to load database values if the target hook of this hook execution // cycle is compatible with displaying database values and has this option enabled. - if (!_executorHelper.ShouldLoadDbValues(resourceType, targetHook)) + if (!_containerProvider.ShouldLoadDbValues(resourceType, targetHook)) { return null; } - return _executorHelper.LoadDbValues(resourceType, uniqueResources, targetHook, relationshipsToNextLayer); + return _containerProvider.LoadDbValues(resourceType, uniqueResources, relationshipsToNextLayer); } /// @@ -554,9 +581,9 @@ private IEnumerable LoadDbValues(Type resourceType, IEnumerable uniqueResources, /// private void FireAfterUpdateRelationship(IResourceHookContainer container, IResourceNode node, ResourcePipeline pipeline) { - Dictionary currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); + IDictionary currentResourcesGrouped = node.RelationshipsFromPreviousLayer.GetRightResources(); - // the relationships attributes in currentResourcesGrouped will be pointing from a + // the relationships attributes in currentResourcesGrouped will be pointing from a // resource in the previous layer to a resource in the current (nested) layer. // For the nested hook we need to replace these attributes with their inverse. // See the FireNestedBeforeUpdateHooks method for a more detailed example. @@ -573,9 +600,24 @@ private void FireAfterUpdateRelationship(IResourceHookContainer container, IReso /// /// IIdentifiable resources. /// - private HashSet GetIds(IEnumerable resources) + private ISet GetIds(IEnumerable resources) { - return new HashSet(resources.Cast().Select(e => e.StringId)); + return new HashSet(resources.Cast().Select(resource => resource.StringId)); + } + + private sealed class GetHookResult + where TResource : class, IIdentifiable + { + public IResourceHookContainer Container { get; } + public RootNode Node { get; } + + public bool Succeeded => Container != null; + + public GetHookResult(IResourceHookContainer container, RootNode node) + { + Container = container; + Node = node; + } } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs index 7d9bbbe8a2..7bf2209118 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ChildNode.cs @@ -6,11 +6,16 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { + internal abstract class ChildNode + { + protected static readonly CollectionConverter CollectionConverter = new CollectionConverter(); + } + /// /// Child node in the tree /// /// - internal sealed class ChildNode : IResourceNode + internal sealed class ChildNode : ChildNode, IResourceNode where TResource : class, IIdentifiable { private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; @@ -20,7 +25,7 @@ internal sealed class ChildNode : IResourceNode public RightType ResourceType { get; } /// - public RelationshipProxy[] RelationshipsToNextLayer { get; } + public IReadOnlyCollection RelationshipsToNextLayer { get; } /// public IEnumerable UniqueResources @@ -34,7 +39,7 @@ public IEnumerable UniqueResources /// public IRelationshipsFromPreviousLayer RelationshipsFromPreviousLayer => _relationshipsFromPreviousLayer; - public ChildNode(RelationshipProxy[] nextLayerRelationships, RelationshipsFromPreviousLayer prevLayerRelationships) + public ChildNode(IReadOnlyCollection nextLayerRelationships, RelationshipsFromPreviousLayer prevLayerRelationships) { ResourceType = typeof(TResource); RelationshipsToNextLayer = nextLayerRelationships; @@ -44,11 +49,11 @@ public ChildNode(RelationshipProxy[] nextLayerRelationships, RelationshipsFromPr /// public void UpdateUnique(IEnumerable updated) { - List cast = updated.Cast().ToList(); + List list = updated.Cast().ToList(); foreach (RelationshipGroup group in _relationshipsFromPreviousLayer) { - group.RightResources = new HashSet(group.RightResources.Intersect(cast, _comparer).Cast()); + group.RightResources = new HashSet(group.RightResources.Intersect(list, _comparer).Cast()); } } @@ -77,7 +82,7 @@ private void Reassign(IEnumerable leftResources, RelationshipProx if (currentValue is IEnumerable relationshipCollection) { IEnumerable intersection = relationshipCollection.Intersect(unique, _comparer); - IEnumerable typedCollection = TypeHelper.CopyToTypedCollection(intersection, relationshipCollection.GetType()); + IEnumerable typedCollection = CollectionConverter.CopyToTypedCollection(intersection, relationshipCollection.GetType()); proxy.SetValue(left, typedCollection); } else if (currentValue is IIdentifiable relationshipSingle) diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/INodeNavigator.cs similarity index 81% rename from src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs rename to src/JsonApiDotNetCore/Hooks/Internal/Traversal/INodeNavigator.cs index a4db2e83f3..1b01751736 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/ITraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/INodeNavigator.cs @@ -3,17 +3,17 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal { - internal interface ITraversalHelper + internal interface INodeNavigator { /// - /// Crates the next layer + /// Creates the next layer /// - NodeLayer CreateNextLayer(IResourceNode node); + IEnumerable CreateNextLayer(IResourceNode node); /// /// Creates the next layer based on the nodes provided /// - NodeLayer CreateNextLayer(IEnumerable nodes); + IEnumerable CreateNextLayer(IEnumerable nodes); /// /// Creates a root node for breadth-first-traversal (BFS). Note that typically, in JsonApiDotNetCore, the root layer will be homogeneous. Also, because diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs index 54d93c7905..3dc9c9a8fa 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipGroup.cs @@ -1,11 +1,6 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Resources; - namespace JsonApiDotNetCore.Hooks.Internal.Traversal { internal interface IRelationshipGroup { - RelationshipProxy Proxy { get; } - HashSet LeftResources { get; } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs index 98886e1b10..ff8795aaa6 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IRelationshipsFromPreviousLayer.cs @@ -15,7 +15,7 @@ internal interface IRelationshipsFromPreviousLayer /// /// The right side resources. /// - Dictionary GetRightResources(); + IDictionary GetRightResources(); /// /// Grouped by relationship to the previous layer, gets all the resources of the previous layer @@ -23,6 +23,6 @@ internal interface IRelationshipsFromPreviousLayer /// /// The right side resources. /// - Dictionary GetLeftResources(); + IDictionary GetLeftResources(); } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs index dcd350666c..d86fb05431 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/IResourceNode.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using RightType = System.Type; namespace JsonApiDotNetCore.Hooks.Internal.Traversal @@ -24,7 +25,7 @@ internal interface IResourceNode /// /// The relationships to next layer. /// - RelationshipProxy[] RelationshipsToNextLayer { get; } + IReadOnlyCollection RelationshipsToNextLayer { get; } /// /// Relationships to the previous layer diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs deleted file mode 100644 index bad0938a20..0000000000 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeLayer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Resources; - -namespace JsonApiDotNetCore.Hooks.Internal.Traversal -{ - /// - /// A helper class that represents all resources in the current layer that are being traversed for which hooks will be executed (see - /// IResourceHookExecutor) - /// - internal sealed class NodeLayer : IEnumerable - { - private readonly List _collection; - - public NodeLayer(List nodes) - { - _collection = nodes; - } - - public bool AnyResources() - { - return _collection.Any(n => n.UniqueResources.Cast().Any()); - } - - public IEnumerator GetEnumerator() - { - return _collection.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeNavigator.cs similarity index 79% rename from src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs rename to src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeNavigator.cs index 5e6451b9c1..ac4435ac7e 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/NodeNavigator.cs @@ -15,8 +15,11 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal /// breadth-first-traversal It creates nodes for each layer. Typically, the first layer is homogeneous (all resources have the same type), and further /// nodes can be mixed. /// - internal sealed class TraversalHelper : ITraversalHelper + internal sealed class NodeNavigator : INodeNavigator { + private static readonly HooksObjectFactory ObjectFactory = new HooksObjectFactory(); + private static readonly HooksCollectionConverter CollectionConverter = new HooksCollectionConverter(); + private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; private readonly IResourceGraph _resourceGraph; private readonly ITargetedFields _targetedFields; @@ -31,7 +34,7 @@ internal sealed class TraversalHelper : ITraversalHelper /// private Dictionary> _processedResources; - public TraversalHelper(IResourceGraph resourceGraph, ITargetedFields targetedFields) + public NodeNavigator(IResourceGraph resourceGraph, ITargetedFields targetedFields) { _targetedFields = targetedFields; _resourceGraph = resourceGraph; @@ -55,10 +58,10 @@ public RootNode CreateRootNode(IEnumerable root { _processedResources = new Dictionary>(); RegisterRelationshipProxies(typeof(TResource)); - HashSet uniqueResources = ProcessResources(rootResources); - RelationshipProxy[] populatedRelationshipsToNextLayer = GetPopulatedRelationships(typeof(TResource), uniqueResources); + ISet uniqueResources = ProcessResources(rootResources); + IReadOnlyCollection populatedRelationshipsToNextLayer = GetPopulatedRelationships(typeof(TResource), uniqueResources); - RelationshipProxy[] allRelationshipsFromType = + IReadOnlyCollection allRelationshipsFromType = _relationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.LeftType == typeof(TResource)).ToArray(); return new RootNode(uniqueResources, populatedRelationshipsToNextLayer, allRelationshipsFromType); @@ -73,7 +76,7 @@ public RootNode CreateRootNode(IEnumerable root /// /// Root node. /// - public NodeLayer CreateNextLayer(IResourceNode rootNode) + public IEnumerable CreateNextLayer(IResourceNode rootNode) { return CreateNextLayer(rootNode.AsEnumerable()); } @@ -85,7 +88,7 @@ public NodeLayer CreateNextLayer(IResourceNode rootNode) /// The next layer. /// /// Nodes. - public NodeLayer CreateNextLayer(IEnumerable nodes) + public IEnumerable CreateNextLayer(IEnumerable nodes) { // first extract resources by parsing populated relationships in the resources // of previous layer @@ -93,8 +96,8 @@ public NodeLayer CreateNextLayer(IEnumerable nodes) // group them conveniently so we can make ChildNodes of them: // there might be several relationship attributes in rights dictionary - // that point to the same right type. - Dictionary>>> leftsGrouped = GroupByRightTypeOfRelationship(lefts); + // that point to the same right type. + IDictionary>>> leftsGrouped = GroupByRightTypeOfRelationship(lefts); // convert the groups into child nodes List nextNodes = leftsGrouped.Select(entry => @@ -114,17 +117,17 @@ public NodeLayer CreateNextLayer(IEnumerable nodes) return CreateNodeInstance(nextNodeType, populatedRelationships.ToArray(), relationshipsToPreviousLayer); }).ToList(); - return new NodeLayer(nextNodes); + return nextNodes; } /// /// iterates through the dictionary and groups the values by matching right type of the keys (which are relationship /// attributes) /// - private Dictionary>>> GroupByRightTypeOfRelationship( + private IDictionary>>> GroupByRightTypeOfRelationship( Dictionary> relationships) { - return relationships.GroupBy(kvp => kvp.Key.RightType).ToDictionary(gdc => gdc.Key, gdc => gdc.ToList()); + return relationships.GroupBy(pair => pair.Key.RightType).ToDictionary(grouping => grouping.Key, grouping => grouping.ToList()); } /// @@ -142,24 +145,24 @@ private Dictionary relationships = node.RelationshipsToNextLayer; ExtractLeftResources(leftResources, relationships, rightResourcesGrouped, leftResourcesGrouped); } MethodInfo processResourcesMethod = GetType().GetMethod(nameof(ProcessResources), BindingFlags.NonPublic | BindingFlags.Instance); - foreach (KeyValuePair> kvp in rightResourcesGrouped) + foreach (KeyValuePair> pair in rightResourcesGrouped) { - RightType type = kvp.Key.RightType; - IList list = TypeHelper.CopyToList(kvp.Value, type); + RightType type = pair.Key.RightType; + IList list = CollectionConverter.CopyToList(pair.Value, type); processResourcesMethod!.MakeGenericMethod(type).Invoke(this, ArrayFactory.Create(list)); } return (leftResourcesGrouped, rightResourcesGrouped); } - private void ExtractLeftResources(IEnumerable leftResources, RelationshipProxy[] relationships, + private void ExtractLeftResources(IEnumerable leftResources, IReadOnlyCollection relationships, Dictionary> rightResourcesGrouped, Dictionary> leftResourcesGrouped) { foreach (IIdentifiable leftResource in leftResources) @@ -168,7 +171,7 @@ private void ExtractLeftResources(IEnumerable leftResources, RelationshipProxy[] } } - private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] relationships, + private void ExtractLeftResource(IIdentifiable leftResource, IReadOnlyCollection relationships, Dictionary> rightResourcesGrouped, Dictionary> leftResourcesGrouped) { foreach (RelationshipProxy proxy in relationships) @@ -181,21 +184,8 @@ private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] continue; } - if (!(relationshipValue is IEnumerable rightResources)) - { - // in the case of a to-one relationship, the assigned value - // will not be a list. We therefore first wrap it in a list. - IList list = TypeHelper.CreateListFor(proxy.RightType); - - if (relationshipValue != null) - { - list.Add(relationshipValue); - } - - rightResources = list; - } - - HashSet uniqueRightResources = UniqueInTree(rightResources.Cast(), proxy.RightType); + ICollection rightResources = CollectionConverter.ExtractResources(relationshipValue); + ISet uniqueRightResources = UniqueInTree(rightResources, proxy.RightType); if (proxy.IsContextRelation || uniqueRightResources.Any()) { @@ -211,12 +201,12 @@ private void ExtractLeftResource(IIdentifiable leftResource, RelationshipProxy[] /// /// The relationships. /// - private RelationshipProxy[] GetPopulatedRelationships(LeftType leftType, IEnumerable lefts) + private IReadOnlyCollection GetPopulatedRelationships(LeftType leftType, IEnumerable lefts) { IEnumerable relationshipsFromLeftToRight = _relationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.LeftType == leftType); - return relationshipsFromLeftToRight.Where(proxy => proxy.IsContextRelation || lefts.Any(p => proxy.GetValue(p) != null)).ToArray(); + return relationshipsFromLeftToRight.Where(proxy => proxy.IsContextRelation || lefts.Any(resource => proxy.GetValue(resource) != null)).ToArray(); } /// @@ -231,11 +221,11 @@ private RelationshipProxy[] GetPopulatedRelationships(LeftType leftType, IEnumer /// /// The 1st type parameter. /// - private HashSet ProcessResources(IEnumerable incomingResources) + private ISet ProcessResources(IEnumerable incomingResources) where TResource : class, IIdentifiable { RightType type = typeof(TResource); - HashSet newResources = UniqueInTree(incomingResources, type); + ISet newResources = UniqueInTree(incomingResources, type); RegisterProcessedResources(newResources, type); return newResources; } @@ -283,7 +273,7 @@ private void RegisterRelationshipProxies(RightType type) /// private void RegisterProcessedResources(IEnumerable resources, RightType resourceType) { - HashSet processedResources = GetProcessedResources(resourceType); + ISet processedResources = GetProcessedResources(resourceType); processedResources.UnionWith(new HashSet(resources)); } @@ -296,7 +286,7 @@ private void RegisterProcessedResources(IEnumerable resources, Ri /// /// Resource type. /// - private HashSet GetProcessedResources(RightType resourceType) + private ISet GetProcessedResources(RightType resourceType) { if (!_processedResources.TryGetValue(resourceType, out HashSet processedResources)) { @@ -313,7 +303,7 @@ private HashSet GetProcessedResources(RightType resourceType) /// /// The in tree. /// - private HashSet UniqueInTree(IEnumerable resources, RightType resourceType) + private ISet UniqueInTree(IEnumerable resources, RightType resourceType) where TResource : class, IIdentifiable { IEnumerable newResources = resources.Except(GetProcessedResources(resourceType), _comparer).Cast(); @@ -332,7 +322,7 @@ private HashSet UniqueInTree(IEnumerable resour /// private RightType GetRightTypeFromRelationship(RelationshipAttribute attr) { - if (attr is HasManyThroughAttribute throughAttr && TypeHelper.IsOrImplementsInterface(throughAttr.ThroughType, typeof(IIdentifiable))) + if (attr is HasManyThroughAttribute throughAttr && throughAttr.ThroughType.IsOrImplementsInterface(typeof(IIdentifiable))) { return throughAttr.ThroughType; } @@ -355,11 +345,11 @@ private void AddToRelationshipGroup(Dictionary /// Reflective helper method to create an instance of ; /// - private IResourceNode CreateNodeInstance(RightType nodeType, RelationshipProxy[] relationshipsToNext, + private IResourceNode CreateNodeInstance(RightType nodeType, IReadOnlyCollection relationshipsToNext, IEnumerable relationshipsFromPrev) { IRelationshipsFromPreviousLayer prev = CreateRelationshipsFromInstance(nodeType, relationshipsFromPrev); - return (IResourceNode)TypeHelper.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, relationshipsToNext, prev); + return (IResourceNode)ObjectFactory.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, relationshipsToNext, prev); } /// @@ -368,8 +358,8 @@ private IResourceNode CreateNodeInstance(RightType nodeType, RelationshipProxy[] private IRelationshipsFromPreviousLayer CreateRelationshipsFromInstance(RightType nodeType, IEnumerable relationshipsFromPrev) { List relationshipsFromPrevList = relationshipsFromPrev.ToList(); - IList cast = TypeHelper.CopyToList(relationshipsFromPrevList, relationshipsFromPrevList.First().GetType()); - return (IRelationshipsFromPreviousLayer)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipsFromPreviousLayer<>), nodeType, cast); + IList list = CollectionConverter.CopyToList(relationshipsFromPrevList, relationshipsFromPrevList.First().GetType()); + return (IRelationshipsFromPreviousLayer)ObjectFactory.CreateInstanceOfOpenType(typeof(RelationshipsFromPreviousLayer<>), nodeType, list); } /// @@ -378,11 +368,10 @@ private IRelationshipsFromPreviousLayer CreateRelationshipsFromInstance(RightTyp private IRelationshipGroup CreateRelationshipGroupInstance(RightType thisLayerType, RelationshipProxy proxy, List leftResources, List rightResources) { - object rightResourcesHashed = - TypeHelper.CreateInstanceOfOpenType(typeof(HashSet<>), thisLayerType, TypeHelper.CopyToList(rightResources, thisLayerType)); + IEnumerable rightResourceSet = CollectionConverter.CopyToHashSet(rightResources, thisLayerType); - return (IRelationshipGroup)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipGroup<>), thisLayerType, proxy, - new HashSet(leftResources), rightResourcesHashed); + return (IRelationshipGroup)ObjectFactory.CreateInstanceOfOpenType(typeof(RelationshipGroup<>), thisLayerType, proxy, + new HashSet(leftResources), rightResourceSet); } } } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs index 1a95426557..b9cd252b4e 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipProxy.cs @@ -14,6 +14,8 @@ namespace JsonApiDotNetCore.Hooks.Internal.Traversal /// internal sealed class RelationshipProxy { + private static readonly HooksCollectionConverter CollectionConverter = new HooksCollectionConverter(); + private readonly bool _skipThroughType; public Type LeftType => Attribute.LeftType; @@ -108,7 +110,8 @@ public void SetValue(IIdentifiable resource, object value) var throughResources = (IEnumerable)hasManyThrough.ThroughProperty.GetValue(resource); var filteredList = new List(); - IList rightResources = TypeHelper.CopyToList((IEnumerable)value, RightType); + + IList rightResources = CollectionConverter.CopyToList((IEnumerable)value, RightType); foreach (object throughResource in throughResources ?? Array.Empty()) { @@ -118,7 +121,7 @@ public void SetValue(IIdentifiable resource, object value) } } - IEnumerable collectionValue = TypeHelper.CopyToTypedCollection(filteredList, hasManyThrough.ThroughProperty.PropertyType); + IEnumerable collectionValue = CollectionConverter.CopyToTypedCollection(filteredList, hasManyThrough.ThroughProperty.PropertyType); hasManyThrough.ThroughProperty.SetValue(resource, collectionValue); return; } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs index add2107d7e..9a91205628 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RelationshipsFromPreviousLayer.cs @@ -17,13 +17,13 @@ public RelationshipsFromPreviousLayer(IEnumerable - public Dictionary GetRightResources() + public IDictionary GetRightResources() { return _collection.ToDictionary(rg => rg.Proxy.Attribute, rg => (IEnumerable)rg.RightResources); } /// - public Dictionary GetLeftResources() + public IDictionary GetLeftResources() { return _collection.ToDictionary(rg => rg.Proxy.Attribute, rg => (IEnumerable)rg.LeftResources); } diff --git a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs index 14b6296f6f..a4453dbace 100644 --- a/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Internal/Traversal/RootNode.cs @@ -14,18 +14,19 @@ internal sealed class RootNode : IResourceNode where TResource : class, IIdentifiable { private readonly IdentifiableComparer _comparer = IdentifiableComparer.Instance; - private readonly RelationshipProxy[] _allRelationshipsToNextLayer; + private readonly IReadOnlyCollection _allRelationshipsToNextLayer; private HashSet _uniqueResources; public Type ResourceType { get; } public IEnumerable UniqueResources => _uniqueResources; - public RelationshipProxy[] RelationshipsToNextLayer { get; } + public IReadOnlyCollection RelationshipsToNextLayer { get; } /// /// The root node does not have a parent layer and therefore does not have any relationships to any previous layer /// public IRelationshipsFromPreviousLayer RelationshipsFromPreviousLayer => null; - public RootNode(IEnumerable uniqueResources, RelationshipProxy[] populatedRelationships, RelationshipProxy[] allRelationships) + public RootNode(IEnumerable uniqueResources, IReadOnlyCollection populatedRelationships, + IReadOnlyCollection allRelationships) { ResourceType = typeof(TResource); _uniqueResources = new HashSet(uniqueResources); @@ -33,18 +34,18 @@ public RootNode(IEnumerable uniqueResources, RelationshipProxy[] popu _allRelationshipsToNextLayer = allRelationships; } - public Dictionary> LeftsToNextLayerByRelationships() + public IDictionary> LeftsToNextLayerByRelationships() { - return _allRelationshipsToNextLayer.GroupBy(proxy => proxy.RightType) - .ToDictionary(gdc => gdc.Key, gdc => gdc.ToDictionary(p => p.Attribute, _ => UniqueResources)); + return _allRelationshipsToNextLayer.GroupBy(proxy => proxy.RightType).ToDictionary(grouping => grouping.Key, + grouping => grouping.ToDictionary(proxy => proxy.Attribute, _ => UniqueResources)); } /// /// The current layer resources grouped by affected relationship to the next layer /// - public Dictionary LeftsToNextLayer() + public IDictionary LeftsToNextLayer() { - return RelationshipsToNextLayer.ToDictionary(p => p.Attribute, _ => UniqueResources); + return RelationshipsToNextLayer.ToDictionary(proxy => proxy.Attribute, _ => UniqueResources); } /// @@ -53,8 +54,8 @@ public Dictionary LeftsToNextLayer() /// Updated. public void UpdateUnique(IEnumerable updated) { - List cast = updated.Cast().ToList(); - IEnumerable intersected = _uniqueResources.Intersect(cast, _comparer).Cast(); + List list = updated.Cast().ToList(); + IEnumerable intersected = _uniqueResources.Intersect(list, _comparer).Cast(); _uniqueResources = new HashSet(intersected); } diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 33a5e9647e..0a57d6e80f 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -22,6 +22,9 @@ + + none + diff --git a/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs b/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs index e60fbc31a3..0a93c9269c 100644 --- a/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs +++ b/src/JsonApiDotNetCore/Middleware/HeaderConstants.cs @@ -1,3 +1,5 @@ +#pragma warning disable AV1008 // Class should not be static + namespace JsonApiDotNetCore.Middleware { public static class HeaderConstants diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 27f9a2a46d..85796ef54f 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -260,7 +260,7 @@ private static string GetCustomRoute(string resourceName, string apiNamespace, H if (routeAttribute != null) { List trimmedComponents = httpContext.Request.Path.Value.Trim('/').Split('/').ToList(); - int resourceNameIndex = trimmedComponents.FindIndex(c => c == resourceName); + int resourceNameIndex = trimmedComponents.FindIndex(component => component == resourceName); string[] newComponents = trimmedComponents.Take(resourceNameIndex).ToArray(); string customRoute = string.Join('/', newComponents); return customRoute == apiNamespace ? null : customRoute; diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index 4d64bfe9d0..c9abf109c1 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -48,7 +48,7 @@ public JsonApiRoutingConvention(IJsonApiOptions options, IResourceContextProvide /// public Type GetResourceTypeForController(string controllerName) { - ArgumentGuard.NotNull(controllerName, nameof(controllerName)); + ArgumentGuard.NotNullNorEmpty(controllerName, nameof(controllerName)); if (_registeredResources.TryGetValue(controllerName, out ResourceContext resourceContext)) { @@ -157,7 +157,8 @@ private Type ExtractResourceTypeFromController(Type type) if ((nextBaseType == aspNetControllerType || nextBaseType == coreControllerType) && currentType.IsGenericType) { - Type resourceType = currentType.GetGenericArguments().FirstOrDefault(t => TypeHelper.IsOrImplementsInterface(t, typeof(IIdentifiable))); + Type resourceType = currentType.GetGenericArguments() + .FirstOrDefault(typeArgument => typeArgument.IsOrImplementsInterface(typeof(IIdentifiable))); if (resourceType != null) { diff --git a/src/JsonApiDotNetCore/ObjectExtensions.cs b/src/JsonApiDotNetCore/ObjectExtensions.cs index 7df7cf3b02..b1bb1eb4a7 100644 --- a/src/JsonApiDotNetCore/ObjectExtensions.cs +++ b/src/JsonApiDotNetCore/ObjectExtensions.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type + namespace JsonApiDotNetCore { internal static class ObjectExtensions diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs index 57357fa6ec..1d3dea04ad 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Queries.Expressions /// /// Converts includes between tree and chain formats. Exists for backwards compatibility, subject to be removed in the future. /// - internal static class IncludeChainConverter + internal sealed class IncludeChainConverter { /// /// Converts a tree of inclusions into a set of relationship chains. @@ -28,7 +28,7 @@ internal static class IncludeChainConverter /// Article -> Revisions -> Author /// ]]> /// - public static IReadOnlyCollection GetRelationshipChains(IncludeExpression include) + public IReadOnlyCollection GetRelationshipChains(IncludeExpression include) { ArgumentGuard.NotNull(include, nameof(include)); @@ -57,7 +57,7 @@ public static IReadOnlyCollection GetRelationshipC /// } /// ]]> /// - public static IncludeExpression FromRelationshipChains(IReadOnlyCollection chains) + public IncludeExpression FromRelationshipChains(IReadOnlyCollection chains) { ArgumentGuard.NotNull(chains, nameof(chains)); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs index 538d698193..9b72d050e7 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs @@ -11,17 +11,15 @@ namespace JsonApiDotNetCore.Queries.Expressions [PublicAPI] public class IncludeExpression : QueryExpression { + private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter(); + public static readonly IncludeExpression Empty = new IncludeExpression(); + public IReadOnlyCollection Elements { get; } public IncludeExpression(IReadOnlyCollection elements) { - ArgumentGuard.NotNull(elements, nameof(elements)); - - if (!elements.Any()) - { - throw new ArgumentException("Must have one or more elements.", nameof(elements)); - } + ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); Elements = elements; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs index 7046c7b1ad..1850ab0b02 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs @@ -15,12 +15,7 @@ public class PaginationQueryStringValueExpression : QueryExpression public PaginationQueryStringValueExpression(IReadOnlyCollection elements) { - ArgumentGuard.NotNull(elements, nameof(elements)); - - if (!elements.Any()) - { - throw new ArgumentException("Must have one or more elements.", nameof(elements)); - } + ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); Elements = elements; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs index 156c7cd091..2ebec58d17 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs @@ -23,12 +23,7 @@ public ResourceFieldChainExpression(ResourceFieldAttribute field) public ResourceFieldChainExpression(IReadOnlyCollection fields) { - ArgumentGuard.NotNull(fields, nameof(fields)); - - if (!fields.Any()) - { - throw new ArgumentException("Must have one or more fields.", nameof(fields)); - } + ArgumentGuard.NotNullNorEmpty(fields, nameof(fields)); Fields = fields; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs index ab4d4a6f43..9f9c74b668 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs @@ -15,12 +15,7 @@ public class SortExpression : QueryExpression public SortExpression(IReadOnlyCollection elements) { - ArgumentGuard.NotNull(elements, nameof(elements)); - - if (!elements.Any()) - { - throw new ArgumentException("Must have one or more elements.", nameof(elements)); - } + ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); Elements = elements; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs index 32ba68c45e..7511ab309a 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs @@ -16,12 +16,7 @@ public class SparseFieldSetExpression : QueryExpression public SparseFieldSetExpression(IReadOnlyCollection fields) { - ArgumentGuard.NotNull(fields, nameof(fields)); - - if (!fields.Any()) - { - throw new ArgumentException("Must have one or more fields.", nameof(fields)); - } + ArgumentGuard.NotNullNorEmpty(fields, nameof(fields)); Fields = fields; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs index 48c86a1cba..4fdc92ec60 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs @@ -17,12 +17,7 @@ public class SparseFieldTableExpression : QueryExpression public SparseFieldTableExpression(IReadOnlyDictionary table) { - ArgumentGuard.NotNull(table, nameof(table)); - - if (!table.Any()) - { - throw new ArgumentException("Must have one or more entries.", nameof(table)); - } + ArgumentGuard.NotNullNorEmpty(table, nameof(table), "entries"); Table = table; } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 448bff8762..e2cc83ceba 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -11,6 +11,8 @@ namespace JsonApiDotNetCore.Queries.Internal.Parsing [PublicAPI] public class IncludeParser : QueryExpressionParser { + private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter(); + private readonly Action _validateSingleRelationshipCallback; private ResourceContext _resourceContextInScope; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/Keywords.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/Keywords.cs index dabe52e605..0bf32ff17e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/Keywords.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/Keywords.cs @@ -1,5 +1,8 @@ using JetBrains.Annotations; +#pragma warning disable AV1008 // Class should not be static +#pragma warning disable AV1010 // Member hides inherited member + namespace JsonApiDotNetCore.Queries.Internal.Parsing { [PublicAPI] diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs index c4c0d1c546..029c76fcb1 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/ResourceFieldChainResolver.cs @@ -197,7 +197,7 @@ public IReadOnlyCollection ResolveToOneChainEndingInAttr private RelationshipAttribute GetRelationship(string publicName, ResourceContext resourceContext, string path) { - RelationshipAttribute relationship = resourceContext.Relationships.FirstOrDefault(r => r.PublicName == publicName); + RelationshipAttribute relationship = resourceContext.Relationships.FirstOrDefault(nextRelationship => nextRelationship.PublicName == publicName); if (relationship == null) { @@ -239,7 +239,7 @@ private RelationshipAttribute GetToOneRelationship(string publicName, ResourceCo private AttrAttribute GetAttribute(string publicName, ResourceContext resourceContext, string path) { - AttrAttribute attribute = resourceContext.Attributes.FirstOrDefault(a => a.PublicName == publicName); + AttrAttribute attribute = resourceContext.Attributes.FirstOrDefault(nextAttribute => nextAttribute.PublicName == publicName); if (attribute == null) { @@ -253,7 +253,7 @@ private AttrAttribute GetAttribute(string publicName, ResourceContext resourceCo public ResourceFieldAttribute GetField(string publicName, ResourceContext resourceContext, string path) { - ResourceFieldAttribute field = resourceContext.Fields.FirstOrDefault(a => a.PublicName == publicName); + ResourceFieldAttribute field = resourceContext.Fields.FirstOrDefault(nextField => nextField.PublicName == publicName); if (field == null) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 8476ecfcb4..ffaab66790 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -13,6 +13,7 @@ namespace JsonApiDotNetCore.Queries.Internal [PublicAPI] public class QueryLayerComposer : IQueryLayerComposer { + private readonly CollectionConverter _collectionConverter = new CollectionConverter(); private readonly IEnumerable _constraintProviders; private readonly IResourceContextProvider _resourceContextProvider; private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; @@ -343,7 +344,7 @@ public QueryLayer ComposeForUpdate(TId id, ResourceContext primaryResource) foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { object rightValue = relationship.GetValue(primaryResource); - ICollection rightResourceIds = TypeHelper.ExtractResources(rightValue); + ICollection rightResourceIds = _collectionConverter.ExtractResources(rightValue); if (rightResourceIds.Any()) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs index 424ff9962e..ef3b23ad84 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/IncludeClauseBuilder.cs @@ -15,6 +15,8 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding [PublicAPI] public class IncludeClauseBuilder : QueryClauseBuilder { + private static readonly IncludeChainConverter IncludeChainConverter = new IncludeChainConverter(); + private readonly Expression _source; private readonly ResourceContext _resourceContext; private readonly IResourceContextProvider _resourceContextProvider; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs index 40d8b241b3..6309c8ac54 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs @@ -14,7 +14,7 @@ public sealed class LambdaParameterNameFactory public LambdaParameterNameScope Create(string typeName) { - ArgumentGuard.NotNull(typeName, nameof(typeName)); + ArgumentGuard.NotNullNorEmpty(typeName, nameof(typeName)); string parameterName = typeName.Camelize(); parameterName = EnsureNameIsUnique(parameterName); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs index 178ed800b6..6d555ed23e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs @@ -12,7 +12,7 @@ public sealed class LambdaParameterNameScope : IDisposable public LambdaParameterNameScope(string name, LambdaParameterNameFactory owner) { - ArgumentGuard.NotNull(name, nameof(name)); + ArgumentGuard.NotNullNorEmpty(name, nameof(name)); ArgumentGuard.NotNull(owner, nameof(owner)); Name = name; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs index 519a800cc2..e8a8b8a6da 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs @@ -42,7 +42,7 @@ private static Expression TryGetCollectionCount(Expression collectionExpression) if (collectionExpression.Type.IsInterface) { - foreach (PropertyInfo item in collectionExpression.Type.GetInterfaces().SelectMany(i => i.GetProperties())) + foreach (PropertyInfo item in collectionExpression.Type.GetInterfaces().SelectMany(@interface => @interface.GetProperties())) { properties.Add(item); } @@ -107,7 +107,7 @@ protected Expression CreateTupleAccessExpressionForConstant(object value, Type t // Tuple.Create(value).Item1; MethodInfo tupleCreateMethod = typeof(Tuple).GetMethods() - .Single(m => m.Name == "Create" && m.IsGenericMethod && m.GetGenericArguments().Length == 1); + .Single(method => method.Name == "Create" && method.IsGenericMethod && method.GetGenericArguments().Length == 1); MethodInfo constructedTupleCreateMethod = tupleCreateMethod.MakeGenericMethod(type); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs index 4d4187b84f..c85c223364 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -74,7 +73,7 @@ public virtual Expression ApplyQuery(QueryLayer layer) expression = ApplyPagination(expression, layer.Pagination); } - if (layer.Projection != null && layer.Projection.Any()) + if (!layer.Projection.IsNullOrEmpty()) { expression = ApplyProjection(expression, layer.Projection, layer.ResourceContext); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs index 79545277ef..b6ddf29c9a 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs @@ -21,6 +21,7 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding [PublicAPI] public class SelectClauseBuilder : QueryClauseBuilder { + private static readonly CollectionConverter CollectionConverter = new CollectionConverter(); private static readonly ConstantExpression NullConstant = Expression.Constant(null); private readonly Expression _source; @@ -116,7 +117,7 @@ private ICollection ToPropertySelectors(IDictionary type.ClrType == elementType); - IEnumerable entityProperties = entityModel.GetProperties().Where(p => !p.IsShadowProperty()).ToArray(); + IEnumerable entityProperties = entityModel.GetProperties().Where(property => !property.IsShadowProperty()).ToArray(); foreach (IProperty entityProperty in entityProperties) { @@ -159,7 +160,7 @@ private MemberAssignment CreatePropertyAssignment(PropertySelector selector, Lam private Expression CreateAssignmentRightHandSideForLayer(QueryLayer layer, LambdaScope outerLambdaScope, MemberExpression propertyAccess, PropertyInfo selectorPropertyInfo, LambdaScopeFactory lambdaScopeFactory) { - Type collectionElementType = TypeHelper.TryGetCollectionElementType(selectorPropertyInfo.PropertyType); + Type collectionElementType = CollectionConverter.TryGetCollectionElementType(selectorPropertyInfo.PropertyType); Type bodyElementType = collectionElementType ?? selectorPropertyInfo.PropertyType; if (collectionElementType != null) @@ -193,7 +194,7 @@ private Expression CreateCollectionInitializer(LambdaScope lambdaScope, Property if (EntityFrameworkCoreSupport.Version.Major < 5) { Type enumerableOfElementType = typeof(IEnumerable<>).MakeGenericType(elementType); - Type typedCollection = TypeHelper.ToConcreteCollectionType(collectionProperty.PropertyType); + Type typedCollection = CollectionConverter.ToConcreteCollectionType(collectionProperty.PropertyType); ConstructorInfo typedCollectionConstructor = typedCollection.GetConstructor(enumerableOfElementType.AsArray()); @@ -205,7 +206,7 @@ private Expression CreateCollectionInitializer(LambdaScope lambdaScope, Property return Expression.New(typedCollectionConstructor, layerExpression); } - string operationName = TypeHelper.TypeCanContainHashSet(collectionProperty.PropertyType) ? "ToHashSet" : "ToList"; + string operationName = CollectionConverter.TypeCanContainHashSet(collectionProperty.PropertyType) ? "ToHashSet" : "ToList"; return CopyCollectionExtensionMethodCall(layerExpression, operationName, elementType); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs index 6ce2a920b2..6c03f24d23 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs @@ -17,6 +17,7 @@ namespace JsonApiDotNetCore.Queries.Internal.QueryableBuilding [PublicAPI] public class WhereClauseBuilder : QueryClauseBuilder { + private static readonly CollectionConverter CollectionConverter = new CollectionConverter(); private static readonly ConstantExpression NullConstant = Expression.Constant(null); private readonly Expression _source; @@ -51,7 +52,7 @@ public override Expression VisitCollectionNotEmpty(CollectionNotEmptyExpression { Expression property = Visit(expression.TargetCollection, argument); - Type elementType = TypeHelper.TryGetCollectionElementType(property.Type); + Type elementType = CollectionConverter.TryGetCollectionElementType(property.Type); if (elementType == null) { @@ -188,7 +189,7 @@ private Type TryResolveCommonType(QueryExpression left, QueryExpression right) { Type leftType = ResolveFixedType(left); - if (TypeHelper.CanContainNull(leftType)) + if (RuntimeTypeConverter.CanContainNull(leftType)) { return leftType; } @@ -200,7 +201,7 @@ private Type TryResolveCommonType(QueryExpression left, QueryExpression right) Type rightType = TryResolveFixedType(right); - if (rightType != null && TypeHelper.CanContainNull(rightType)) + if (rightType != null && RuntimeTypeConverter.CanContainNull(rightType)) { return rightType; } @@ -258,7 +259,7 @@ private static object ConvertTextToTargetType(string text, Type targetType) { try { - return TypeHelper.ConvertType(text, targetType); + return RuntimeTypeConverter.ConvertType(text, targetType); } catch (FormatException exception) { diff --git a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs index 35b7f4ac3f..c44a0a34db 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs @@ -141,7 +141,9 @@ 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 { ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); diff --git a/src/JsonApiDotNetCore/Queries/QueryLayer.cs b/src/JsonApiDotNetCore/Queries/QueryLayer.cs index dfc022f164..1ea0f62733 100644 --- a/src/JsonApiDotNetCore/Queries/QueryLayer.cs +++ b/src/JsonApiDotNetCore/Queries/QueryLayer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -66,7 +65,7 @@ private static void WriteLayer(IndentingStringWriter writer, QueryLayer layer, s writer.WriteLine($"{nameof(Pagination)}: {layer.Pagination}"); } - if (layer.Projection != null && layer.Projection.Any()) + if (!layer.Projection.IsNullOrEmpty()) { writer.WriteLine(nameof(Projection)); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index c39c5e6f47..bcfce7fcd5 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -62,7 +62,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr /// public virtual bool CanRead(string parameterName) { - ArgumentGuard.NotNull(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); bool isNested = parameterName.StartsWith("filter[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); return parameterName == "filter" || isNested; diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs index ff4a47f845..6be267289d 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs @@ -29,7 +29,7 @@ public sealed class LegacyFilterNotationConverter public IEnumerable ExtractConditions(string parameterValue) { - ArgumentGuard.NotNull(parameterValue, nameof(parameterValue)); + ArgumentGuard.NotNullNorEmpty(parameterValue, nameof(parameterValue)); if (parameterValue.StartsWith(ExpressionPrefix, StringComparison.Ordinal) || parameterValue.StartsWith(InPrefix, StringComparison.Ordinal) || parameterValue.StartsWith(NotInPrefix, StringComparison.Ordinal)) @@ -47,8 +47,8 @@ public IEnumerable ExtractConditions(string parameterValue) public (string parameterName, string parameterValue) Convert(string parameterName, string parameterValue) { - ArgumentGuard.NotNull(parameterName, nameof(parameterName)); - ArgumentGuard.NotNull(parameterValue, nameof(parameterValue)); + ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(parameterValue, nameof(parameterValue)); if (parameterValue.StartsWith(ExpressionPrefix, StringComparison.Ordinal)) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs index ce7ac0288d..19673f7fb3 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs @@ -45,7 +45,7 @@ public virtual void ReadAll(DisableQueryStringAttribute disableQueryStringAttrib $"Missing value for '{parameterName}' query string parameter."); } - IQueryStringParameterReader reader = _parameterReaders.FirstOrDefault(r => r.CanRead(parameterName)); + IQueryStringParameterReader reader = _parameterReaders.FirstOrDefault(nextReader => nextReader.CanRead(parameterName)); if (reader != null) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs index 0271d3f95d..058fa6817c 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs @@ -48,7 +48,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr /// public virtual bool CanRead(string parameterName) { - ArgumentGuard.NotNull(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); bool isNested = parameterName.StartsWith("sort[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); return parameterName == "sort" || isNested; diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index 6a0cbf9244..1ab65dc332 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -49,7 +49,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr /// public virtual bool CanRead(string parameterName) { - ArgumentGuard.NotNull(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); return parameterName.StartsWith("fields[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index b305938bb0..d1e6028ebe 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -28,6 +28,7 @@ namespace JsonApiDotNetCore.Repositories public class EntityFrameworkCoreRepository : IResourceRepository, IRepositorySupportsTransaction where TResource : class, IIdentifiable { + private readonly CollectionConverter _collectionConverter = new CollectionConverter(); private readonly ITargetedFields _targetedFields; private readonly DbContext _dbContext; private readonly IResourceGraph _resourceGraph; @@ -239,12 +240,14 @@ protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute rel } } - private static bool IsToManyRelationshipBeingCleared(RelationshipAttribute relationship, TResource leftResource, object valueToAssign) + private bool IsToManyRelationshipBeingCleared(RelationshipAttribute relationship, TResource leftResource, object valueToAssign) { - ICollection newRightResourceIds = TypeHelper.ExtractResources(valueToAssign); + ICollection newRightResourceIds = _collectionConverter.ExtractResources(valueToAssign); object existingRightValue = relationship.GetValue(leftResource); - HashSet existingRightResourceIds = TypeHelper.ExtractResources(existingRightValue).ToHashSet(IdentifiableComparer.Instance); + + HashSet existingRightResourceIds = + _collectionConverter.ExtractResources(existingRightValue).ToHashSet(IdentifiableComparer.Instance); existingRightResourceIds.ExceptWith(newRightResourceIds); @@ -265,7 +268,7 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke foreach (RelationshipAttribute relationship in _resourceGraph.GetRelationships()) { // Loads the data of the relationship, if in EF Core it is configured in such a way that loading the related - // entities into memory is required for successfully executing the selected deletion behavior. + // entities into memory is required for successfully executing the selected deletion behavior. if (RequiresLoadOfRelationshipForDeletion(relationship)) { NavigationEntry navigation = GetNavigationEntry(resource, relationship); @@ -385,7 +388,7 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource primaryRes object rightValue = relationship.GetValue(primaryResource); - HashSet rightResourceIds = TypeHelper.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); + HashSet rightResourceIds = _collectionConverter.ExtractResources(rightValue).ToHashSet(IdentifiableComparer.Instance); rightResourceIds.ExceptWith(secondaryResourceIds); AssertIsNotClearingRequiredRelationship(relationship, primaryResource, rightResourceIds); @@ -419,25 +422,25 @@ private object EnsureRelationshipValueToAssignIsTracked(object rightValue, Type return null; } - ICollection rightResources = TypeHelper.ExtractResources(rightValue); + ICollection rightResources = _collectionConverter.ExtractResources(rightValue); IIdentifiable[] rightResourcesTracked = rightResources.Select(collector.CaptureExisting).ToArray(); return rightValue is IEnumerable - ? (object)TypeHelper.CopyToTypedCollection(rightResourcesTracked, relationshipPropertyType) + ? (object)_collectionConverter.CopyToTypedCollection(rightResourcesTracked, relationshipPropertyType) : rightResourcesTracked.Single(); } - private static bool RequireLoadOfInverseRelationship(RelationshipAttribute relationship, object trackedValueToAssign) + private bool RequireLoadOfInverseRelationship(RelationshipAttribute relationship, object trackedValueToAssign) { // See https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/502. return trackedValueToAssign != null && relationship.InverseNavigationProperty != null && IsOneToOneRelationship(relationship); } - private static bool IsOneToOneRelationship(RelationshipAttribute relationship) + private bool IsOneToOneRelationship(RelationshipAttribute relationship) { if (relationship is HasOneAttribute hasOneRelationship) { - Type elementType = TypeHelper.TryGetCollectionElementType(hasOneRelationship.InverseNavigationProperty.PropertyType); + Type elementType = _collectionConverter.TryGetCollectionElementType(hasOneRelationship.InverseNavigationProperty.PropertyType); return elementType == null; } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs index 5f4110cbe5..042c4613ce 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreSupport.cs @@ -1,6 +1,8 @@ using System; using Microsoft.EntityFrameworkCore; +#pragma warning disable AV1008 // Class should not be static + namespace JsonApiDotNetCore.Repositories { internal static class EntityFrameworkCoreSupport diff --git a/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs b/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs index 9d6c58a0ca..557f330791 100644 --- a/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs +++ b/src/JsonApiDotNetCore/Repositories/MemoryLeakDetectionBugRewriter.cs @@ -38,7 +38,7 @@ private QueryLayer RewriteLayer(QueryLayer queryLayer) private IDictionary RewriteProjection(IDictionary projection, ResourceContext resourceContext) { - if (projection == null || projection.Count == 0) + if (projection.IsNullOrEmpty()) { return projection; } diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 887ebb06a6..25de9a3409 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -156,7 +156,7 @@ private object GetWriteRepository(Type resourceType) if (repository.TransactionId != _request.TransactionId) { - throw new NonSharedTransactionException(); + throw new NonParticipatingTransactionException(); } } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs index 1987eb4d94..23461fc3dd 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/AttrAttribute.cs @@ -60,8 +60,7 @@ public void SetValue(object resource, object newValue) throw new InvalidOperationException($"Property '{Property.DeclaringType?.Name}.{Property.Name}' is read-only."); } - object convertedValue = TypeHelper.ConvertType(newValue, Property.PropertyType); - Property.SetValue(resource, convertedValue); + Property.SetValue(resource, newValue); } public override bool Equals(object obj) diff --git a/src/JsonApiDotNetCore/Resources/Annotations/EagerLoadAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/EagerLoadAttribute.cs index 17685ccd47..623e7eeeb2 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/EagerLoadAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/EagerLoadAttribute.cs @@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Resources.Annotations /// Name.First + " " + Name.Last; /// diff --git a/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs index b2f51f4a47..3a8b3bc16a 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Resources.Annotations /// /// /// Articles { get; set; } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs index 0b961a47e4..cfe180c761 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/HasManyThroughAttribute.cs @@ -45,6 +45,8 @@ namespace JsonApiDotNetCore.Resources.Annotations [AttributeUsage(AttributeTargets.Property)] public sealed class HasManyThroughAttribute : HasManyAttribute { + private static readonly CollectionConverter CollectionConverter = new CollectionConverter(); + /// /// The name of the join property on the parent resource. In the example described above, this would be "ArticleTags". /// @@ -119,7 +121,7 @@ public sealed class HasManyThroughAttribute : HasManyAttribute /// public HasManyThroughAttribute(string throughPropertyName) { - ArgumentGuard.NotNull(throughPropertyName, nameof(throughPropertyName)); + ArgumentGuard.NotNullNorEmpty(throughPropertyName, nameof(throughPropertyName)); ThroughPropertyName = throughPropertyName; } @@ -141,7 +143,7 @@ public override object GetValue(object resource) IEnumerable rightResources = ((IEnumerable)throughEntity).Cast().Select(rightResource => RightProperty.GetValue(rightResource)); - return TypeHelper.CopyToTypedCollection(rightResources, Property.PropertyType); + return CollectionConverter.CopyToTypedCollection(rightResources, Property.PropertyType); } /// @@ -164,14 +166,14 @@ public override void SetValue(object resource, object newValue) foreach (IIdentifiable rightResource in (IEnumerable)newValue) { - object throughEntity = TypeHelper.CreateInstance(ThroughType); + object throughEntity = Activator.CreateInstance(ThroughType); LeftProperty.SetValue(throughEntity, resource); RightProperty.SetValue(throughEntity, rightResource); throughResources.Add(throughEntity); } - IEnumerable typedCollection = TypeHelper.CopyToTypedCollection(throughResources, ThroughProperty.PropertyType); + IEnumerable typedCollection = CollectionConverter.CopyToTypedCollection(throughResources, ThroughProperty.PropertyType); ThroughProperty.SetValue(resource, typedCollection); } } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs index db6f7c8621..501d085d57 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/ResourceAttribute.cs @@ -18,7 +18,7 @@ public sealed class ResourceAttribute : Attribute public ResourceAttribute(string publicName) { - ArgumentGuard.NotNull(publicName, nameof(publicName)); + ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName)); PublicName = publicName; } diff --git a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs index 8688a412ec..69cf85639d 100644 --- a/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/IResourceDefinition.cs @@ -121,7 +121,9 @@ public interface IResourceDefinition /// } /// ]]> /// +#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type QueryStringParameterHandlers OnRegisterQueryableHandlersForQueryStringParameters(); +#pragma warning restore AV1130 // Return type in method signature should be a collection interface instead of a concrete type /// /// Enables to add JSON:API meta information, specific to this resource. diff --git a/src/JsonApiDotNetCore/Resources/Identifiable.cs b/src/JsonApiDotNetCore/Resources/Identifiable.cs index 69875bfdac..121f7b65d4 100644 --- a/src/JsonApiDotNetCore/Resources/Identifiable.cs +++ b/src/JsonApiDotNetCore/Resources/Identifiable.cs @@ -44,7 +44,7 @@ protected virtual string GetStringId(TId value) /// protected virtual TId GetTypedId(string value) { - return value == null ? default : (TId)TypeHelper.ConvertType(value, typeof(TId)); + return value == null ? default : (TId)RuntimeTypeConverter.ConvertType(value, typeof(TId)); } } } diff --git a/src/JsonApiDotNetCore/Resources/OperationContainer.cs b/src/JsonApiDotNetCore/Resources/OperationContainer.cs index 33dd890f39..9d448e42da 100644 --- a/src/JsonApiDotNetCore/Resources/OperationContainer.cs +++ b/src/JsonApiDotNetCore/Resources/OperationContainer.cs @@ -12,6 +12,8 @@ namespace JsonApiDotNetCore.Resources [PublicAPI] public sealed class OperationContainer { + private static readonly CollectionConverter CollectionConverter = new CollectionConverter(); + public OperationKind Kind { get; } public IIdentifiable Resource { get; } public ITargetedFields TargetedFields { get; } @@ -57,7 +59,7 @@ private void AddSecondaryResources(RelationshipAttribute relationship, HashSet m.Name == "Create" && m.IsGenericMethod && m.GetGenericArguments().Length == 1); + .Single(method => method.Name == "Create" && method.IsGenericMethod && method.GetGenericArguments().Length == 1); MethodInfo constructedTupleCreateMethod = tupleCreateMethod.MakeGenericMethod(type); @@ -103,14 +107,14 @@ private static Expression CreateTupleAccessExpressionForConstant(object value, T internal static bool HasSingleConstructorWithoutParameters(Type type) { - ConstructorInfo[] constructors = type.GetConstructors().Where(c => !c.IsStatic).ToArray(); + ConstructorInfo[] constructors = type.GetConstructors().Where(constructor => !constructor.IsStatic).ToArray(); return constructors.Length == 1 && constructors[0].GetParameters().Length == 0; } private static ConstructorInfo GetLongestConstructor(Type type) { - ConstructorInfo[] constructors = type.GetConstructors().Where(c => !c.IsStatic).ToArray(); + ConstructorInfo[] constructors = type.GetConstructors().Where(constructor => !constructor.IsStatic).ToArray(); if (constructors.Length == 0) { diff --git a/src/JsonApiDotNetCore/RuntimeTypeConverter.cs b/src/JsonApiDotNetCore/RuntimeTypeConverter.cs new file mode 100644 index 0000000000..1b4790cdd9 --- /dev/null +++ b/src/JsonApiDotNetCore/RuntimeTypeConverter.cs @@ -0,0 +1,88 @@ +using System; + +#pragma warning disable AV1008 // Class should not be static + +namespace JsonApiDotNetCore +{ + internal static class RuntimeTypeConverter + { + public static object ConvertType(object value, Type type) + { + ArgumentGuard.NotNull(type, nameof(type)); + + if (value == null) + { + if (!CanContainNull(type)) + { + throw new FormatException($"Failed to convert 'null' to type '{type.Name}'."); + } + + return null; + } + + Type runtimeType = value.GetType(); + + if (type == runtimeType || type.IsAssignableFrom(runtimeType)) + { + return value; + } + + string stringValue = value.ToString(); + + if (string.IsNullOrEmpty(stringValue)) + { + return GetDefaultValue(type); + } + + bool isNullableTypeRequested = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + Type nonNullableType = Nullable.GetUnderlyingType(type) ?? type; + + try + { + if (nonNullableType == typeof(Guid)) + { + Guid convertedValue = Guid.Parse(stringValue); + return isNullableTypeRequested ? (Guid?)convertedValue : convertedValue; + } + + if (nonNullableType == typeof(DateTimeOffset)) + { + DateTimeOffset convertedValue = DateTimeOffset.Parse(stringValue); + return isNullableTypeRequested ? (DateTimeOffset?)convertedValue : convertedValue; + } + + if (nonNullableType == typeof(TimeSpan)) + { + TimeSpan convertedValue = TimeSpan.Parse(stringValue); + return isNullableTypeRequested ? (TimeSpan?)convertedValue : convertedValue; + } + + if (nonNullableType.IsEnum) + { + object convertedValue = Enum.Parse(nonNullableType, stringValue); + + // https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html + return convertedValue; + } + + // https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html + return Convert.ChangeType(stringValue, nonNullableType); + } + catch (Exception exception) when (exception is FormatException || exception is OverflowException || exception is InvalidCastException || + exception is ArgumentException) + { + throw new FormatException($"Failed to convert '{value}' of type '{runtimeType.Name}' to type '{type.Name}'.", exception); + } + } + + public static bool CanContainNull(Type type) + { + return !type.IsValueType || Nullable.GetUnderlyingType(type) != null; + } + + public static object GetDefaultValue(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index c5a393efe9..f716b87ac2 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -21,6 +21,8 @@ namespace JsonApiDotNetCore.Serialization [PublicAPI] public abstract class BaseDeserializer { + private protected static readonly CollectionConverter CollectionConverter = new CollectionConverter(); + protected IResourceContextProvider ResourceContextProvider { get; } protected IResourceFactory ResourceFactory { get; } protected Document Document { get; set; } @@ -56,7 +58,7 @@ protected BaseDeserializer(IResourceContextProvider resourceContextProvider, IRe protected object DeserializeBody(string body) { - ArgumentGuard.NotNull(body, nameof(body)); + ArgumentGuard.NotNullNorEmpty(body, nameof(body)); JToken bodyJToken = LoadJToken(body); Document = bodyJToken.ToObject(); @@ -95,7 +97,7 @@ protected IIdentifiable SetAttributes(IIdentifiable resource, IDictionary rightResources = relationshipData.ManyData.Select(rio => CreateRightResource(hasManyRelationship, rio)) .ToHashSet(IdentifiableComparer.Instance); - IEnumerable convertedCollection = TypeHelper.CopyToTypedCollection(rightResources, hasManyRelationship.Property.PropertyType); + IEnumerable convertedCollection = CollectionConverter.CopyToTypedCollection(rightResources, hasManyRelationship.Property.PropertyType); hasManyRelationship.SetValue(resource, convertedCollection); AfterProcessField(resource, hasManyRelationship, relationshipData); @@ -344,7 +348,7 @@ private object ConvertAttrValue(object newValue, Type targetType) } // the attribute value is a native C# type. - object convertedValue = TypeHelper.ConvertType(newValue, targetType); + object convertedValue = RuntimeTypeConverter.ConvertType(newValue, targetType); return convertedValue; } diff --git a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs index 50ac9f5851..376c6f36e4 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/LinkBuilder.cs @@ -186,7 +186,7 @@ private string GetPageLink(ResourceContext resourceContext, int pageOffset, Page private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPageSize) { - List elements = ParsePageSizeExpression(pageSizeParameterValue); + IList elements = ParsePageSizeExpression(pageSizeParameterValue); int elementInTopScopeIndex = elements.FindIndex(expression => expression.Scope == null); if (topPageSize != null) @@ -216,7 +216,7 @@ private string ChangeTopPageSize(string pageSizeParameterValue, PageSize topPage return parameterValue == string.Empty ? null : parameterValue; } - private List ParsePageSizeExpression(string pageSizeParameterValue) + private IList ParsePageSizeExpression(string pageSizeParameterValue) { if (pageSizeParameterValue == null) { @@ -234,8 +234,8 @@ private List ParsePageSizeExpressio /// public ResourceLinks GetResourceLinks(string resourceName, string id) { - ArgumentGuard.NotNull(resourceName, nameof(resourceName)); - ArgumentGuard.NotNull(id, nameof(id)); + ArgumentGuard.NotNullNorEmpty(resourceName, nameof(resourceName)); + ArgumentGuard.NotNullNorEmpty(id, nameof(id)); ResourceContext resourceContext = _provider.GetResourceContext(resourceName); diff --git a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs index 3e1c782c18..b1ad1d7f72 100644 --- a/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Building/ResourceObjectBuilder.cs @@ -13,6 +13,8 @@ namespace JsonApiDotNetCore.Serialization.Building [PublicAPI] public class ResourceObjectBuilder : IResourceObjectBuilder { + private static readonly CollectionConverter CollectionConverter = new CollectionConverter(); + private readonly ResourceObjectBuilderSettings _settings; protected IResourceContextProvider ResourceContextProvider { get; } @@ -110,7 +112,7 @@ private ResourceIdentifierObject GetRelatedResourceLinkageForHasOne(HasOneAttrib private IList GetRelatedResourceLinkageForHasMany(HasManyAttribute relationship, IIdentifiable resource) { object value = relationship.GetValue(resource); - ICollection relatedResources = TypeHelper.ExtractResources(value); + ICollection relatedResources = CollectionConverter.ExtractResources(value); var manyData = new List(); @@ -172,7 +174,7 @@ private void ProcessAttributes(IIdentifiable resource, IEnumerable _constraintProviders; private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; @@ -70,13 +72,13 @@ protected override RelationshipEntry GetRelationshipData(RelationshipAttribute r ArgumentGuard.NotNull(resource, nameof(resource)); RelationshipEntry relationshipEntry = null; - List> relationshipChains = null; + IReadOnlyCollection> relationshipChains = GetInclusionChain(relationship); - if (Equals(relationship, _requestRelationship) || ShouldInclude(relationship, out relationshipChains)) + if (Equals(relationship, _requestRelationship) || relationshipChains.Any()) { relationshipEntry = base.GetRelationshipData(relationship, resource); - if (relationshipChains != null && relationshipEntry.HasResource) + if (relationshipChains.Any() && relationshipEntry.HasResource) { foreach (IReadOnlyCollection chain in relationshipChains) { @@ -117,7 +119,7 @@ private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship) /// Inspects the included relationship chains (see to see if should be /// included or not. /// - private bool ShouldInclude(RelationshipAttribute relationship, out List> inclusionChain) + private IReadOnlyCollection> GetInclusionChain(RelationshipAttribute relationship) { // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true @@ -132,7 +134,7 @@ private bool ShouldInclude(RelationshipAttribute relationship, out List>(); + var inclusionChain = new List>(); foreach (ResourceFieldChainExpression chain in chains) { @@ -142,7 +144,7 @@ private bool ShouldInclude(RelationshipAttribute relationship, out List DeserializeSingle(string body) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(body, nameof(body)); + ArgumentGuard.NotNullNorEmpty(body, nameof(body)); object resource = DeserializeBody(body); @@ -43,7 +43,7 @@ public SingleResponse DeserializeSingle(string body) public ManyResponse DeserializeMany(string body) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(body, nameof(body)); + ArgumentGuard.NotNullNorEmpty(body, nameof(body)); object resources = DeserializeBody(body); @@ -82,7 +82,7 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA } // if the included property is empty or absent, there is no additional data to be parsed. - if (Document.Included == null || Document.Included.Count == 0) + if (Document.Included.IsNullOrEmpty()) { return; } @@ -99,7 +99,7 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA { // add attributes and relationships of a parsed HasMany relationship IEnumerable items = data.ManyData.Select(ParseIncludedRelationship); - IEnumerable values = TypeHelper.CopyToTypedCollection(items, hasManyAttr.Property.PropertyType); + IEnumerable values = CollectionConverter.CopyToTypedCollection(items, hasManyAttr.Property.PropertyType); hasManyAttr.SetValue(resource, values); } } @@ -135,13 +135,14 @@ private ResourceObject GetLinkedResource(ResourceIdentifierObject relatedResourc { try { - return Document.Included.SingleOrDefault(r => r.Type == relatedResourceIdentifier.Type && r.Id == relatedResourceIdentifier.Id); + return Document.Included.SingleOrDefault(resourceObject => + resourceObject.Type == relatedResourceIdentifier.Type && resourceObject.Id == relatedResourceIdentifier.Id); } - catch (InvalidOperationException e) + catch (InvalidOperationException exception) { throw new InvalidOperationException( "A compound document MUST NOT include more than one resource object for each type and ID pair." + - $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", e); + $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", exception); } } } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs index c2b32d0381..71ebee0a3f 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs @@ -64,7 +64,9 @@ public async Task ReadAsync(InputFormatterContext context) { throw ToInvalidRequestBodyException(exception, body); } +#pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException catch (Exception exception) +#pragma warning restore AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException { throw new InvalidRequestBodyException(null, null, body, exception); } @@ -181,7 +183,7 @@ private IEnumerable GetResourceTypesFromRequestBody(object model) { if (model is IEnumerable resourceCollection) { - return resourceCollection.Select(r => r.GetType()).Distinct(); + return resourceCollection.Select(resource => resource.GetType()).Distinct(); } return model == null ? Enumerable.Empty() : model.GetType().AsEnumerable(); diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs b/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs index bb37d29087..71f507e33e 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs @@ -53,7 +53,9 @@ public async Task WriteAsync(OutputFormatterWriteContext context) { responseContent = SerializeResponse(context.Object, (HttpStatusCode)response.StatusCode); } +#pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException catch (Exception exception) +#pragma warning restore AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException { ErrorDocument errorDocument = _exceptionHandler.HandleException(exception); responseContent = _serializer.Serialize(errorDocument); diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs index d558b94186..971fdecce3 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorDocument.cs @@ -30,7 +30,7 @@ public ErrorDocument(IEnumerable errors) public HttpStatusCode GetErrorStatusCode() { - int[] statusCodes = Errors.Select(e => (int)e.StatusCode).Distinct().ToArray(); + int[] statusCodes = Errors.Select(error => (int)error.StatusCode).Distinct().ToArray(); if (statusCodes.Length == 1) { diff --git a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs index df0e66f562..6324d01a62 100644 --- a/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/RequestDeserializer.cs @@ -44,7 +44,7 @@ public RequestDeserializer(IResourceContextProvider resourceContextProvider, IRe /// public object Deserialize(string body) { - ArgumentGuard.NotNull(body, nameof(body)); + ArgumentGuard.NotNullNorEmpty(body, nameof(body)); if (_request.Kind == EndpointKind.Relationship) { @@ -260,7 +260,7 @@ private void AssertCompatibleId(ResourceIdentifierObject resourceIdentifierObjec { try { - TypeHelper.ConvertType(resourceIdentifierObject.Id, idType); + RuntimeTypeConverter.ConvertType(resourceIdentifierObject.Id, idType); } catch (FormatException exception) { @@ -426,7 +426,7 @@ private void ParseDataForRelationship(RelationshipAttribute relationship, Resour secondaryResources.Add(secondaryResource); } - IEnumerable rightResources = TypeHelper.CopyToTypedCollection(secondaryResources, relationship.Property.PropertyType); + IEnumerable rightResources = CollectionConverter.CopyToTypedCollection(secondaryResources, relationship.Property.PropertyType); relationship.SetValue(primaryResource, rightResources); } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 0b472d9d6e..d75f6737bd 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -25,6 +25,7 @@ namespace JsonApiDotNetCore.Services public class JsonApiResourceService : IResourceService where TResource : class, IIdentifiable { + private readonly CollectionConverter _collectionConverter = new CollectionConverter(); private readonly IResourceRepositoryAccessor _repositoryAccessor; private readonly IQueryLayerComposer _queryLayerComposer; private readonly IPaginationContext _paginationContext; @@ -156,7 +157,7 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh relationshipName }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); AssertHasRelationship(_request.Relationship, relationshipName); @@ -244,7 +245,7 @@ private async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource re foreach ((QueryLayer queryLayer, RelationshipAttribute relationship) in _queryLayerComposer.ComposeForGetTargetedSecondaryResourceIds(resource)) { object rightValue = relationship.GetValue(resource); - ICollection rightResourceIds = TypeHelper.ExtractResources(rightValue); + ICollection rightResourceIds = _collectionConverter.ExtractResources(rightValue); IAsyncEnumerable missingResourcesInRelationship = GetMissingRightResourcesAsync(queryLayer, relationship, rightResourceIds, cancellationToken); @@ -286,7 +287,7 @@ public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshi secondaryResourceIds }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); AssertHasRelationship(_request.Relationship, relationshipName); @@ -325,7 +326,7 @@ private async Task RemoveExistingIdsFromSecondarySetAsync(TId primaryId, ISet existingRightResourceIds = TypeHelper.ExtractResources(rightValue); + ICollection existingRightResourceIds = _collectionConverter.ExtractResources(rightValue); secondaryResourceIds.ExceptWith(existingRightResourceIds); } @@ -401,7 +402,7 @@ public virtual async Task SetRelationshipAsync(TId primaryId, string relationshi secondaryResourceIds }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); AssertHasRelationship(_request.Relationship, relationshipName); @@ -415,7 +416,7 @@ public virtual async Task SetRelationshipAsync(TId primaryId, string relationshi } catch (DataStoreUpdateException) { - await AssertResourcesExistAsync(TypeHelper.ExtractResources(secondaryResourceIds), cancellationToken); + await AssertResourcesExistAsync(_collectionConverter.ExtractResources(secondaryResourceIds), cancellationToken); throw; } @@ -457,7 +458,7 @@ public async Task RemoveFromToManyRelationshipAsync(TId primaryId, string relati secondaryResourceIds }); - ArgumentGuard.NotNull(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); ArgumentGuard.NotNull(secondaryResourceIds, nameof(secondaryResourceIds)); AssertHasRelationship(_request.Relationship, relationshipName); diff --git a/src/JsonApiDotNetCore/TypeExtensions.cs b/src/JsonApiDotNetCore/TypeExtensions.cs new file mode 100644 index 0000000000..d25599b821 --- /dev/null +++ b/src/JsonApiDotNetCore/TypeExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq; + +namespace JsonApiDotNetCore +{ + internal static class TypeExtensions + { + /// + /// Whether the specified source type implements or equals the specified interface. + /// + public static bool IsOrImplementsInterface(this Type source, Type interfaceType) + { + ArgumentGuard.NotNull(interfaceType, nameof(interfaceType)); + + if (source == null) + { + return false; + } + + return source == interfaceType || source.GetInterfaces().Any(type => type == interfaceType); + } + } +} diff --git a/src/JsonApiDotNetCore/TypeHelper.cs b/src/JsonApiDotNetCore/TypeHelper.cs deleted file mode 100644 index b648a9387c..0000000000 --- a/src/JsonApiDotNetCore/TypeHelper.cs +++ /dev/null @@ -1,401 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCore -{ - internal static class TypeHelper - { - private static readonly Type[] HashSetCompatibleCollectionTypes = - { - typeof(HashSet<>), - typeof(ICollection<>), - typeof(ISet<>), - typeof(IEnumerable<>), - typeof(IReadOnlyCollection<>) - }; - - public static object ConvertType(object value, Type type) - { - ArgumentGuard.NotNull(type, nameof(type)); - - if (value == null) - { - if (!CanContainNull(type)) - { - throw new FormatException($"Failed to convert 'null' to type '{type.Name}'."); - } - - return null; - } - - Type runtimeType = value.GetType(); - - if (type == runtimeType || type.IsAssignableFrom(runtimeType)) - { - return value; - } - - string stringValue = value.ToString(); - - if (string.IsNullOrEmpty(stringValue)) - { - return GetDefaultValue(type); - } - - bool isNullableTypeRequested = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - Type nonNullableType = Nullable.GetUnderlyingType(type) ?? type; - - try - { - if (nonNullableType == typeof(Guid)) - { - Guid convertedValue = Guid.Parse(stringValue); - return isNullableTypeRequested ? (Guid?)convertedValue : convertedValue; - } - - if (nonNullableType == typeof(DateTimeOffset)) - { - DateTimeOffset convertedValue = DateTimeOffset.Parse(stringValue); - return isNullableTypeRequested ? (DateTimeOffset?)convertedValue : convertedValue; - } - - if (nonNullableType == typeof(TimeSpan)) - { - TimeSpan convertedValue = TimeSpan.Parse(stringValue); - return isNullableTypeRequested ? (TimeSpan?)convertedValue : convertedValue; - } - - if (nonNullableType.IsEnum) - { - object convertedValue = Enum.Parse(nonNullableType, stringValue); - - // https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html - return convertedValue; - } - - // https://bradwilson.typepad.com/blog/2008/07/creating-nullab.html - return Convert.ChangeType(stringValue, nonNullableType); - } - catch (Exception exception) when (exception is FormatException || exception is OverflowException || exception is InvalidCastException || - exception is ArgumentException) - { - throw new FormatException($"Failed to convert '{value}' of type '{runtimeType.Name}' to type '{type.Name}'.", exception); - } - } - - public static bool CanContainNull(Type type) - { - return !type.IsValueType || Nullable.GetUnderlyingType(type) != null; - } - - public static object GetDefaultValue(Type type) - { - return type.IsValueType ? CreateInstance(type) : null; - } - - public static Type TryGetCollectionElementType(Type type) - { - if (type != null) - { - if (type.IsGenericType && type.GenericTypeArguments.Length == 1) - { - if (IsOrImplementsInterface(type, typeof(IEnumerable))) - { - return type.GenericTypeArguments[0]; - } - } - } - - return null; - } - - /// - /// Gets the property info that is referenced in the NavigationAction expression. Credits: https://stackoverflow.com/a/17116267/4441216 - /// - public static PropertyInfo ParseNavigationExpression(Expression> navigationExpression) - { - ArgumentGuard.NotNull(navigationExpression, nameof(navigationExpression)); - - MemberExpression exp; - - //this line is necessary, because sometimes the expression comes in as Convert(originalExpression) - if (navigationExpression.Body is UnaryExpression unaryExpression) - { - if (unaryExpression.Operand is MemberExpression memberExpression) - { - exp = memberExpression; - } - else - { - throw new ArgumentException("Invalid expression.", nameof(navigationExpression)); - } - } - else if (navigationExpression.Body is MemberExpression memberExpression) - { - exp = memberExpression; - } - else - { - throw new ArgumentException("Invalid expression.", nameof(navigationExpression)); - } - - return (PropertyInfo)exp.Member; - } - - /// - /// Creates an instance of the specified generic type - /// - /// - /// The instance of the parameterized generic type - /// - /// - /// Generic type parameters to be used in open type. - /// - /// - /// Constructor arguments to be provided in instantiation. - /// - /// - /// Open generic type - /// - private static object CreateInstanceOfOpenType(Type openType, Type[] parameters, params object[] constructorArguments) - { - Type parameterizedType = openType.MakeGenericType(parameters); - return Activator.CreateInstance(parameterizedType, constructorArguments); - } - - /// - /// Helper method that "unboxes" the TValue from the relationship dictionary into - /// - public static Dictionary> ConvertRelationshipDictionary( - Dictionary relationships) - { - return relationships.ToDictionary(pair => pair.Key, pair => (HashSet)pair.Value); - } - - /// - /// Converts a dictionary of AttrAttributes to the underlying PropertyInfo that is referenced - /// - public static Dictionary> ConvertAttributeDictionary(IEnumerable attributes, - HashSet resources) - { - return attributes.ToDictionary(attr => attr.Property, _ => resources); - } - - /// - /// Creates an instance of the specified generic type - /// - /// - /// The instance of the parameterized generic type - /// - /// - /// Generic type parameter to be used in open type. - /// - /// - /// Constructor arguments to be provided in instantiation. - /// - /// - /// Open generic type - /// - public static object CreateInstanceOfOpenType(Type openType, Type parameter, params object[] constructorArguments) - { - return CreateInstanceOfOpenType(openType, parameter.AsArray(), constructorArguments); - } - - /// - /// Use this overload if you need to instantiate a type that has an internal constructor - /// - public static object CreateInstanceOfOpenType(Type openType, Type parameter, bool hasInternalConstructor, params object[] constructorArguments) - { - Type[] parameters = - { - parameter - }; - - if (!hasInternalConstructor) - { - return CreateInstanceOfOpenType(openType, parameters, constructorArguments); - } - - Type parameterizedType = openType.MakeGenericType(parameters); - // note that if for whatever reason the constructor of AffectedResource is set from - // internal to public, this will throw an error, as it is looking for a non-public one. - return Activator.CreateInstance(parameterizedType, BindingFlags.NonPublic | BindingFlags.Instance, null, constructorArguments, null); - } - - /// - /// Reflectively instantiates a list of a certain type. - /// - /// - /// The list of the target type - /// - /// - /// The target type - /// - public static IList CreateListFor(Type elementType) - { - return (IList)CreateInstanceOfOpenType(typeof(List<>), elementType); - } - - /// - /// Reflectively instantiates a hashset of a certain type. - /// - public static IEnumerable CreateHashSetFor(Type type, object elements) - { - return (IEnumerable)CreateInstanceOfOpenType(typeof(HashSet<>), type, elements); - } - - /// - /// Returns a compatible collection type that can be instantiated, for example IList{Article} -> List{Article} or ISet{Article} -> HashSet{Article} - /// - public static Type ToConcreteCollectionType(Type collectionType) - { - if (collectionType.IsInterface && collectionType.IsGenericType) - { - Type genericTypeDefinition = collectionType.GetGenericTypeDefinition(); - - if (genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(ISet<>) || - genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(IReadOnlyCollection<>)) - { - return typeof(HashSet<>).MakeGenericType(collectionType.GenericTypeArguments[0]); - } - - if (genericTypeDefinition == typeof(IList<>) || genericTypeDefinition == typeof(IReadOnlyList<>)) - { - return typeof(List<>).MakeGenericType(collectionType.GenericTypeArguments[0]); - } - } - - return collectionType; - } - - /// - /// Indicates whether a instance can be assigned to the specified type, for example IList{Article} -> false or ISet{Article} -> - /// true. - /// - public static bool TypeCanContainHashSet(Type collectionType) - { - if (collectionType.IsGenericType) - { - Type openCollectionType = collectionType.GetGenericTypeDefinition(); - return HashSetCompatibleCollectionTypes.Contains(openCollectionType); - } - - return false; - } - - /// - /// Gets the type (such as Guid or int) of the Id property on a type that implements . - /// - public static Type GetIdType(Type resourceType) - { - PropertyInfo property = resourceType.GetProperty(nameof(Identifiable.Id)); - - if (property == null) - { - throw new ArgumentException($"Type '{resourceType.Name}' does not have 'Id' property."); - } - - return property.PropertyType; - } - - public static ICollection ExtractResources(object value) - { - if (value is ICollection resourceCollection) - { - return resourceCollection; - } - - if (value is IEnumerable resources) - { - return resources.ToList(); - } - - if (value is IIdentifiable resource) - { - return resource.AsArray(); - } - - return Array.Empty(); - } - - public static object CreateInstance(Type type) - { - ArgumentGuard.NotNull(type, nameof(type)); - - try - { - return Activator.CreateInstance(type); - } - catch (Exception exception) - { - throw new InvalidOperationException($"Failed to create an instance of '{type.FullName}' using its default constructor.", exception); - } - } - - /// - /// Extension to use the LINQ cast method in a non-generic way: - /// - /// Type targetType = typeof(TResource) - /// ((IList)myList).CopyToList(targetType). - /// - /// - public static IList CopyToList(IEnumerable copyFrom, Type elementType, Converter elementConverter = null) - { - Type collectionType = typeof(List<>).MakeGenericType(elementType); - - if (elementConverter != null) - { - IEnumerable converted = copyFrom.Cast().Select(element => elementConverter(element)); - return (IList)CopyToTypedCollection(converted, collectionType); - } - - return (IList)CopyToTypedCollection(copyFrom, collectionType); - } - - /// - /// Creates a collection instance based on the specified collection type and copies the specified elements into it. - /// - /// - /// Source to copy from. - /// - /// - /// Target collection type, for example: typeof(List{Article}) or typeof(ISet{Person}). - /// - public static IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType) - { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(collectionType, nameof(collectionType)); - - Type concreteCollectionType = ToConcreteCollectionType(collectionType); - dynamic concreteCollectionInstance = CreateInstance(concreteCollectionType); - - foreach (object item in source) - { - concreteCollectionInstance.Add((dynamic)item); - } - - return concreteCollectionInstance; - } - - /// - /// Whether the specified source type implements or equals the specified interface. - /// - public static bool IsOrImplementsInterface(Type source, Type interfaceType) - { - ArgumentGuard.NotNull(interfaceType, nameof(interfaceType)); - - if (source == null) - { - return false; - } - - return source == interfaceType || source.GetInterfaces().Any(type => type == interfaceType); - } - } -} diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index ecdc3569d6..ef402b78b2 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -28,7 +28,7 @@ public sealed class ServiceDiscoveryFacadeTests public ServiceDiscoveryFacadeTests() { var dbResolverMock = new Mock(); - dbResolverMock.Setup(m => m.GetContext()).Returns(new Mock().Object); + dbResolverMock.Setup(resolver => resolver.GetContext()).Returns(new Mock().Object); _services.AddScoped(_ => dbResolverMock.Object); _services.AddSingleton(_options); diff --git a/test/JsonApiDotNetCoreExampleTests/ExampleFakers.cs b/test/JsonApiDotNetCoreExampleTests/ExampleFakers.cs index a67c525537..106eb8fe40 100644 --- a/test/JsonApiDotNetCoreExampleTests/ExampleFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/ExampleFakers.cs @@ -14,36 +14,36 @@ internal sealed class ExampleFakers : FakerContainer private readonly Lazy> _lazyAuthorFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(author => author.FirstName, f => f.Person.FirstName) - .RuleFor(author => author.LastName, f => f.Person.LastName)); + .RuleFor(author => author.FirstName, faker => faker.Person.FirstName) + .RuleFor(author => author.LastName, faker => faker.Person.LastName)); private readonly Lazy> _lazyArticleFaker = new Lazy>(() => new Faker
() .UseSeed(GetFakerSeed()) - .RuleFor(article => article.Caption, f => f.Lorem.Word()) - .RuleFor(article => article.Url, f => f.Internet.Url())); + .RuleFor(article => article.Caption, faker => faker.Lorem.Word()) + .RuleFor(article => article.Url, faker => faker.Internet.Url())); private readonly Lazy> _lazyUserFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(user => user.UserName, f => f.Person.UserName) - .RuleFor(user => user.Password, f => f.Internet.Password())); + .RuleFor(user => user.UserName, faker => faker.Person.UserName) + .RuleFor(user => user.Password, faker => faker.Internet.Password())); private readonly Lazy> _lazyTodoItemFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(todoItem => todoItem.Description, f => f.Random.Words())); + .RuleFor(todoItem => todoItem.Description, faker => faker.Random.Words())); private readonly Lazy> _lazyPersonFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(person => person.FirstName, f => f.Person.FirstName) - .RuleFor(person => person.LastName, f => f.Person.LastName)); + .RuleFor(person => person.FirstName, faker => faker.Person.FirstName) + .RuleFor(person => person.LastName, faker => faker.Person.LastName)); private readonly Lazy> _lazyTagFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(tag => tag.Name, f => f.Lorem.Word())); + .RuleFor(tag => tag.Name, faker => faker.Lorem.Word())); public Faker Author => _lazyAuthorFaker.Value; public Faker
Article => _lazyArticleFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs b/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs index 50e796e08d..96c92656db 100644 --- a/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/ExampleIntegrationTestContext.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using JsonApiDotNetCoreExample.Startups; using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; @@ -13,6 +14,7 @@ namespace JsonApiDotNetCoreExampleTests /// /// The EF Core database context, which can be defined in the test project. /// + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ExampleIntegrationTestContext : BaseIntegrationTestContext where TStartup : class where TDbContext : DbContext diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs index d652a8bed5..546cb84b03 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs @@ -22,38 +22,38 @@ internal sealed class OperationsFakers : FakerContainer private readonly Lazy> _lazyPlaylistFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(playlist => playlist.Name, f => f.Lorem.Sentence())); + .RuleFor(playlist => playlist.Name, faker => faker.Lorem.Sentence())); private readonly Lazy> _lazyMusicTrackFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(musicTrack => musicTrack.Title, f => f.Lorem.Word()) - .RuleFor(musicTrack => musicTrack.LengthInSeconds, f => f.Random.Decimal(3 * 60, 5 * 60)) - .RuleFor(musicTrack => musicTrack.Genre, f => f.Lorem.Word()) - .RuleFor(musicTrack => musicTrack.ReleasedAt, f => f.Date.PastOffset())); + .RuleFor(musicTrack => musicTrack.Title, faker => faker.Lorem.Word()) + .RuleFor(musicTrack => musicTrack.LengthInSeconds, faker => faker.Random.Decimal(3 * 60, 5 * 60)) + .RuleFor(musicTrack => musicTrack.Genre, faker => faker.Lorem.Word()) + .RuleFor(musicTrack => musicTrack.ReleasedAt, faker => faker.Date.PastOffset())); private readonly Lazy> _lazyLyricFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(lyric => lyric.Text, f => f.Lorem.Text()) + .RuleFor(lyric => lyric.Text, faker => faker.Lorem.Text()) .RuleFor(lyric => lyric.Format, "LRC")); private readonly Lazy> _lazyTextLanguageFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(textLanguage => textLanguage.IsoCode, f => f.PickRandom(LazyLanguageIsoCodes.Value))); + .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); private readonly Lazy> _lazyPerformerFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(performer => performer.ArtistName, f => f.Name.FullName()) - .RuleFor(performer => performer.BornAt, f => f.Date.PastOffset())); + .RuleFor(performer => performer.ArtistName, faker => faker.Name.FullName()) + .RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset())); private readonly Lazy> _lazyRecordCompanyFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(recordCompany => recordCompany.Name, f => f.Company.CompanyName()) - .RuleFor(recordCompany => recordCompany.CountryOfResidence, f => f.Address.Country())); + .RuleFor(recordCompany => recordCompany.Name, faker => faker.Company.CompanyName()) + .RuleFor(recordCompany => recordCompany.CountryOfResidence, faker => faker.Address.Country())); public Faker Playlist => _lazyPlaylistFaker.Value; public Faker MusicTrack => _lazyMusicTrackFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteFakers.cs index 2c15eb3e30..9bd81abec4 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteFakers.cs @@ -12,14 +12,14 @@ internal sealed class CustomRouteFakers : FakerContainer private readonly Lazy> _lazyTownFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(town => town.Name, f => f.Address.City()) - .RuleFor(town => town.Latitude, f => f.Address.Latitude()) - .RuleFor(town => town.Longitude, f => f.Address.Longitude())); + .RuleFor(town => town.Name, faker => faker.Address.City()) + .RuleFor(town => town.Latitude, faker => faker.Address.Latitude()) + .RuleFor(town => town.Longitude, faker => faker.Address.Longitude())); private readonly Lazy> _lazyCivilianFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(civilian => civilian.Name, f => f.Person.FullName)); + .RuleFor(civilian => civilian.Name, faker => faker.Person.FullName)); public Faker Town => _lazyTownFaker.Value; public Faker Civilian => _lazyCivilianFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs index 7bee8663e2..9663c6c830 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs @@ -28,6 +28,7 @@ public async Task Can_get_resource_at_custom_route() { // Arrange Town town = _fakers.Town.Generate(); + town.Civilians = _fakers.Civilian.Generate(1).ToHashSet(); await _testContext.RunOnDatabaseAsync(async dbContext => { diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingFakers.cs index 5910102644..bb41ba2449 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/EagerLoadingFakers.cs @@ -12,33 +12,33 @@ internal sealed class EagerLoadingFakers : FakerContainer private readonly Lazy> _lazyStateFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(state => state.Name, f => f.Address.City())); + .RuleFor(state => state.Name, faker => faker.Address.City())); private readonly Lazy> _lazyCityFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(city => city.Name, f => f.Address.City())); + .RuleFor(city => city.Name, faker => faker.Address.City())); private readonly Lazy> _lazyStreetFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(street => street.Name, f => f.Address.StreetName())); + .RuleFor(street => street.Name, faker => faker.Address.StreetName())); private readonly Lazy> _lazyBuildingFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(building => building.Number, f => f.Address.BuildingNumber())); + .RuleFor(building => building.Number, faker => faker.Address.BuildingNumber())); private readonly Lazy> _lazyWindowFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(window => window.HeightInCentimeters, f => f.Random.Number(30, 199)) - .RuleFor(window => window.WidthInCentimeters, f => f.Random.Number(30, 199))); + .RuleFor(window => window.HeightInCentimeters, faker => faker.Random.Number(30, 199)) + .RuleFor(window => window.WidthInCentimeters, faker => faker.Random.Number(30, 199))); private readonly Lazy> _lazyDoorFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(door => door.Color, f => f.Commerce.Color())); + .RuleFor(door => door.Color, faker => faker.Commerce.Color())); public Faker State => _lazyStateFaker.Value; public Faker City => _lazyCityFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs index 0bbe24f429..b67b79ab6e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/HostingInIIS/HostingFakers.cs @@ -12,12 +12,12 @@ internal sealed class HostingFakers : FakerContainer private readonly Lazy> _lazyArtGalleryFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(artGallery => artGallery.Theme, f => f.Lorem.Word())); + .RuleFor(artGallery => artGallery.Theme, faker => faker.Lorem.Word())); private readonly Lazy> _lazyPaintingFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(painting => painting.Title, f => f.Lorem.Sentence())); + .RuleFor(painting => painting.Title, faker => faker.Lorem.Sentence())); public Faker ArtGallery => _lazyArtGalleryFaker.Value; public Faker Painting => _lazyPaintingFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs index 19dfbea6b0..a84be616b1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/HexadecimalCodec.cs @@ -8,9 +8,9 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { - internal static class HexadecimalCodec + internal sealed class HexadecimalCodec { - public static int Decode(string value) + public int Decode(string value) { if (value == null) { @@ -45,7 +45,7 @@ private static string FromHexString(string hexString) return new string(chars); } - public static string Encode(int value) + public string Encode(int value) { if (value == 0) { diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs index 99da5dacb9..99e569f724 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs @@ -59,7 +59,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - string route = $"/bankAccounts?filter=any(id,'{accounts[1].StringId}','{HexadecimalCodec.Encode(99999999)}')"; + var codec = new HexadecimalCodec(); + string route = $"/bankAccounts?filter=any(id,'{accounts[1].StringId}','{codec.Encode(99999999)}')"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -244,7 +245,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.SingleData.Attributes["ownerName"].Should().Be(newCard.OwnerName); responseDocument.SingleData.Attributes["pinCode"].Should().Be(newCard.PinCode); - int newCardId = HexadecimalCodec.Decode(responseDocument.SingleData.Id); + var codec = new HexadecimalCodec(); + int newCardId = codec.Decode(responseDocument.SingleData.Id); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -448,7 +450,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_delete_missing_resource() { // Arrange - string stringId = HexadecimalCodec.Encode(99999999); + var codec = new HexadecimalCodec(); + string stringId = codec.Encode(99999999); string route = "/bankAccounts/" + stringId; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiable.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiable.cs index ffe9baa52a..e0e728a938 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiable.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiable.cs @@ -4,14 +4,16 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { public abstract class ObfuscatedIdentifiable : Identifiable { + private static readonly HexadecimalCodec Codec = new HexadecimalCodec(); + protected override string GetStringId(int value) { - return HexadecimalCodec.Encode(value); + return Codec.Encode(value); } protected override int GetTypedId(string value) { - return HexadecimalCodec.Decode(value); + return Codec.Decode(value); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index ee6e3de278..ab4a02a70f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -13,6 +13,8 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation public abstract class ObfuscatedIdentifiableController : BaseJsonApiController where TResource : class, IIdentifiable { + private readonly HexadecimalCodec _codec = new HexadecimalCodec(); + protected ObfuscatedIdentifiableController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { @@ -27,21 +29,21 @@ public override Task GetAsync(CancellationToken cancellationToken [HttpGet("{id}")] public Task GetAsync(string id, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.GetAsync(idValue, cancellationToken); } [HttpGet("{id}/{relationshipName}")] public Task GetSecondaryAsync(string id, string relationshipName, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.GetSecondaryAsync(idValue, relationshipName, cancellationToken); } [HttpGet("{id}/relationships/{relationshipName}")] public Task GetRelationshipAsync(string id, string relationshipName, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.GetRelationshipAsync(idValue, relationshipName, cancellationToken); } @@ -55,14 +57,14 @@ public override Task PostAsync([FromBody] TResource resource, Can public Task PostRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.PostRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); } [HttpPatch("{id}")] public Task PatchAsync(string id, [FromBody] TResource resource, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.PatchAsync(idValue, resource, cancellationToken); } @@ -70,14 +72,14 @@ public Task PatchAsync(string id, [FromBody] TResource resource, public Task PatchRelationshipAsync(string id, string relationshipName, [FromBody] object secondaryResourceIds, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.PatchRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); } [HttpDelete("{id}")] public Task DeleteAsync(string id, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.DeleteAsync(idValue, cancellationToken); } @@ -85,7 +87,7 @@ public Task DeleteAsync(string id, CancellationToken cancellation public Task DeleteRelationshipAsync(string id, string relationshipName, [FromBody] ISet secondaryResourceIds, CancellationToken cancellationToken) { - int idValue = HexadecimalCodec.Decode(id); + int idValue = _codec.Decode(id); return base.DeleteRelationshipAsync(idValue, relationshipName, secondaryResourceIds, cancellationToken); } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscationFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscationFakers.cs index d6a08114e4..ed064f2956 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscationFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscationFakers.cs @@ -12,13 +12,13 @@ internal sealed class ObfuscationFakers : FakerContainer private readonly Lazy> _lazyBankAccountFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(bankAccount => bankAccount.Iban, f => f.Finance.Iban())); + .RuleFor(bankAccount => bankAccount.Iban, faker => faker.Finance.Iban())); private readonly Lazy> _lazyDebitCardFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(debitCard => debitCard.OwnerName, f => f.Name.FullName()) - .RuleFor(debitCard => debitCard.PinCode, f => (short)f.Random.Number(1000, 9999))); + .RuleFor(debitCard => debitCard.OwnerName, faker => faker.Name.FullName()) + .RuleFor(debitCard => debitCard.PinCode, faker => (short)faker.Random.Number(1000, 9999))); public Faker BankAccount => _lazyBankAccountFaker.Value; public Faker DebitCard => _lazyDebitCardFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs index b0ade264dc..bd6ee4acb3 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs @@ -12,19 +12,19 @@ internal sealed class LinksFakers : FakerContainer private readonly Lazy> _lazyPhotoAlbumFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(photoAlbum => photoAlbum.Name, f => f.Lorem.Sentence())); + .RuleFor(photoAlbum => photoAlbum.Name, faker => faker.Lorem.Sentence())); private readonly Lazy> _lazyPhotoFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(photo => photo.Url, f => f.Image.PlaceImgUrl())); + .RuleFor(photo => photo.Url, faker => faker.Image.PlaceImgUrl())); private readonly Lazy> _lazyPhotoLocationFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(photoLocation => photoLocation.PlaceName, f => f.Address.FullAddress()) - .RuleFor(photoLocation => photoLocation.Latitude, f => f.Address.Latitude()) - .RuleFor(photoLocation => photoLocation.Longitude, f => f.Address.Longitude())); + .RuleFor(photoLocation => photoLocation.PlaceName, faker => faker.Address.FullAddress()) + .RuleFor(photoLocation => photoLocation.Latitude, faker => faker.Address.Latitude()) + .RuleFor(photoLocation => photoLocation.Longitude, faker => faker.Address.Longitude())); public Faker PhotoAlbum => _lazyPhotoAlbumFaker.Value; public Faker Photo => _lazyPhotoFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditFakers.cs index ea3e4c79a4..702411dddd 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Logging/AuditFakers.cs @@ -12,8 +12,8 @@ internal sealed class AuditFakers : FakerContainer private readonly Lazy> _lazyAuditEntryFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(auditEntry => auditEntry.UserName, f => f.Internet.UserName()) - .RuleFor(auditEntry => auditEntry.CreatedAt, f => f.Date.PastOffset())); + .RuleFor(auditEntry => auditEntry.UserName, faker => faker.Internet.UserName()) + .RuleFor(auditEntry => auditEntry.CreatedAt, faker => faker.Date.PastOffset())); public Faker AuditEntry => _lazyAuditEntryFaker.Value; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportFakers.cs index 59de00b55c..797696c8f6 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Meta/SupportFakers.cs @@ -12,12 +12,12 @@ internal sealed class SupportFakers : FakerContainer private readonly Lazy> _lazyProductFamilyFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(productFamily => productFamily.Name, f => f.Commerce.ProductName())); + .RuleFor(productFamily => productFamily.Name, faker => faker.Commerce.ProductName())); private readonly Lazy> _lazySupportTicketFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(supportTicket => supportTicket.Description, f => f.Lorem.Paragraph())); + .RuleFor(supportTicket => supportTicket.Description, faker => faker.Lorem.Paragraph())); public Faker ProductFamily => _lazyProductFamilyFaker.Value; public Faker SupportTicket => _lazySupportTicketFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs index c130f596b6..7e76eb5617 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/ModelStateDbContext.cs @@ -20,7 +20,7 @@ protected override void OnModelCreating(ModelBuilder builder) { builder.Entity() .HasMany(systemDirectory => systemDirectory.Subdirectories) - .WithOne(x => x.Parent); + .WithOne(systemDirectory => systemDirectory.Parent); builder.Entity() .HasOne(systemDirectory => systemDirectory.Self) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingFakers.cs index 86ecc8d1ad..13debc6ca7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/NamingConventions/SwimmingFakers.cs @@ -12,17 +12,17 @@ internal sealed class SwimmingFakers : FakerContainer private readonly Lazy> _lazySwimmingPoolFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(swimmingPool => swimmingPool.IsIndoor, f => f.Random.Bool())); + .RuleFor(swimmingPool => swimmingPool.IsIndoor, faker => faker.Random.Bool())); private readonly Lazy> _lazyWaterSlideFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(waterSlide => waterSlide.LengthInMeters, f => f.Random.Decimal(3, 100))); + .RuleFor(waterSlide => waterSlide.LengthInMeters, faker => faker.Random.Decimal(3, 100))); private readonly Lazy> _lazyDivingBoardFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(divingBoard => divingBoard.HeightInMeters, f => f.Random.Decimal(1, 15))); + .RuleFor(divingBoard => divingBoard.HeightInMeters, faker => faker.Random.Decimal(1, 15))); public Faker SwimmingPool => _lazySwimmingPoolFaker.Value; public Faker WaterSlide => _lazyWaterSlideFaker.Value; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/QueryStringFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/QueryStringFakers.cs index b2c878853e..32591440c7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/QueryStringFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/QueryStringFakers.cs @@ -12,53 +12,53 @@ internal sealed class QueryStringFakers : FakerContainer private readonly Lazy> _lazyBlogFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(blog => blog.Title, f => f.Lorem.Word()) - .RuleFor(blog => blog.PlatformName, f => f.Company.CompanyName())); + .RuleFor(blog => blog.Title, faker => faker.Lorem.Word()) + .RuleFor(blog => blog.PlatformName, faker => faker.Company.CompanyName())); private readonly Lazy> _lazyBlogPostFaker = new Lazy>(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(blogPost => blogPost.Caption, f => f.Lorem.Sentence()) - .RuleFor(blogPost => blogPost.Url, f => f.Internet.Url())); + .RuleFor(blogPost => blogPost.Caption, faker => faker.Lorem.Sentence()) + .RuleFor(blogPost => blogPost.Url, faker => faker.Internet.Url())); private readonly Lazy> _lazyLabelFaker = new Lazy>(() => new Faker