From 71e53981ae74b33db338635ea5de5c90f3f80cce Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 6 Apr 2022 20:05:01 +0200 Subject: [PATCH 01/50] Increment version number (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5b311343c7..403d433302 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ 6.0.* 4.1.* 2.14.1 - 5.0.1 + 5.0.2 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable From 59f319f3c29c64e55f3aa5dc310ef57200f41d46 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 29 Apr 2022 22:30:45 +0200 Subject: [PATCH 02/50] Package updates --- Directory.Build.props | 4 ++-- .../Configuration/ServiceCollectionExtensions.cs | 2 -- .../Queries/Expressions/QueryableHandlerExpression.cs | 2 -- .../Repositories/EntityFrameworkCoreRepository.cs | 4 ---- test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +- 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 403d433302..55df28cab3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,8 +16,8 @@ - - + + diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs index 8e5d15a7c4..8b5356fdef 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs @@ -6,8 +6,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection - namespace JsonApiDotNetCore.Configuration; [PublicAPI] diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs index 4cd035ba2d..1d9c910955 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs @@ -21,10 +21,8 @@ public QueryableHandlerExpression(object queryableHandler, StringValues paramete _parameterValue = parameterValue; } -#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection public IQueryable Apply(IQueryable query) where TResource : class, IIdentifiable -#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { var handler = (Func, StringValues, IQueryable>)_queryableHandler; return handler(query, _parameterValue); diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 7384cec693..358f63a2cd 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -105,9 +105,7 @@ public virtual async Task CountAsync(FilterExpression? filter, Cancellation } } -#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection protected virtual IQueryable ApplyQueryLayer(QueryLayer queryLayer) -#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { _traceWriter.LogMethodStart(new { @@ -151,9 +149,7 @@ protected virtual IQueryable ApplyQueryLayer(QueryLayer queryLayer) } } -#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection protected virtual IQueryable GetAll() -#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { return _dbContext.Set(); } diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 3a8655508c..68db5c994c 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -10,7 +10,7 @@ - + From bba90e55feaa197e8b51c603b56e5b75a5876a15 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 11 May 2022 21:16:06 +0200 Subject: [PATCH 03/50] Fixed broken link --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3e23a87e27..1a1c618dd7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,6 @@ Closes #{ISSUE_NUMBER} #### QUALITY CHECKLIST - [ ] Changes implemented in code -- [ ] Complies with our [contributing guidelines](./.github/CONTRIBUTING.md) +- [ ] Complies with our [contributing guidelines](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md) - [ ] Adapted tests - [ ] Documentation updated From 9e0fed89f5878c9e6629b7ebb5a8d4f2a53738f1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 21 May 2022 15:24:18 +0200 Subject: [PATCH 04/50] Removed workaround for debugging source generators (#1155) * Removed workaround for https://github.com/dotnet/roslyn/issues/55802. The bug has been fixed in VS 2022 v17.2. * Package updates (in non-breaking fashion) --- Directory.Build.props | 6 +- JsonApiDotNetCore.sln | 15 ----- .../Annotations/ResourceAttribute.cs | 3 - .../ControllerSourceGenerator.cs | 6 ++ .../JsonApiDotNetCore.SourceGenerators.csproj | 2 +- .../Properties/launchSettings.json | 2 +- .../Controllers/ArticlesController.cs | 16 ----- .../Controllers/CustomersController.cs | 18 ------ .../JsonApiDotNetCore/ArgumentGuard.cs | 19 ------ .../JsonApiDotNetCore/AttrAttribute.cs | 15 ----- .../BaseJsonApiController.cs | 41 ------------- .../IAddToRelationshipService.cs | 18 ------ .../JsonApiDotNetCore/ICreateService.cs | 18 ------ .../JsonApiDotNetCore/IDeleteService.cs | 18 ------ .../JsonApiDotNetCore/IGetAllService.cs | 18 ------ .../JsonApiDotNetCore/IGetByIdService.cs | 18 ------ .../IGetRelationshipService.cs | 18 ------ .../JsonApiDotNetCore/IGetSecondaryService.cs | 18 ------ .../JsonApiDotNetCore/IIdentifiable.cs | 23 -------- .../JsonApiDotNetCore/IJsonApiOptions.cs | 15 ----- .../IRemoveFromRelationshipService.cs | 18 ------ .../IResourceCommandService.cs | 19 ------ .../JsonApiDotNetCore/IResourceGraph.cs | 15 ----- .../IResourceQueryService.cs | 18 ------ .../ISetRelationshipService.cs | 18 ------ .../JsonApiDotNetCore/IUpdateService.cs | 18 ------ .../JsonApiDotNetCore/Identifiable.cs | 19 ------ .../JsonApiCommandController.cs | 25 -------- .../JsonApiDotNetCore/JsonApiController.cs | 36 ------------ .../JsonApiDotNetCore/JsonApiOptions.cs | 16 ----- .../JsonApiQueryController.cs | 25 -------- .../JsonApiResourceService.cs | 17 ------ .../JsonApiDotNetCore/ResourceGraph.cs | 15 ----- .../ResourceServiceInterfaces.cs | 17 ------ .../SourceGeneratorDebugger/Models/Account.cs | 14 ----- .../SourceGeneratorDebugger/Models/Article.cs | 14 ----- .../Models/Customer.cs | 12 ---- test/SourceGeneratorDebugger/Models/Global.cs | 14 ----- test/SourceGeneratorDebugger/Models/Login.cs | 28 --------- test/SourceGeneratorDebugger/Models/Order.cs | 14 ----- .../Models/SimpleNamespace.cs | 15 ----- test/SourceGeneratorDebugger/Program.cs | 58 ------------------- .../SourceGeneratorDebugger.csproj | 28 --------- .../TestBuildingBlocks.csproj | 4 +- 44 files changed, 13 insertions(+), 771 deletions(-) delete mode 100644 test/SourceGeneratorDebugger/Controllers/ArticlesController.cs delete mode 100644 test/SourceGeneratorDebugger/Controllers/CustomersController.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/ArgumentGuard.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/AttrAttribute.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/BaseJsonApiController.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IAddToRelationshipService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/ICreateService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IDeleteService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetAllService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetByIdService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetRelationshipService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetSecondaryService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IIdentifiable.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IJsonApiOptions.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IRemoveFromRelationshipService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceCommandService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceGraph.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceQueryService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/ISetRelationshipService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/IUpdateService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/Identifiable.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiCommandController.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiController.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiOptions.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiQueryController.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiResourceService.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceGraph.cs delete mode 100644 test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceServiceInterfaces.cs delete mode 100644 test/SourceGeneratorDebugger/Models/Account.cs delete mode 100644 test/SourceGeneratorDebugger/Models/Article.cs delete mode 100644 test/SourceGeneratorDebugger/Models/Customer.cs delete mode 100644 test/SourceGeneratorDebugger/Models/Global.cs delete mode 100644 test/SourceGeneratorDebugger/Models/Login.cs delete mode 100644 test/SourceGeneratorDebugger/Models/Order.cs delete mode 100644 test/SourceGeneratorDebugger/Models/SimpleNamespace.cs delete mode 100644 test/SourceGeneratorDebugger/Program.cs delete mode 100644 test/SourceGeneratorDebugger/SourceGeneratorDebugger.csproj diff --git a/Directory.Build.props b/Directory.Build.props index 55df28cab3..26ff3e7175 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ 6.0.* 6.0.* 6.0.* - 4.1.* + 4.2.* 2.14.1 5.0.2 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset @@ -34,7 +34,7 @@ 3.1.2 - 4.17.2 - 17.1.0 + 4.18.1 + 17.2.0 diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln index 0a8ed12d2a..21b1cca7ce 100644 --- a/JsonApiDotNetCore.sln +++ b/JsonApiDotNetCore.sln @@ -46,8 +46,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBuildingBlocks", "test\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.SourceGenerators", "src\JsonApiDotNetCore.SourceGenerators\JsonApiDotNetCore.SourceGenerators.csproj", "{952C0FDE-AFC8-455C-986F-6CC882ED8953}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorDebugger", "test\SourceGeneratorDebugger\SourceGeneratorDebugger.csproj", "{87D066F9-3540-4AC7-A748-134900969EE5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorTests", "test\SourceGeneratorTests\SourceGeneratorTests.csproj", "{0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.Annotations", "src\JsonApiDotNetCore.Annotations\JsonApiDotNetCore.Annotations.csproj", "{83FF097C-C8C6-477B-9FAB-DF99B84978B5}" @@ -232,18 +230,6 @@ Global {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x64.Build.0 = Release|Any CPU {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x86.ActiveCfg = Release|Any CPU {952C0FDE-AFC8-455C-986F-6CC882ED8953}.Release|x86.Build.0 = Release|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x64.ActiveCfg = Debug|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x64.Build.0 = Debug|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x86.ActiveCfg = Debug|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Debug|x86.Build.0 = Debug|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Release|Any CPU.Build.0 = Release|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x64.ActiveCfg = Release|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x64.Build.0 = Release|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x86.ActiveCfg = Release|Any CPU - {87D066F9-3540-4AC7-A748-134900969EE5}.Release|x86.Build.0 = Release|Any CPU {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -299,7 +285,6 @@ Global {EC3202C6-1D4C-4B14-A599-B9D3F27FE3BA} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {210FD61E-FF5D-4CEE-8E0D-C739ECCCBA21} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {952C0FDE-AFC8-455C-986F-6CC882ED8953} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} - {87D066F9-3540-4AC7-A748-134900969EE5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {83FF097C-C8C6-477B-9FAB-DF99B84978B5} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} {60334658-BE51-43B3-9C4D-F2BBF56C89CE} = {026FBC6C-AF76-4568-9B87-EC73457899FD} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs index ca517e3e99..72669de585 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs @@ -1,9 +1,6 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Controllers; -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - namespace JsonApiDotNetCore.Resources.Annotations; /// diff --git a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs index 6728fd537c..65800bba82 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs @@ -13,6 +13,12 @@ namespace JsonApiDotNetCore.SourceGenerators { + // To debug in Visual Studio (requires v17.2 or higher): + // - Set JsonApiDotNetCore.SourceGenerators as startup project + // - Add a breakpoint at the start of the Initialize or Execute method + // - Optional: change targetProject in Properties\launchSettings.json + // - Press F5 + [Generator(LanguageNames.CSharp)] public sealed class ControllerSourceGenerator : ISourceGenerator { diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj index 9f7c0b85dd..bcd8c06b0a 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj +++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj @@ -48,6 +48,6 @@ - + diff --git a/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json b/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json index 2679b059a9..03635841ec 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json +++ b/src/JsonApiDotNetCore.SourceGenerators/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "JsonApiDotNetCore.SourceGenerators": { "commandName": "DebugRoslynComponent", - "targetProject": "..\\..\\test\\SourceGeneratorDebugger\\SourceGeneratorDebugger.csproj" + "targetProject": "..\\Examples\\JsonApiDotNetCoreExample\\JsonApiDotNetCoreExample.csproj" } } } diff --git a/test/SourceGeneratorDebugger/Controllers/ArticlesController.cs b/test/SourceGeneratorDebugger/Controllers/ArticlesController.cs deleted file mode 100644 index 1ceafa5d02..0000000000 --- a/test/SourceGeneratorDebugger/Controllers/ArticlesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JetBrains.Annotations; - -namespace SourceGeneratorDebugger.Controllers; - -// Workaround for https://youtrack.jetbrains.com/issue/RSRP-487028 -public partial class ArticlesController -{ -} - -[PublicAPI] -partial class ArticlesController -{ - public void ExtraMethod() - { - } -} diff --git a/test/SourceGeneratorDebugger/Controllers/CustomersController.cs b/test/SourceGeneratorDebugger/Controllers/CustomersController.cs deleted file mode 100644 index a10de41483..0000000000 --- a/test/SourceGeneratorDebugger/Controllers/CustomersController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; -using SourceGeneratorDebugger.Models; - -namespace SourceGeneratorDebugger.Controllers; - -[PublicAPI] -public sealed class CustomersController : JsonApiController -{ - public CustomersController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ArgumentGuard.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/ArgumentGuard.cs deleted file mode 100644 index e806a3caa1..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ArgumentGuard.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name -#pragma warning disable AV1008 // Class should not be static - -namespace JsonApiDotNetCore; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -internal static class ArgumentGuard -{ - public static void NotNullNorEmpty(string? value, string name) - { - } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/AttrAttribute.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/AttrAttribute.cs deleted file mode 100644 index d8201a0a59..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/AttrAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Resources.Annotations; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public sealed class AttrAttribute : Attribute -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/BaseJsonApiController.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/BaseJsonApiController.cs deleted file mode 100644 index 2cd3fd0cd6..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/BaseJsonApiController.cs +++ /dev/null @@ -1,41 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Controllers; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public abstract class BaseJsonApiController - where TResource : class, IIdentifiable -{ - protected BaseJsonApiController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : this(options, resourceGraph, loggerFactory, resourceService, resourceService) - { - } - - protected BaseJsonApiController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceQueryService? queryService = null, IResourceCommandService? commandService = null) - : this(options, resourceGraph, loggerFactory, queryService, queryService, queryService, queryService, commandService, commandService, commandService, - commandService, commandService, commandService) - { - } - - protected BaseJsonApiController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IGetAllService? getAll = null, IGetByIdService? getById = null, - IGetSecondaryService? getSecondary = null, IGetRelationshipService? getRelationship = null, - ICreateService? create = null, IAddToRelationshipService? addToRelationship = null, - IUpdateService? update = null, ISetRelationshipService? setRelationship = null, - IDeleteService? delete = null, IRemoveFromRelationshipService? removeFromRelationship = null) - { - } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IAddToRelationshipService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IAddToRelationshipService.cs deleted file mode 100644 index 4bdfbe99ab..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IAddToRelationshipService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IAddToRelationshipService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ICreateService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/ICreateService.cs deleted file mode 100644 index 3e1d7525a3..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ICreateService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface ICreateService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IDeleteService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IDeleteService.cs deleted file mode 100644 index 10a1092bd9..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IDeleteService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IDeleteService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetAllService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetAllService.cs deleted file mode 100644 index 9a6644e428..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetAllService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IGetAllService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetByIdService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetByIdService.cs deleted file mode 100644 index f59885af15..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetByIdService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IGetByIdService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetRelationshipService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetRelationshipService.cs deleted file mode 100644 index 7e28fe1cb1..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetRelationshipService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IGetRelationshipService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetSecondaryService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetSecondaryService.cs deleted file mode 100644 index 3d2d0fd381..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IGetSecondaryService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IGetSecondaryService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IIdentifiable.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IIdentifiable.cs deleted file mode 100644 index a5a857cf3e..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IIdentifiable.cs +++ /dev/null @@ -1,23 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Resources; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IIdentifiable -{ - string? StringId { get; set; } - string? LocalId { get; set; } -} - -[PublicAPI] -public interface IIdentifiable : IIdentifiable -{ - TId Id { get; set; } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IJsonApiOptions.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IJsonApiOptions.cs deleted file mode 100644 index 5d076dd4ca..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IJsonApiOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Configuration; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IJsonApiOptions -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IRemoveFromRelationshipService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IRemoveFromRelationshipService.cs deleted file mode 100644 index dd071fdc6e..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IRemoveFromRelationshipService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IRemoveFromRelationshipService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceCommandService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceCommandService.cs deleted file mode 100644 index e7fcebc608..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceCommandService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IResourceCommandService - : ICreateService, IAddToRelationshipService, IUpdateService, ISetRelationshipService, - IDeleteService, IRemoveFromRelationshipService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceGraph.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceGraph.cs deleted file mode 100644 index 7d1f1562ba..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceGraph.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Configuration; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IResourceGraph -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceQueryService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceQueryService.cs deleted file mode 100644 index b734963ad3..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IResourceQueryService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IResourceQueryService - : IGetAllService, IGetByIdService, IGetRelationshipService, IGetSecondaryService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ISetRelationshipService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/ISetRelationshipService.cs deleted file mode 100644 index 2cd43448bd..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ISetRelationshipService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface ISetRelationshipService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IUpdateService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/IUpdateService.cs deleted file mode 100644 index 974335097c..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/IUpdateService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable UnusedTypeParameter -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IUpdateService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/Identifiable.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/Identifiable.cs deleted file mode 100644 index 6e78d67867..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/Identifiable.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Resources; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public abstract class Identifiable : IIdentifiable -{ - public virtual TId Id { get; set; } = default!; - - public string? StringId { get; set; } - public string? LocalId { get; set; } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiCommandController.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiCommandController.cs deleted file mode 100644 index 31736e8088..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiCommandController.cs +++ /dev/null @@ -1,25 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Controllers; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public abstract class JsonApiCommandController : BaseJsonApiController - where TResource : class, IIdentifiable -{ - protected JsonApiCommandController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceCommandService commandService) - : base(options, resourceGraph, loggerFactory, null, commandService) - { - } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiController.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiController.cs deleted file mode 100644 index 9d7d1d6e9c..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Controllers; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public abstract class JsonApiController : BaseJsonApiController - where TResource : class, IIdentifiable -{ - protected JsonApiController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, resourceGraph, loggerFactory, resourceService) - { - } - - protected JsonApiController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IGetAllService? getAll = null, IGetByIdService? getById = null, - IGetSecondaryService? getSecondary = null, IGetRelationshipService? getRelationship = null, - ICreateService? create = null, IAddToRelationshipService? addToRelationship = null, - IUpdateService? update = null, ISetRelationshipService? setRelationship = null, - IDeleteService? delete = null, IRemoveFromRelationshipService? removeFromRelationship = null) - : base(options, resourceGraph, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, setRelationship, - delete, removeFromRelationship) - { - } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiOptions.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiOptions.cs deleted file mode 100644 index bad4ca2a28..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ReSharper disable CheckNamespace - -using JetBrains.Annotations; - -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Configuration; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public sealed class JsonApiOptions : IJsonApiOptions -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiQueryController.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiQueryController.cs deleted file mode 100644 index cebb57e7bc..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiQueryController.cs +++ /dev/null @@ -1,25 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Controllers; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public abstract class JsonApiQueryController : BaseJsonApiController - where TResource : class, IIdentifiable -{ - protected JsonApiQueryController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, - IResourceQueryService queryService) - : base(options, resourceGraph, loggerFactory, queryService) - { - } -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiResourceService.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiResourceService.cs deleted file mode 100644 index 118f5d315d..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/JsonApiResourceService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public sealed class JsonApiResourceService : IResourceService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceGraph.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceGraph.cs deleted file mode 100644 index 5be951c9f4..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceGraph.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Configuration; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public sealed class ResourceGraph : IResourceGraph -{ -} diff --git a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceServiceInterfaces.cs b/test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceServiceInterfaces.cs deleted file mode 100644 index 72cd018d77..0000000000 --- a/test/SourceGeneratorDebugger/JsonApiDotNetCore/ResourceServiceInterfaces.cs +++ /dev/null @@ -1,17 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace JsonApiDotNetCore.Services; - -/// -/// Represents a stripped-down copy of this type in the JsonApiDotNetCore project. It exists solely to fulfill the dependency needs for successfully -/// compiling the source-generated controllers in this project. -/// -[PublicAPI] -public interface IResourceService : IResourceCommandService, IResourceQueryService - where TResource : class, IIdentifiable -{ -} diff --git a/test/SourceGeneratorDebugger/Models/Account.cs b/test/SourceGeneratorDebugger/Models/Account.cs deleted file mode 100644 index 360f6f837f..0000000000 --- a/test/SourceGeneratorDebugger/Models/Account.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace SourceGeneratorDebugger.Models; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(GenerateControllerEndpoints = JsonApiEndpoints.Query, ControllerNamespace = "Some.Namespace.To.Place.Controllers")] -public sealed class Account : Identifiable -{ - [Attr] - public string? DisplayName { get; set; } -} diff --git a/test/SourceGeneratorDebugger/Models/Article.cs b/test/SourceGeneratorDebugger/Models/Article.cs deleted file mode 100644 index fd28b80494..0000000000 --- a/test/SourceGeneratorDebugger/Models/Article.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace SourceGeneratorDebugger.Models; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(GenerateControllerEndpoints = JsonApiEndpoints.GetCollection | JsonApiEndpoints.GetSingle | JsonApiEndpoints.GetSecondary)] -public sealed class Article : Identifiable -{ - [Attr] - public string? DisplayName { get; set; } -} diff --git a/test/SourceGeneratorDebugger/Models/Customer.cs b/test/SourceGeneratorDebugger/Models/Customer.cs deleted file mode 100644 index 26d869c550..0000000000 --- a/test/SourceGeneratorDebugger/Models/Customer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace SourceGeneratorDebugger.Models; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class Customer : Identifiable -{ - [Attr] - public string Name { get; set; } = null!; -} diff --git a/test/SourceGeneratorDebugger/Models/Global.cs b/test/SourceGeneratorDebugger/Models/Global.cs deleted file mode 100644 index ef62b974ca..0000000000 --- a/test/SourceGeneratorDebugger/Models/Global.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -// ReSharper disable CheckNamespace -#pragma warning disable AV1505 // Namespace should match with assembly name - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource] -public sealed class Global : Identifiable -{ - [Attr] - public string? Value { get; set; } -} diff --git a/test/SourceGeneratorDebugger/Models/Login.cs b/test/SourceGeneratorDebugger/Models/Login.cs deleted file mode 100644 index 334a395a21..0000000000 --- a/test/SourceGeneratorDebugger/Models/Login.cs +++ /dev/null @@ -1,28 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -#pragma warning disable AV1505 // Namespace should match with assembly name - -namespace SourceGeneratorDebugger.Models -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - [Resource(GenerateControllerEndpoints = JsonApiEndpoints.Command)] - public sealed class Login : Identifiable - { - [Attr] - public DateTimeOffset Time { get; set; } - } -} - -namespace Some.Other.Path -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - [Resource(GenerateControllerEndpoints = JsonApiEndpoints.Command)] - public sealed class Login : Identifiable - { - [Attr] - public DateTimeOffset Time { get; set; } - } -} diff --git a/test/SourceGeneratorDebugger/Models/Order.cs b/test/SourceGeneratorDebugger/Models/Order.cs deleted file mode 100644 index 40d5582ff1..0000000000 --- a/test/SourceGeneratorDebugger/Models/Order.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace SourceGeneratorDebugger.Models; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(GenerateControllerEndpoints = JsonApiEndpoints.All)] -public sealed class Order : Identifiable -{ - [Attr] - public decimal TotalAmount { get; set; } -} diff --git a/test/SourceGeneratorDebugger/Models/SimpleNamespace.cs b/test/SourceGeneratorDebugger/Models/SimpleNamespace.cs deleted file mode 100644 index ea02d20413..0000000000 --- a/test/SourceGeneratorDebugger/Models/SimpleNamespace.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -// ReSharper disable CheckNamespace - -namespace SourceGeneratorDebugger; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource] -public sealed class SimpleNamespace : Identifiable -{ - [Attr] - public string? Value { get; set; } -} diff --git a/test/SourceGeneratorDebugger/Program.cs b/test/SourceGeneratorDebugger/Program.cs deleted file mode 100644 index b237a86f68..0000000000 --- a/test/SourceGeneratorDebugger/Program.cs +++ /dev/null @@ -1,58 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging.Abstractions; -using SourceGeneratorDebugger.Controllers; -using SourceGeneratorDebugger.Models; - -namespace SourceGeneratorDebugger; - -// Until https://github.com/dotnet/roslyn/issues/55802 is fixed, this project enables us to debug the ASP.NET Controller source generator. -// In Visual Studio, set JsonApiDotNetCore.SourceGenerators as startup project, add a breakpoint at the start of ControllerSourceGenerator and press F5. -internal static class Program -{ - public static void Main() - { - JsonApiOptions options = new(); - ResourceGraph resourceGraph = new(); - - // Built-in - IResourceService customerResourceService = new JsonApiResourceService(); - CustomersController customersController = new(options, resourceGraph, NullLoggerFactory.Instance, customerResourceService); - GC.KeepAlive(customersController); - - // Generated - IResourceService orderResourceService = new JsonApiResourceService(); - OrdersController ordersController = new(options, resourceGraph, NullLoggerFactory.Instance, orderResourceService); - GC.KeepAlive(ordersController); - - // Generated Query - IResourceQueryService accountQueryService = new JsonApiResourceService(); - AccountsController accountsController = new(options, resourceGraph, NullLoggerFactory.Instance, accountQueryService); - GC.KeepAlive(accountsController); - - // Generated Command - IResourceCommandService loginCommandService = new JsonApiResourceService(); - LoginsController loginsController = new(options, resourceGraph, NullLoggerFactory.Instance, loginCommandService); - GC.KeepAlive(loginsController); - - // Generated mix - IGetAllService articleGetAllResourceService = new JsonApiResourceService(); - IGetByIdService articleGetByIdResourceService = new JsonApiResourceService(); - IGetSecondaryService articleGetSecondaryResourceService = new JsonApiResourceService(); - - ArticlesController articlesController = new(options, resourceGraph, NullLoggerFactory.Instance, articleGetAllResourceService, - articleGetByIdResourceService, articleGetSecondaryResourceService); - - articlesController.ExtraMethod(); - - // Generated in global namespace - IResourceService globalResourceService = new JsonApiResourceService(); - GlobalsController globalsController = new(options, resourceGraph, NullLoggerFactory.Instance, globalResourceService); - GC.KeepAlive(globalsController); - - // Generated in non-nested namespace - IResourceService singleNamespaceResourceService = new JsonApiResourceService(); - SimpleNamespacesController singleNamespacesController = new(options, resourceGraph, NullLoggerFactory.Instance, singleNamespaceResourceService); - GC.KeepAlive(singleNamespacesController); - } -} diff --git a/test/SourceGeneratorDebugger/SourceGeneratorDebugger.csproj b/test/SourceGeneratorDebugger/SourceGeneratorDebugger.csproj deleted file mode 100644 index 2909905e2b..0000000000 --- a/test/SourceGeneratorDebugger/SourceGeneratorDebugger.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - $(TargetFrameworkName) - Exe - true - $(BaseIntermediateOutputPath)\GeneratedFiles - - - - - - - - - - - - - - - - - - - - - diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 68db5c994c..2f383d72e9 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -10,13 +10,13 @@ - + - + From 7246d1344c59f6f4c17bd856532979b560ee3d4b Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 26 May 2022 23:17:06 +0200 Subject: [PATCH 05/50] Updated issue templates --- .github/CONTRIBUTING.md | 4 +-- .github/ISSUE_TEMPLATE/bug_report.md | 13 ++++---- .github/ISSUE_TEMPLATE/feature_request.md | 12 +++---- .github/ISSUE_TEMPLATE/question.md | 38 +++++++++++++++++++++++ 4 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 150c8b45df..5e87f135f0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,8 +1,6 @@ # I don't want to read this whole thing I just have a question!!! -> Note: Please don't file an issue to ask a question. - -You'll get faster results by using our official [Gitter channel](https://gitter.im/json-api-dotnet-core/Lobby) or [StackOverflow](https://stackoverflow.com/search?q=jsonapidotnetcore) where the community chimes in with helpful advice if you have questions. +> You can file an issue to ask a question, but you'll get faster results by using our official [Gitter channel](https://gitter.im/json-api-dotnet-core/Lobby) or [StackOverflow](https://stackoverflow.com/search?q=jsonapidotnetcore) where the community chimes in with helpful advice if you have questions. # How can I contribute? diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e0c2388a38..c59acf46d6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,18 +2,18 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: 'bug' assignees: '' --- -_Please read our [Contributing Guides](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md) before submitting a bug._ + #### DESCRIPTION -_A clear and concise description of what the bug is._ + #### STEPS TO REPRODUCE -_Consider to include your code here, such as models, DbContext, controllers, resource services, repositories, resource definitions etc. Please also include the request URL with body (if applicable) and the full exception stack trace (set `options.IncludeExceptionStackTraceInErrors` to `true`) in case of errors._ It may also be helpful to include the produced SQL, which can be made visible in logs by adding this to appsettings.json: + 1. 2. 3. #### EXPECTED BEHAVIOR -_A clear and concise description of what you expected to happen._ + #### ACTUAL BEHAVIOR -_A clear and concise description of what happens instead._ + #### VERSIONS USED - JsonApiDotNetCore version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index f7b806fe50..f629ca472d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,21 +2,21 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: '' +labels: 'enhancement' assignees: '' --- -_Please read our [Contributing Guides](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md) before suggesting an idea._ + **Is your feature request related to a problem? Please describe.** -_A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]_ + **Describe the solution you'd like** -_A clear and concise description of what you want to happen._ + **Describe alternatives you've considered** -_A clear and concise description of any alternative solutions or features you've considered._ + **Additional context** -_Add any other context or screenshots about the feature request here._ + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..689f2daa01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,38 @@ +--- +name: Question +about: Ask a question +title: '' +labels: 'question' +assignees: '' + +--- + +#### SUMMARY + + +#### DETAILS + + +#### STEPS TO REPRODUCE + + +1. +2. +3. + +#### VERSIONS USED +- JsonApiDotNetCore version: +- ASP.NET Core version: +- Entity Framework Core version: +- Database provider: From 9af5eda9f6c01190414d07b5ca50ed9adbccd966 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 18 Jun 2022 10:29:20 +0200 Subject: [PATCH 06/50] Fixed spelling error --- src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs b/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs index 44cc2955d9..a2aa7a379b 100644 --- a/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs +++ b/src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs @@ -18,7 +18,7 @@ static CascadingCodeTimer() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // Be default, measurements using Stopwatch can differ 25%-30% on the same function on the same computer. + // By default, measurements using Stopwatch can differ 25%-30% on the same function on the same computer. // The steps below ensure to get an accuracy of 0.1%-0.2%. With this accuracy, algorithms can be tested and compared. // https://www.codeproject.com/Articles/61964/Performance-Tests-Precise-Run-Time-Measurements-wi From 174a599a169c343f9c1ec9ba8ced088cd5a6a5ea Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 23 Jun 2022 00:25:10 +0200 Subject: [PATCH 07/50] Fixed crash when a generic resource definition references an interface field from its OnApplySort lambda. Where we used to special-case for Identifiable.Id, we now always use the expression type for resource graph lookups. This is safe because its not possible in C# to remove base members. The value of memberExpression.Expression is null on static member access, which is unlikely to occur in sort lambdas. --- .../Resources/SortExpressionLambdaConverter.cs | 7 ++----- .../CreateSortExpressionFromLambdaTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs b/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs index a2371419b6..36a6d97aec 100644 --- a/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs +++ b/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs @@ -114,12 +114,9 @@ private static (Expression? innerExpression, bool isCount) TryReadCount(Expressi private Expression? ReadAttribute(Expression expression) { - if (expression is MemberExpression memberExpression) + if (expression is MemberExpression { Expression: { } } memberExpression) { - ResourceType resourceType = memberExpression.Member.Name == nameof(Identifiable.Id) && memberExpression.Expression != null - ? _resourceGraph.GetResourceType(memberExpression.Expression.Type) - : _resourceGraph.GetResourceType(memberExpression.Member.DeclaringType!); - + ResourceType resourceType = _resourceGraph.GetResourceType(memberExpression.Expression.Type); AttrAttribute? attribute = resourceType.FindAttributeByPropertyName(memberExpression.Member.Name); if (attribute != null) diff --git a/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs index aff961eeeb..9efa95a19e 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/ResourceDefinitions/CreateSortExpressionFromLambdaTests.cs @@ -37,7 +37,7 @@ public void Can_convert_chain_of_ToOne_relationships_ending_in_attribute() string[] expected = { "-fileEntries:content", - "fileSystemEntries:name", + "fileEntries:name", "fileEntries:length", "fileSystemEntries:parent.fileSystemEntries:name", "fileSystemEntries:parent.fileSystemEntries:parent.fileSystemEntries:name" @@ -103,7 +103,7 @@ public void Can_convert_chain_with_conversion_to_derived_types() "fileSystemEntries:parent.fileEntries:content", "count(directoryEntries:subdirectories)", "count(fileSystemEntries:parent.directoryEntries:files)", - "-fileSystemEntries:name" + "-directoryEntries:name" }; expression.ToFullString().Should().Be(string.Join(',', expected)); From 1ff2456004135d1312823ce9d2ec620fd69a4bee Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 24 Jun 2022 02:12:45 +0200 Subject: [PATCH 08/50] Increment version number (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 26ff3e7175..515d7de1ba 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ 6.0.* 4.2.* 2.14.1 - 5.0.2 + 5.0.3 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable From 018531106a5542e7b74155670aae79658857b76f Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 2 Jul 2022 16:42:22 +0200 Subject: [PATCH 09/50] Fixed invalid name, due to copy/paste --- .../Repositories/ResourceRepositoryAccessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 5f00cdf08d..9d42788940 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -126,8 +126,8 @@ protected object ResolveReadRepository(Type resourceClrType) protected virtual object ResolveReadRepository(ResourceType resourceType) { - Type resourceDefinitionType = typeof(IResourceReadRepository<,>).MakeGenericType(resourceType.ClrType, resourceType.IdentityClrType); - return _serviceProvider.GetRequiredService(resourceDefinitionType); + Type repositoryType = typeof(IResourceReadRepository<,>).MakeGenericType(resourceType.ClrType, resourceType.IdentityClrType); + return _serviceProvider.GetRequiredService(repositoryType); } private object GetWriteRepository(Type resourceClrType) From 770b848230fd5ec28a4225e866b8b8c62a562bd9 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 31 Jul 2022 10:44:49 +0200 Subject: [PATCH 10/50] Enable running code cleanup locally only on files changed since the specified branch --- .config/dotnet-tools.json | 2 +- .github/CONTRIBUTING.md | 4 ++-- Build.ps1 | 17 ++++++++++------ cleanupcode.ps1 | 43 +++++++++++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 69e33b978d..3bd80e6084 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -9,7 +9,7 @@ ] }, "regitlint": { - "version": "6.0.8", + "version": "6.1.1", "commands": [ "regitlint" ] diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5e87f135f0..4205b1ceec 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -58,8 +58,8 @@ Please follow these steps to have your contribution considered by the maintainer We use [CSharpGuidelines](https://csharpcodingguidelines.com/) as our coding standard (with a few minor exceptions). Coding style is validated during PR build, where we inject an extra settings layer that promotes various suggestions to warning level. This ensures a high-quality codebase without interfering too much when editing code. You can run the following [PowerShell scripts](https://github.com/PowerShell/PowerShell/releases) locally: -- `pwsh inspectcode.ps1`: Scans the code for style violations and opens the result in your web browser. -- `pwsh cleanupcode.ps1`: Reformats the entire codebase to match with our configured style. +- `pwsh ./inspectcode.ps1`: Scans the code for style violations and opens the result in your web browser. +- `pwsh ./cleanupcode.ps1 [branch-name-or-commit-hash]`: Reformats the codebase to match with our configured style, optionally only changed files since the specified branch (usually master). Code inspection violations can be addressed in several ways, depending on the situation: - Types that are reported to be never instantiated (because the IoC container creates them dynamically) should be decorated with `[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]`. diff --git a/Build.ps1 b/Build.ps1 index e2e638fafa..4a92feed89 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -40,16 +40,21 @@ function RunCleanupCode { # When running in cibuild for a pull request, this reformats only the files changed in the PR and fails if the reformat produces changes. if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) { - Write-Output "Running code cleanup on changed files in pull request" - # In the past, we used $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT for the merge commit hash. That is the pinned hash at the time the build is enqueued. # When a force-push happens after that, while the build hasn't yet started, this hash becomes invalid during the build, resulting in a lookup error. - # To prevent failing the build for unobvious reasons we use HEAD, which is always the latest version. - $mergeCommitHash = git rev-parse "HEAD" - $targetCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH" + # To prevent failing the build for unobvious reasons we use HEAD, which is always a detached head (the PR merge result). + + $headCommitHash = git rev-parse HEAD + CheckLastExitCode - dotnet regitlint -s JsonApiDotNetCore.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $mergeCommitHash -b $targetCommitHash --fail-on-diff --print-diff + $baseCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH" CheckLastExitCode + + if ($baseCommitHash -ne $headCommitHash) { + Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request." + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff + CheckLastExitCode + } } } diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 6db01a863a..bab8b82af1 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -1,17 +1,44 @@ #Requires -Version 7.0 -# This script reformats the entire codebase to make it compliant with our coding guidelines. +# This script reformats (part of) the codebase to make it compliant with our coding guidelines. -dotnet tool restore +param( + # Git branch name or base commit hash to reformat only the subset of changed files. Omit for all files. + [string] $revision +) -if ($LASTEXITCODE -ne 0) { - throw "Tool restore failed with exit code $LASTEXITCODE" +function VerifySuccessExitCode { + if ($LastExitCode -ne 0) { + throw "Command failed with exit code $LastExitCode." + } } +dotnet tool restore +VerifySuccessExitCode + dotnet restore +VerifySuccessExitCode -if ($LASTEXITCODE -ne 0) { - throw "Package restore failed with exit code $LASTEXITCODE" -} +if ($revision) { + $headCommitHash = git rev-parse HEAD + VerifySuccessExitCode -dotnet regitlint -s JsonApiDotNetCore.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN + $baseCommitHash = git rev-parse $revision + VerifySuccessExitCode + + if ($baseCommitHash -eq $headCommitHash) { + Write-Output "Running code cleanup on staged/unstaged files." + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified + VerifySuccessExitCode + } + else { + Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash, including staged/unstaged files." + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash + VerifySuccessExitCode + } +} +else { + Write-Output "Running code cleanup on all files." + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN + VerifySuccessExitCode +} From 02d19b5ecf71b5cf9652425b87ff61ae659ef773 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 4 Aug 2022 00:22:05 +0200 Subject: [PATCH 11/50] Adapts marking many-to-many relationships as tracked so it no longer needs the Issue26779 EF Core back-compat switch. The switch is unavailable in EF 7, so this change unblocks us from upgrade. --- .../Configuration/JsonApiOptions.cs | 6 ---- .../EntityFrameworkCoreRepository.cs | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index eda6374acf..aa7ed0d434 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -102,12 +102,6 @@ public sealed class JsonApiOptions : IJsonApiOptions } }; - static JsonApiOptions() - { - // Bug workaround for https://github.com/dotnet/efcore/issues/27436 - AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue26779", true); - } - public JsonApiOptions() { _lazySerializerReadOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.PublicationOnly); diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 358f63a2cd..1b807fd24f 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -547,6 +547,34 @@ private void MarkRelationshipAsLoaded(TResource leftResource, RelationshipAttrib EntityEntry leftEntry = _dbContext.Entry(leftResource); CollectionEntry rightCollectionEntry = leftEntry.Collection(relationship.Property.Name); rightCollectionEntry.IsLoaded = true; + + if (rightCollectionEntry.Metadata is ISkipNavigation skipNavigation) + { + MarkManyToManyRelationshipAsLoaded(leftEntry, skipNavigation); + } + } + + private void MarkManyToManyRelationshipAsLoaded(EntityEntry leftEntry, ISkipNavigation skipNavigation) + { + string[] primaryKeyNames = skipNavigation.ForeignKey.PrincipalKey.Properties.Select(property => property.Name).ToArray(); + object?[] primaryKeyValues = GetCurrentKeyValues(leftEntry, primaryKeyNames); + + string[] foreignKeyNames = skipNavigation.ForeignKey.Properties.Select(property => property.Name).ToArray(); + + foreach (EntityEntry joinEntry in _dbContext.ChangeTracker.Entries().Where(entry => entry.Metadata == skipNavigation.JoinEntityType).ToList()) + { + object?[] foreignKeyValues = GetCurrentKeyValues(joinEntry, foreignKeyNames); + + if (primaryKeyValues.SequenceEqual(foreignKeyValues)) + { + joinEntry.State = EntityState.Unchanged; + } + } + } + + private static object?[] GetCurrentKeyValues(EntityEntry entry, IEnumerable keyNames) + { + return keyNames.Select(keyName => entry.Property(keyName).CurrentValue).ToArray(); } protected async Task UpdateRelationshipAsync(RelationshipAttribute relationship, TResource leftResource, object? valueToAssign, From 6825c8345a81ed8024e2968bd95f0264c22cf1ee Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 6 Aug 2022 10:16:04 +0200 Subject: [PATCH 12/50] Added test for removing from many-to-many relationship with composite key --- .../IntegrationTests/CompositeKeys/Car.cs | 3 ++ .../CompositeKeys/CompositeDbContext.cs | 4 ++ .../CompositeKeys/CompositeKeyTests.cs | 48 +++++++++++++++++++ .../CompositeKeys/Dealership.cs | 3 ++ 4 files changed, 58 insertions(+) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Car.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Car.cs index 29213f5e69..d61d378db7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Car.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Car.cs @@ -47,4 +47,7 @@ public override string? Id [HasOne] public Dealership? Dealership { get; set; } + + [HasMany] + public ISet PreviousDealerships { get; set; } = new HashSet(); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs index 67213057b7..e9e12439e6 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs @@ -34,5 +34,9 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(dealership => dealership.Inventory) .WithOne(car => car.Dealership!); + + builder.Entity() + .HasMany(car => car.PreviousDealerships) + .WithMany(dealership => dealership.SoldCars); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs index 185367930d..9e273be36e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeKeyTests.cs @@ -508,4 +508,52 @@ await _testContext.RunOnDatabaseAsync(async dbContext => carInDatabase.Should().BeNull(); }); } + + [Fact] + public async Task Can_remove_from_ManyToMany_relationship() + { + // Arrange + Dealership existingDealership = _fakers.Dealership.Generate(); + existingDealership.SoldCars = _fakers.Car.Generate(2).ToHashSet(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.Dealerships.Add(existingDealership); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new[] + { + new + { + type = "cars", + id = existingDealership.SoldCars.ElementAt(1).StringId + } + } + }; + + string route = $"/dealerships/{existingDealership.StringId}/relationships/soldCars"; + + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.NoContent); + + responseDocument.Should().BeEmpty(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + Dealership dealershipInDatabase = await dbContext.Dealerships.Include(dealership => dealership.SoldCars).FirstWithIdAsync(existingDealership.Id); + + dealershipInDatabase.SoldCars.ShouldHaveCount(1); + dealershipInDatabase.SoldCars.Single().Id.Should().Be(existingDealership.SoldCars.ElementAt(0).Id); + + List carsInDatabase = await dbContext.Cars.ToListAsync(); + carsInDatabase.ShouldHaveCount(2); + }); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Dealership.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Dealership.cs index 14784cb438..091e7acbe1 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Dealership.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/Dealership.cs @@ -13,4 +13,7 @@ public sealed class Dealership : Identifiable [HasMany] public ISet Inventory { get; set; } = new HashSet(); + + [HasMany] + public ISet SoldCars { get; set; } = new HashSet(); } From 0fe0c59d14080184e7ea280e0ffc042f379c1ce7 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 8 Aug 2022 01:06:28 +0200 Subject: [PATCH 13/50] Use C# 10, which enables static lambdas to guard against accidentally captured variables, potentially resulting in unintended allocations --- JsonApiDotNetCore.sln.DotSettings | 1 + .../ControllerSourceGenerator.cs | 236 +++++------ .../JsonApiDotNetCore.SourceGenerators.csproj | 3 +- .../JsonApiEndpointsCopy.cs | 39 +- .../SourceCodeWriter.cs | 393 +++++++++--------- .../TypeWithAttributeSyntaxReceiver.cs | 62 ++- 6 files changed, 360 insertions(+), 374 deletions(-) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index 2a7eb28d9b..95c0c2b7b2 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -54,6 +54,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); WARNING WARNING WARNING + WARNING DO_NOT_SHOW HINT SUGGESTION diff --git a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs index 65800bba82..89a511b08e 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Text; using Humanizer; using Microsoft.CodeAnalysis; @@ -11,166 +8,163 @@ #pragma warning disable RS2008 // Enable analyzer release tracking -namespace JsonApiDotNetCore.SourceGenerators +namespace JsonApiDotNetCore.SourceGenerators; +// To debug in Visual Studio (requires v17.2 or higher): +// - Set JsonApiDotNetCore.SourceGenerators as startup project +// - Add a breakpoint at the start of the Initialize or Execute method +// - Optional: change targetProject in Properties\launchSettings.json +// - Press F5 + +[Generator(LanguageNames.CSharp)] +public sealed class ControllerSourceGenerator : ISourceGenerator { - // To debug in Visual Studio (requires v17.2 or higher): - // - Set JsonApiDotNetCore.SourceGenerators as startup project - // - Add a breakpoint at the start of the Initialize or Execute method - // - Optional: change targetProject in Properties\launchSettings.json - // - Press F5 - - [Generator(LanguageNames.CSharp)] - public sealed class ControllerSourceGenerator : ISourceGenerator + private const string Category = "JsonApiDotNetCore"; + + private static readonly DiagnosticDescriptor MissingInterfaceWarning = new("JADNC001", "Resource type does not implement IIdentifiable", + "Type '{0}' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers", Category, DiagnosticSeverity.Warning, + true); + + private static readonly DiagnosticDescriptor MissingIndentInTableError = new("JADNC900", "Internal error: Insufficient entries in IndentTable", + "Internal error: Missing entry in IndentTable for depth {0}", Category, DiagnosticSeverity.Warning, true); + + // PERF: Heap-allocate the delegate only once, instead of per compilation. + private static readonly SyntaxReceiverCreator CreateSyntaxReceiver = static () => new TypeWithAttributeSyntaxReceiver(); + + public void Initialize(GeneratorInitializationContext context) { - private const string Category = "JsonApiDotNetCore"; + context.RegisterForSyntaxNotifications(CreateSyntaxReceiver); + } - private static readonly DiagnosticDescriptor MissingInterfaceWarning = new DiagnosticDescriptor("JADNC001", - "Resource type does not implement IIdentifiable", - "Type '{0}' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers", Category, - DiagnosticSeverity.Warning, true); + public void Execute(GeneratorExecutionContext context) + { + var receiver = (TypeWithAttributeSyntaxReceiver?)context.SyntaxReceiver; - private static readonly DiagnosticDescriptor MissingIndentInTableError = new DiagnosticDescriptor("JADNC900", - "Internal error: Insufficient entries in IndentTable", "Internal error: Missing entry in IndentTable for depth {0}", Category, - DiagnosticSeverity.Warning, true); + if (receiver == null) + { + return; + } - // PERF: Heap-allocate the delegate only once, instead of per compilation. - private static readonly SyntaxReceiverCreator CreateSyntaxReceiver = () => new TypeWithAttributeSyntaxReceiver(); + INamedTypeSymbol? resourceAttributeType = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.Annotations.ResourceAttribute"); + INamedTypeSymbol? identifiableOpenInterface = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.IIdentifiable`1"); + INamedTypeSymbol? loggerFactoryInterface = context.Compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILoggerFactory"); - public void Initialize(GeneratorInitializationContext context) + if (resourceAttributeType == null || identifiableOpenInterface == null || loggerFactoryInterface == null) { - context.RegisterForSyntaxNotifications(CreateSyntaxReceiver); + return; } - public void Execute(GeneratorExecutionContext context) + var controllerNamesInUse = new Dictionary(StringComparer.OrdinalIgnoreCase); + var writer = new SourceCodeWriter(context, MissingIndentInTableError); + + foreach (TypeDeclarationSyntax? typeDeclarationSyntax in receiver.TypeDeclarations) { - var receiver = (TypeWithAttributeSyntaxReceiver)context.SyntaxReceiver; + // PERF: Note that our code runs on every keystroke in the IDE, which makes it critical to provide near-realtime performance. + // This means keeping an eye on memory allocations and bailing out early when compilations are cancelled while the user is still typing. + context.CancellationToken.ThrowIfCancellationRequested(); + + SemanticModel semanticModel = context.Compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree); + INamedTypeSymbol? resourceType = semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken); - if (receiver == null) + if (resourceType == null) { - return; + continue; } - INamedTypeSymbol resourceAttributeType = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.Annotations.ResourceAttribute"); - INamedTypeSymbol identifiableOpenInterface = context.Compilation.GetTypeByMetadataName("JsonApiDotNetCore.Resources.IIdentifiable`1"); - INamedTypeSymbol loggerFactoryInterface = context.Compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILoggerFactory"); + AttributeData? resourceAttributeData = FirstOrDefault(resourceType.GetAttributes(), resourceAttributeType, + static (data, type) => SymbolEqualityComparer.Default.Equals(data.AttributeClass, type)); - if (resourceAttributeType == null || identifiableOpenInterface == null || loggerFactoryInterface == null) + if (resourceAttributeData == null) { - return; + continue; } - var controllerNamesInUse = new Dictionary(StringComparer.OrdinalIgnoreCase); - var writer = new SourceCodeWriter(context, MissingIndentInTableError); + TypedConstant endpointsArgument = + resourceAttributeData.NamedArguments.FirstOrDefault(static pair => pair.Key == "GenerateControllerEndpoints").Value; - foreach (TypeDeclarationSyntax typeDeclarationSyntax in receiver.TypeDeclarations) + if (endpointsArgument.Value != null && (JsonApiEndpointsCopy)endpointsArgument.Value == JsonApiEndpointsCopy.None) { - // PERF: Note that our code runs on every keystroke in the IDE, which makes it critical to provide near-realtime performance. - // This means keeping an eye on memory allocations and bailing out early when compilations are cancelled while the user is still typing. - context.CancellationToken.ThrowIfCancellationRequested(); - - SemanticModel semanticModel = context.Compilation.GetSemanticModel(typeDeclarationSyntax.SyntaxTree); - INamedTypeSymbol resourceType = semanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken); - - if (resourceType == null) - { - continue; - } - - AttributeData resourceAttributeData = FirstOrDefault(resourceType.GetAttributes(), resourceAttributeType, - (data, type) => SymbolEqualityComparer.Default.Equals(data.AttributeClass, type)); - - if (resourceAttributeData == null) - { - continue; - } - - TypedConstant endpointsArgument = resourceAttributeData.NamedArguments.FirstOrDefault(pair => pair.Key == "GenerateControllerEndpoints").Value; - - if (endpointsArgument.Value != null && (JsonApiEndpointsCopy)endpointsArgument.Value == JsonApiEndpointsCopy.None) - { - continue; - } - - TypedConstant controllerNamespaceArgument = - resourceAttributeData.NamedArguments.FirstOrDefault(pair => pair.Key == "ControllerNamespace").Value; - - string controllerNamespace = GetControllerNamespace(controllerNamespaceArgument, resourceType); - - INamedTypeSymbol identifiableClosedInterface = FirstOrDefault(resourceType.AllInterfaces, identifiableOpenInterface, - (@interface, openInterface) => @interface.IsGenericType && - SymbolEqualityComparer.Default.Equals(@interface.ConstructedFrom, openInterface)); + continue; + } - if (identifiableClosedInterface == null) - { - var diagnostic = Diagnostic.Create(MissingInterfaceWarning, typeDeclarationSyntax.GetLocation(), resourceType.Name); - context.ReportDiagnostic(diagnostic); - continue; - } + TypedConstant controllerNamespaceArgument = + resourceAttributeData.NamedArguments.FirstOrDefault(static pair => pair.Key == "ControllerNamespace").Value; - ITypeSymbol idType = identifiableClosedInterface.TypeArguments[0]; - string controllerName = $"{resourceType.Name.Pluralize()}Controller"; - JsonApiEndpointsCopy endpointsToGenerate = (JsonApiEndpointsCopy?)(int?)endpointsArgument.Value ?? JsonApiEndpointsCopy.All; + string? controllerNamespace = GetControllerNamespace(controllerNamespaceArgument, resourceType); - string sourceCode = writer.Write(resourceType, idType, endpointsToGenerate, controllerNamespace, controllerName, loggerFactoryInterface); - SourceText sourceText = SourceText.From(sourceCode, Encoding.UTF8); + INamedTypeSymbol? identifiableClosedInterface = FirstOrDefault(resourceType.AllInterfaces, identifiableOpenInterface, + static (@interface, openInterface) => + @interface.IsGenericType && SymbolEqualityComparer.Default.Equals(@interface.ConstructedFrom, openInterface)); - string fileName = GetUniqueFileName(controllerName, controllerNamesInUse); - context.AddSource(fileName, sourceText); + if (identifiableClosedInterface == null) + { + var diagnostic = Diagnostic.Create(MissingInterfaceWarning, typeDeclarationSyntax.GetLocation(), resourceType.Name); + context.ReportDiagnostic(diagnostic); + continue; } - } - private static TElement FirstOrDefault(ImmutableArray source, TContext context, Func predicate) - { - // PERF: Using this method enables to avoid allocating a closure in the passed lambda expression. - // See https://www.jetbrains.com/help/resharper/2021.2/Fixing_Issues_Found_by_DPA.html#closures-in-lambda-expressions. + ITypeSymbol idType = identifiableClosedInterface.TypeArguments[0]; + string controllerName = $"{resourceType.Name.Pluralize()}Controller"; + JsonApiEndpointsCopy endpointsToGenerate = (JsonApiEndpointsCopy?)(int?)endpointsArgument.Value ?? JsonApiEndpointsCopy.All; - foreach (TElement element in source) - { - if (predicate(element, context)) - { - return element; - } - } + string sourceCode = writer.Write(resourceType, idType, endpointsToGenerate, controllerNamespace, controllerName, loggerFactoryInterface); + SourceText sourceText = SourceText.From(sourceCode, Encoding.UTF8); - return default; + string fileName = GetUniqueFileName(controllerName, controllerNamesInUse); + context.AddSource(fileName, sourceText); } + } - private static string GetControllerNamespace(TypedConstant controllerNamespaceArgument, INamedTypeSymbol resourceType) + private static TElement? FirstOrDefault(ImmutableArray source, TContext context, Func predicate) + { + // PERF: Using this method enables to avoid allocating a closure in the passed lambda expression. + // See https://www.jetbrains.com/help/resharper/2021.2/Fixing_Issues_Found_by_DPA.html#closures-in-lambda-expressions. + + foreach (TElement element in source) { - if (!controllerNamespaceArgument.IsNull) + if (predicate(element, context)) { - return (string)controllerNamespaceArgument.Value; + return element; } + } - if (resourceType.ContainingNamespace.IsGlobalNamespace) - { - return null; - } + return default; + } - if (resourceType.ContainingNamespace.ContainingNamespace.IsGlobalNamespace) - { - return "Controllers"; - } + private static string? GetControllerNamespace(TypedConstant controllerNamespaceArgument, INamedTypeSymbol resourceType) + { + if (!controllerNamespaceArgument.IsNull) + { + return (string?)controllerNamespaceArgument.Value; + } - return $"{resourceType.ContainingNamespace.ContainingNamespace}.Controllers"; + if (resourceType.ContainingNamespace.IsGlobalNamespace) + { + return null; } - private static string GetUniqueFileName(string controllerName, IDictionary controllerNamesInUse) + if (resourceType.ContainingNamespace.ContainingNamespace.IsGlobalNamespace) { - // We emit unique file names to prevent a failure in the source generator, but also because our test suite - // may contain two resources with the same class name in different namespaces. That works, as long as only - // one of its controllers gets registered. + return "Controllers"; + } - if (controllerNamesInUse.TryGetValue(controllerName, out int lastIndex)) - { - lastIndex++; - controllerNamesInUse[controllerName] = lastIndex; + return $"{resourceType.ContainingNamespace.ContainingNamespace}.Controllers"; + } - return $"{controllerName}{lastIndex}.g.cs"; - } + private static string GetUniqueFileName(string controllerName, IDictionary controllerNamesInUse) + { + // We emit unique file names to prevent a failure in the source generator, but also because our test suite + // may contain two resources with the same class name in different namespaces. That works, as long as only + // one of its controllers gets registered. - controllerNamesInUse[controllerName] = 1; - return $"{controllerName}.g.cs"; + if (controllerNamesInUse.TryGetValue(controllerName, out int lastIndex)) + { + lastIndex++; + controllerNamesInUse[controllerName] = lastIndex; + + return $"{controllerName}{lastIndex}.g.cs"; } + + controllerNamesInUse[controllerName] = 1; + return $"{controllerName}.g.cs"; } } diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj index bcd8c06b0a..8bf3e90cf6 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj +++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj @@ -5,8 +5,7 @@ true false $(NoWarn);NU5128 - disable - disable + latest true diff --git a/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs b/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs index 14134adcfd..911be3f359 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/JsonApiEndpointsCopy.cs @@ -1,26 +1,23 @@ -using System; +namespace JsonApiDotNetCore.SourceGenerators; -namespace JsonApiDotNetCore.SourceGenerators +// IMPORTANT: A copy of this type exists in the JsonApiDotNetCore project. Keep these in sync when making changes. +[Flags] +public enum JsonApiEndpointsCopy { - // IMPORTANT: A copy of this type exists in the JsonApiDotNetCore project. Keep these in sync when making changes. - [Flags] - public enum JsonApiEndpointsCopy - { - None = 0, - GetCollection = 1, - GetSingle = 1 << 1, - GetSecondary = 1 << 2, - GetRelationship = 1 << 3, - Post = 1 << 4, - PostRelationship = 1 << 5, - Patch = 1 << 6, - PatchRelationship = 1 << 7, - Delete = 1 << 8, - DeleteRelationship = 1 << 9, + None = 0, + GetCollection = 1, + GetSingle = 1 << 1, + GetSecondary = 1 << 2, + GetRelationship = 1 << 3, + Post = 1 << 4, + PostRelationship = 1 << 5, + Patch = 1 << 6, + PatchRelationship = 1 << 7, + Delete = 1 << 8, + DeleteRelationship = 1 << 9, - Query = GetCollection | GetSingle | GetSecondary | GetRelationship, - Command = Post | PostRelationship | Patch | PatchRelationship | Delete | DeleteRelationship, + Query = GetCollection | GetSingle | GetSecondary | GetRelationship, + Command = Post | PostRelationship | Patch | PatchRelationship | Delete | DeleteRelationship, - All = Query | Command - } + All = Query | Command } diff --git a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs index e03e3cbad2..bd6f96e82d 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs @@ -1,266 +1,263 @@ -using System.Collections.Generic; using System.Text; using Microsoft.CodeAnalysis; -namespace JsonApiDotNetCore.SourceGenerators +namespace JsonApiDotNetCore.SourceGenerators; + +/// +/// Writes the source code for an ASP.NET controller for a JSON:API resource. +/// +internal sealed class SourceCodeWriter { - /// - /// Writes the source code for an ASP.NET controller for a JSON:API resource. - /// - internal sealed class SourceCodeWriter + private const int SpacesPerIndent = 4; + + private static readonly IDictionary IndentTable = new Dictionary { - private const int SpacesPerIndent = 4; + [0] = string.Empty, + [1] = new(' ', 1 * SpacesPerIndent), + [2] = new(' ', 2 * SpacesPerIndent), + [3] = new(' ', 3 * SpacesPerIndent) + }; + + private static readonly IDictionary AggregateEndpointToServiceNameMap = + new Dictionary + { + [JsonApiEndpointsCopy.All] = ("IResourceService", "resourceService"), + [JsonApiEndpointsCopy.Query] = ("IResourceQueryService", "queryService"), + [JsonApiEndpointsCopy.Command] = ("IResourceCommandService", "commandService") + }; - private static readonly IDictionary IndentTable = new Dictionary + private static readonly IDictionary EndpointToServiceNameMap = + new Dictionary { - [0] = string.Empty, - [1] = new string(' ', 1 * SpacesPerIndent), - [2] = new string(' ', 2 * SpacesPerIndent), - [3] = new string(' ', 3 * SpacesPerIndent) + [JsonApiEndpointsCopy.GetCollection] = ("IGetAllService", "getAll"), + [JsonApiEndpointsCopy.GetSingle] = ("IGetByIdService", "getById"), + [JsonApiEndpointsCopy.GetSecondary] = ("IGetSecondaryService", "getSecondary"), + [JsonApiEndpointsCopy.GetRelationship] = ("IGetRelationshipService", "getRelationship"), + [JsonApiEndpointsCopy.Post] = ("ICreateService", "create"), + [JsonApiEndpointsCopy.PostRelationship] = ("IAddToRelationshipService", "addToRelationship"), + [JsonApiEndpointsCopy.Patch] = ("IUpdateService", "update"), + [JsonApiEndpointsCopy.PatchRelationship] = ("ISetRelationshipService", "setRelationship"), + [JsonApiEndpointsCopy.Delete] = ("IDeleteService", "delete"), + [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship") }; - private static readonly IDictionary AggregateEndpointToServiceNameMap = - new Dictionary - { - [JsonApiEndpointsCopy.All] = ("IResourceService", "resourceService"), - [JsonApiEndpointsCopy.Query] = ("IResourceQueryService", "queryService"), - [JsonApiEndpointsCopy.Command] = ("IResourceCommandService", "commandService") - }; + private readonly GeneratorExecutionContext _context; + private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor; - private static readonly IDictionary EndpointToServiceNameMap = - new Dictionary - { - [JsonApiEndpointsCopy.GetCollection] = ("IGetAllService", "getAll"), - [JsonApiEndpointsCopy.GetSingle] = ("IGetByIdService", "getById"), - [JsonApiEndpointsCopy.GetSecondary] = ("IGetSecondaryService", "getSecondary"), - [JsonApiEndpointsCopy.GetRelationship] = ("IGetRelationshipService", "getRelationship"), - [JsonApiEndpointsCopy.Post] = ("ICreateService", "create"), - [JsonApiEndpointsCopy.PostRelationship] = ("IAddToRelationshipService", "addToRelationship"), - [JsonApiEndpointsCopy.Patch] = ("IUpdateService", "update"), - [JsonApiEndpointsCopy.PatchRelationship] = ("ISetRelationshipService", "setRelationship"), - [JsonApiEndpointsCopy.Delete] = ("IDeleteService", "delete"), - [JsonApiEndpointsCopy.DeleteRelationship] = ("IRemoveFromRelationshipService", "removeFromRelationship") - }; - - private readonly GeneratorExecutionContext _context; - private readonly DiagnosticDescriptor _missingIndentInTableErrorDescriptor; - - private readonly StringBuilder _sourceBuilder = new StringBuilder(); - private int _depth; - - public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor) + private readonly StringBuilder _sourceBuilder = new(); + private int _depth; + + public SourceCodeWriter(GeneratorExecutionContext context, DiagnosticDescriptor missingIndentInTableErrorDescriptor) + { + _context = context; + _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor; + } + + public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string? controllerNamespace, + string controllerName, INamedTypeSymbol loggerFactoryInterface) + { + _sourceBuilder.Clear(); + _depth = 0; + + if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated) { - _context = context; - _missingIndentInTableErrorDescriptor = missingIndentInTableErrorDescriptor; + WriteNullableEnable(); } - public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEndpointsCopy endpointsToGenerate, string controllerNamespace, - string controllerName, INamedTypeSymbol loggerFactoryInterface) + WriteNamespaceImports(loggerFactoryInterface, resourceType); + + if (controllerNamespace != null) { - _sourceBuilder.Clear(); - _depth = 0; + WriteNamespaceDeclaration(controllerNamespace); + } - if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated) - { - WriteNullableEnable(); - } + WriteOpenClassDeclaration(controllerName, endpointsToGenerate, resourceType, idType); + _depth++; - WriteNamespaceImports(loggerFactoryInterface, resourceType); + WriteConstructor(controllerName, loggerFactoryInterface, endpointsToGenerate, resourceType, idType); - if (controllerNamespace != null) - { - WriteNamespaceDeclaration(controllerNamespace); - } + _depth--; + WriteCloseCurly(); - WriteOpenClassDeclaration(controllerName, endpointsToGenerate, resourceType, idType); - _depth++; + return _sourceBuilder.ToString(); + } - WriteConstructor(controllerName, loggerFactoryInterface, endpointsToGenerate, resourceType, idType); + private void WriteNullableEnable() + { + _sourceBuilder.AppendLine("#nullable enable"); + _sourceBuilder.AppendLine(); + } - _depth--; - WriteCloseCurly(); + private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType) + { + _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};"); - return _sourceBuilder.ToString(); - } + _sourceBuilder.AppendLine("using JsonApiDotNetCore.Configuration;"); + _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;"); + _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;"); - private void WriteNullableEnable() + if (!resourceType.ContainingNamespace.IsGlobalNamespace) { - _sourceBuilder.AppendLine("#nullable enable"); - _sourceBuilder.AppendLine(); + _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};"); } - private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType) - { - _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};"); + _sourceBuilder.AppendLine(); + } - _sourceBuilder.AppendLine("using JsonApiDotNetCore.Configuration;"); - _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;"); - _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;"); + private void WriteNamespaceDeclaration(string controllerNamespace) + { + _sourceBuilder.AppendLine($"namespace {controllerNamespace};"); + _sourceBuilder.AppendLine(); + } - if (!resourceType.ContainingNamespace.IsGlobalNamespace) - { - _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};"); - } + private void WriteOpenClassDeclaration(string controllerName, JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType) + { + string baseClassName = GetControllerBaseClassName(endpointsToGenerate); - _sourceBuilder.AppendLine(); - } + WriteIndent(); + _sourceBuilder.AppendLine($@"public sealed partial class {controllerName} : {baseClassName}<{resourceType.Name}, {idType}>"); - private void WriteNamespaceDeclaration(string controllerNamespace) + WriteOpenCurly(); + } + + private static string GetControllerBaseClassName(JsonApiEndpointsCopy endpointsToGenerate) + { + switch (endpointsToGenerate) { - _sourceBuilder.AppendLine($"namespace {controllerNamespace};"); - _sourceBuilder.AppendLine(); + case JsonApiEndpointsCopy.Query: + { + return "JsonApiQueryController"; + } + case JsonApiEndpointsCopy.Command: + { + return "JsonApiCommandController"; + } + default: + { + return "JsonApiController"; + } } + } - private void WriteOpenClassDeclaration(string controllerName, JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, - ITypeSymbol idType) - { - string baseClassName = GetControllerBaseClassName(endpointsToGenerate); + private void WriteConstructor(string controllerName, INamedTypeSymbol loggerFactoryInterface, JsonApiEndpointsCopy endpointsToGenerate, + INamedTypeSymbol resourceType, ITypeSymbol idType) + { + string loggerName = loggerFactoryInterface.Name; - WriteIndent(); - _sourceBuilder.AppendLine($@"public sealed partial class {controllerName} : {baseClassName}<{resourceType.Name}, {idType}>"); + WriteIndent(); + _sourceBuilder.AppendLine($"public {controllerName}(IJsonApiOptions options, IResourceGraph resourceGraph, {loggerName} loggerFactory,"); - WriteOpenCurly(); - } + _depth++; - private static string GetControllerBaseClassName(JsonApiEndpointsCopy endpointsToGenerate) + if (AggregateEndpointToServiceNameMap.TryGetValue(endpointsToGenerate, out (string ServiceName, string ParameterName) value)) { - switch (endpointsToGenerate) - { - case JsonApiEndpointsCopy.Query: - { - return "JsonApiQueryController"; - } - case JsonApiEndpointsCopy.Command: - { - return "JsonApiCommandController"; - } - default: - { - return "JsonApiController"; - } - } + WriteParameterListForShortConstructor(value.ServiceName, value.ParameterName, resourceType, idType); } - - private void WriteConstructor(string controllerName, INamedTypeSymbol loggerFactoryInterface, JsonApiEndpointsCopy endpointsToGenerate, - INamedTypeSymbol resourceType, ITypeSymbol idType) + else { - string loggerName = loggerFactoryInterface.Name; - - WriteIndent(); - _sourceBuilder.AppendLine($"public {controllerName}(IJsonApiOptions options, IResourceGraph resourceGraph, {loggerName} loggerFactory,"); + WriteParameterListForLongConstructor(endpointsToGenerate, resourceType, idType); + } - _depth++; + _depth--; - if (AggregateEndpointToServiceNameMap.TryGetValue(endpointsToGenerate, out (string ServiceName, string ParameterName) value)) - { - WriteParameterListForShortConstructor(value.ServiceName, value.ParameterName, resourceType, idType); - } - else - { - WriteParameterListForLongConstructor(endpointsToGenerate, resourceType, idType); - } - - _depth--; + WriteOpenCurly(); + WriteCloseCurly(); + } - WriteOpenCurly(); - WriteCloseCurly(); - } + private void WriteParameterListForShortConstructor(string serviceName, string parameterName, INamedTypeSymbol resourceType, ITypeSymbol idType) + { + WriteIndent(); + _sourceBuilder.AppendLine($"{serviceName}<{resourceType.Name}, {idType}> {parameterName})"); - private void WriteParameterListForShortConstructor(string serviceName, string parameterName, INamedTypeSymbol resourceType, ITypeSymbol idType) - { - WriteIndent(); - _sourceBuilder.AppendLine($"{serviceName}<{resourceType.Name}, {idType}> {parameterName})"); + WriteIndent(); + _sourceBuilder.AppendLine($": base(options, resourceGraph, loggerFactory, {parameterName})"); + } - WriteIndent(); - _sourceBuilder.AppendLine($": base(options, resourceGraph, loggerFactory, {parameterName})"); - } + private void WriteParameterListForLongConstructor(JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType) + { + bool isFirstEntry = true; - private void WriteParameterListForLongConstructor(JsonApiEndpointsCopy endpointsToGenerate, INamedTypeSymbol resourceType, ITypeSymbol idType) + foreach (KeyValuePair entry in EndpointToServiceNameMap) { - bool isFirstEntry = true; - - foreach (KeyValuePair entry in EndpointToServiceNameMap) + if ((endpointsToGenerate & entry.Key) == entry.Key) { - if ((endpointsToGenerate & entry.Key) == entry.Key) + if (isFirstEntry) { - if (isFirstEntry) - { - isFirstEntry = false; - } - else - { - _sourceBuilder.AppendLine(Tokens.Comma); - } - - WriteIndent(); - _sourceBuilder.Append($"{entry.Value.ServiceName}<{resourceType.Name}, {idType}> {entry.Value.ParameterName}"); + isFirstEntry = false; } + else + { + _sourceBuilder.AppendLine(Tokens.Comma); + } + + WriteIndent(); + _sourceBuilder.Append($"{entry.Value.ServiceName}<{resourceType.Name}, {idType}> {entry.Value.ParameterName}"); } + } - _sourceBuilder.AppendLine(Tokens.CloseParen); + _sourceBuilder.AppendLine(Tokens.CloseParen); - WriteIndent(); - _sourceBuilder.AppendLine(": base(options, resourceGraph, loggerFactory,"); + WriteIndent(); + _sourceBuilder.AppendLine(": base(options, resourceGraph, loggerFactory,"); - isFirstEntry = true; - _depth++; + isFirstEntry = true; + _depth++; - foreach (KeyValuePair entry in EndpointToServiceNameMap) + foreach (KeyValuePair entry in EndpointToServiceNameMap) + { + if ((endpointsToGenerate & entry.Key) == entry.Key) { - if ((endpointsToGenerate & entry.Key) == entry.Key) + if (isFirstEntry) { - if (isFirstEntry) - { - isFirstEntry = false; - } - else - { - _sourceBuilder.AppendLine(Tokens.Comma); - } - - WriteIndent(); - _sourceBuilder.Append($"{entry.Value.ParameterName}: {entry.Value.ParameterName}"); + isFirstEntry = false; + } + else + { + _sourceBuilder.AppendLine(Tokens.Comma); } - } - _sourceBuilder.AppendLine(Tokens.CloseParen); - _depth--; + WriteIndent(); + _sourceBuilder.Append($"{entry.Value.ParameterName}: {entry.Value.ParameterName}"); + } } - private void WriteOpenCurly() - { - WriteIndent(); - _sourceBuilder.AppendLine(Tokens.OpenCurly); - } + _sourceBuilder.AppendLine(Tokens.CloseParen); + _depth--; + } - private void WriteCloseCurly() - { - WriteIndent(); - _sourceBuilder.AppendLine(Tokens.CloseCurly); - } + private void WriteOpenCurly() + { + WriteIndent(); + _sourceBuilder.AppendLine(Tokens.OpenCurly); + } - private void WriteIndent() - { - // PERF: Reuse pre-calculated indents instead of allocating a new string each time. - if (!IndentTable.TryGetValue(_depth, out string indent)) - { - var diagnostic = Diagnostic.Create(_missingIndentInTableErrorDescriptor, Location.None, _depth.ToString()); - _context.ReportDiagnostic(diagnostic); + private void WriteCloseCurly() + { + WriteIndent(); + _sourceBuilder.AppendLine(Tokens.CloseCurly); + } - indent = new string(' ', _depth * SpacesPerIndent); - } + private void WriteIndent() + { + // PERF: Reuse pre-calculated indents instead of allocating a new string each time. + if (!IndentTable.TryGetValue(_depth, out string? indent)) + { + var diagnostic = Diagnostic.Create(_missingIndentInTableErrorDescriptor, Location.None, _depth.ToString()); + _context.ReportDiagnostic(diagnostic); - _sourceBuilder.Append(indent); + indent = new string(' ', _depth * SpacesPerIndent); } + _sourceBuilder.Append(indent); + } + #pragma warning disable AV1008 // Class should not be static - private static class Tokens - { - public const string OpenCurly = "{"; - public const string CloseCurly = "}"; - public const string CloseParen = ")"; - public const string Comma = ","; - } -#pragma warning restore AV1008 // Class should not be static + private static class Tokens + { + public const string OpenCurly = "{"; + public const string CloseCurly = "}"; + public const string CloseParen = ")"; + public const string Comma = ","; } +#pragma warning restore AV1008 // Class should not be static } diff --git a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs index 0fbc18a758..b23de19cc9 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs @@ -1,41 +1,39 @@ -using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace JsonApiDotNetCore.SourceGenerators +namespace JsonApiDotNetCore.SourceGenerators; + +/// +/// Collects type declarations in the project that have at least one attribute on them. Because this receiver operates at the syntax level, we cannot +/// check for the expected attribute. This must be done during semantic analysis, because source code may contain any of these: +/// { } +/// +/// [ResourceAttribute] +/// public class ExampleResource2 : Identifiable { } +/// +/// [AlternateNamespaceName.Annotations.Resource] +/// public class ExampleResource3 : Identifiable { } +/// +/// [AlternateTypeName] +/// public class ExampleResource4 : Identifiable { } +/// ]]> +/// +/// +internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver { - /// - /// Collects type declarations in the project that have at least one attribute on them. Because this receiver operates at the syntax level, we cannot - /// check for the expected attribute. This must be done during semantic analysis, because source code may contain any of these: - /// { } - /// - /// [ResourceAttribute] - /// public class ExampleResource2 : Identifiable { } - /// - /// [AlternateNamespaceName.Annotations.Resource] - /// public class ExampleResource3 : Identifiable { } - /// - /// [AlternateTypeName] - /// public class ExampleResource4 : Identifiable { } - /// ]]> - /// - /// - internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver - { - public readonly ISet TypeDeclarations = new HashSet(); + public readonly ISet TypeDeclarations = new HashSet(); - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.AttributeLists.Any()) { - if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.AttributeLists.Any()) - { - TypeDeclarations.Add(typeDeclarationSyntax); - } + TypeDeclarations.Add(typeDeclarationSyntax); } } } From 549586b3d32ee8819284ed87491b12e6561f268f Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Tue, 9 Aug 2022 23:36:26 +0200 Subject: [PATCH 14/50] Write "auto-generated" header comment, to prevent 3rd party analyzers from producing diagnostics --- .../SourceCodeWriter.cs | 8 ++++++ .../ControllerGenerationTests.cs | 28 ++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs index bd6f96e82d..13dc91a836 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs @@ -59,6 +59,8 @@ public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEn _sourceBuilder.Clear(); _depth = 0; + WriteAutoGeneratedComment(); + if (idType.IsReferenceType && idType.NullableAnnotation == NullableAnnotation.Annotated) { WriteNullableEnable(); @@ -82,6 +84,12 @@ public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEn return _sourceBuilder.ToString(); } + private void WriteAutoGeneratedComment() + { + _sourceBuilder.AppendLine("// "); + _sourceBuilder.AppendLine(); + } + private void WriteNullableEnable() { _sourceBuilder.AppendLine("#nullable enable"); diff --git a/test/SourceGeneratorTests/ControllerGenerationTests.cs b/test/SourceGeneratorTests/ControllerGenerationTests.cs index 614b4d316c..9f5f9f83d3 100644 --- a/test/SourceGeneratorTests/ControllerGenerationTests.cs +++ b/test/SourceGeneratorTests/ControllerGenerationTests.cs @@ -51,7 +51,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -111,7 +113,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -171,7 +175,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -234,7 +240,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -574,7 +582,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -633,7 +643,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -691,7 +703,9 @@ public sealed class Item : Identifiable GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().NotHaveDiagnostics(); - runResult.Should().HaveProducedSourceCode(@"using Microsoft.Extensions.Logging; + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; From 826f29089125c74f02cf01ed7bf447fdb230d7bc Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:18:33 +0200 Subject: [PATCH 15/50] Minor fixes --- src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs | 9 +++++---- src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs | 4 ++-- .../Queries/Internal/Parsing/IncludeParser.cs | 10 +++++----- .../JsonConverters/SingleOrManyDataConverterFactory.cs | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index eda6374acf..2e7cc54282 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -11,8 +11,8 @@ namespace JsonApiDotNetCore.Configuration; [PublicAPI] public sealed class JsonApiOptions : IJsonApiOptions { - private Lazy _lazySerializerWriteOptions; - private Lazy _lazySerializerReadOptions; + private readonly Lazy _lazySerializerWriteOptions; + private readonly Lazy _lazySerializerReadOptions; /// JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value; @@ -110,7 +110,8 @@ static JsonApiOptions() public JsonApiOptions() { - _lazySerializerReadOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.PublicationOnly); + _lazySerializerReadOptions = + new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.ExecutionAndPublication); _lazySerializerWriteOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions) { @@ -119,6 +120,6 @@ public JsonApiOptions() new WriteOnlyDocumentConverter(), new WriteOnlyRelationshipObjectConverter() } - }, LazyThreadSafetyMode.PublicationOnly); + }, LazyThreadSafetyMode.ExecutionAndPublication); } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 94507750da..2e15e6ae9a 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -72,7 +72,7 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin return; } - SetupOperationsRequest((JsonApiRequest)request, options, httpContext.Request); + SetupOperationsRequest((JsonApiRequest)request); httpContext.RegisterJsonApiRequest(); } @@ -280,7 +280,7 @@ private static bool IsRouteForOperations(RouteValueDictionary routeValues) return actionName == "PostOperations"; } - private static void SetupOperationsRequest(JsonApiRequest request, IJsonApiOptions options, HttpRequest httpRequest) + private static void SetupOperationsRequest(JsonApiRequest request) { request.IsReadOnly = false; request.Kind = EndpointKind.AtomicOperations; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index a453921989..14d2f1ec15 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -202,7 +202,7 @@ public string Path var pathBuilder = new StringBuilder(); IncludeTreeNode? parent = this; - while (parent is { Relationship: not HiddenRootRelationship }) + while (parent is { Relationship: not HiddenRootRelationshipAttribute }) { pathBuilder.Insert(0, pathBuilder.Length > 0 ? $"{parent.Relationship.PublicName}." : parent.Relationship.PublicName); parent = parent._parent; @@ -220,7 +220,7 @@ private IncludeTreeNode(RelationshipAttribute relationship, IncludeTreeNode? par public static IncludeTreeNode CreateRoot(ResourceType resourceType) { - var relationship = new HiddenRootRelationship(resourceType); + var relationship = new HiddenRootRelationshipAttribute(resourceType); return new IncludeTreeNode(relationship, null); } @@ -242,7 +242,7 @@ public IncludeExpression ToExpression() { IncludeElementExpression element = ToElementExpression(); - if (element.Relationship is HiddenRootRelationship) + if (element.Relationship is HiddenRootRelationshipAttribute) { return new IncludeExpression(element.Children); } @@ -262,9 +262,9 @@ public override string ToString() return include.ToFullString(); } - private sealed class HiddenRootRelationship : RelationshipAttribute + private sealed class HiddenRootRelationshipAttribute : RelationshipAttribute { - public HiddenRootRelationship(ResourceType rightType) + public HiddenRootRelationshipAttribute(ResourceType rightType) { ArgumentGuard.NotNull(rightType, nameof(rightType)); diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs index 25e497c2c1..3952ca93d8 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs @@ -28,7 +28,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer private sealed class SingleOrManyDataConverter : JsonObjectConverter> where T : class, IResourceIdentity, new() { - public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions serializerOptions) + public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var objects = new List(); bool isManyData = false; @@ -54,7 +54,7 @@ public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToC } case JsonTokenType.StartObject: { - var resourceObject = ReadSubTree(ref reader, serializerOptions); + var resourceObject = ReadSubTree(ref reader, options); objects.Add(resourceObject); break; } From 96340092d6d388ea860ff21e9bbf9fdeaf11c817 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:51:49 +0200 Subject: [PATCH 16/50] Reduce duplication in JSON:API type hierarchy by using JsonPropertyOrder (added in .NET 6). Also makes the model a bit more correct, as "ref" elements (used in atomic:operations) cannot occur in "data". --- .../SingleOrManyDataConverterFactory.cs | 2 +- .../Serialization/Objects/AtomicReference.cs | 14 +--------- .../Objects/IResourceIdentity.cs | 8 ------ .../Objects/ResourceIdentifierObject.cs | 15 ++--------- .../Serialization/Objects/ResourceIdentity.cs | 26 +++++++++++++++++++ .../Serialization/Objects/ResourceObject.cs | 21 +++------------ .../Serialization/Objects/SingleOrManyData.cs | 2 +- .../Request/Adapters/BaseAdapter.cs | 6 ++--- .../Adapters/ResourceIdentityAdapter.cs | 20 +++++++------- .../Adapters/ResourceIdentityRequirements.cs | 2 +- 10 files changed, 49 insertions(+), 67 deletions(-) delete mode 100644 src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs index 3952ca93d8..b842cace0e 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs @@ -26,7 +26,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer } private sealed class SingleOrManyDataConverter : JsonObjectConverter> - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs b/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs index 01693d1db6..fcc56298c1 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs @@ -7,20 +7,8 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See "ref" in https://jsonapi.org/ext/atomic/#operation-objects. /// [PublicAPI] -public sealed class AtomicReference : IResourceIdentity +public sealed class AtomicReference : ResourceIdentity { - [JsonPropertyName("type")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string? Type { get; set; } - - [JsonPropertyName("id")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Id { get; set; } - - [JsonPropertyName("lid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Lid { get; set; } - [JsonPropertyName("relationship")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Relationship { get; set; } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs b/src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs deleted file mode 100644 index c4b57f535f..0000000000 --- a/src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace JsonApiDotNetCore.Serialization.Objects; - -public interface IResourceIdentity -{ - public string? Type { get; } - public string? Id { get; } - public string? Lid { get; } -} diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs index a1b8271cf7..20c30909ed 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs @@ -7,21 +7,10 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See https://jsonapi.org/format/1.1/#document-resource-identifier-objects. /// [PublicAPI] -public sealed class ResourceIdentifierObject : IResourceIdentity +public class ResourceIdentifierObject : ResourceIdentity { - [JsonPropertyName("type")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string? Type { get; set; } - - [JsonPropertyName("id")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Id { get; set; } - - [JsonPropertyName("lid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Lid { get; set; } - [JsonPropertyName("meta")] + [JsonPropertyOrder(100)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Meta { get; set; } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs new file mode 100644 index 0000000000..41a3d951e6 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Serialization.Objects; + +/// +/// Shared identity information for various JSON:API objects. +/// +[PublicAPI] +public abstract class ResourceIdentity +{ + [JsonPropertyName("type")] + [JsonPropertyOrder(-3)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string? Type { get; set; } + + [JsonPropertyName("id")] + [JsonPropertyOrder(-2)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Id { get; set; } + + [JsonPropertyName("lid")] + [JsonPropertyOrder(-1)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Lid { get; set; } +} diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs index 43b3b9616a..ed38a40f9a 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs @@ -7,33 +7,20 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See https://jsonapi.org/format/1.1/#document-resource-objects. /// [PublicAPI] -public sealed class ResourceObject : IResourceIdentity +public sealed class ResourceObject : ResourceIdentifierObject { - [JsonPropertyName("type")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string? Type { get; set; } - - [JsonPropertyName("id")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Id { get; set; } - - [JsonPropertyName("lid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Lid { get; set; } - [JsonPropertyName("attributes")] + [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Attributes { get; set; } [JsonPropertyName("relationships")] + [JsonPropertyOrder(2)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Relationships { get; set; } [JsonPropertyName("links")] + [JsonPropertyOrder(3)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ResourceLinks? Links { get; set; } - - [JsonPropertyName("meta")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IDictionary? Meta { get; set; } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs b/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs index 1d2f99e126..1126f84f26 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs @@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; public readonly struct SingleOrManyData // The "new()" constraint exists for parity with SingleOrManyDataConverterFactory, which creates empty instances // to ensure ManyValue never contains null items. - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { public object? Value => ManyValue != null ? ManyValue : SingleValue; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs index 64e2f6d53b..fb1111bea1 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs @@ -11,7 +11,7 @@ public abstract class BaseAdapter { [AssertionMethod] protected static void AssertHasData(SingleOrManyData data, RequestAdapterState state) - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { if (!data.IsAssigned) { @@ -21,7 +21,7 @@ protected static void AssertHasData(SingleOrManyData data, RequestAdapterS [AssertionMethod] protected static void AssertDataHasSingleValue(SingleOrManyData data, bool allowNull, RequestAdapterState state) - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { if (data.SingleValue == null) { @@ -44,7 +44,7 @@ protected static void AssertDataHasSingleValue(SingleOrManyData data, bool [AssertionMethod] protected static void AssertDataHasManyValue(SingleOrManyData data, RequestAdapterState state) - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { if (data.ManyValue == null) { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs index d163eb56d1..61c6cc1857 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs @@ -25,7 +25,7 @@ protected ResourceIdentityAdapter(IResourceGraph resourceGraph, IResourceFactory _resourceFactory = resourceFactory; } - protected (IIdentifiable resource, ResourceType resourceType) ConvertResourceIdentity(IResourceIdentity identity, ResourceIdentityRequirements requirements, + protected (IIdentifiable resource, ResourceType resourceType) ConvertResourceIdentity(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { ArgumentGuard.NotNull(identity, nameof(identity)); @@ -38,7 +38,7 @@ protected ResourceIdentityAdapter(IResourceGraph resourceGraph, IResourceFactory return (resource, resourceType); } - private ResourceType ResolveType(IResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) + private ResourceType ResolveType(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { AssertHasType(identity.Type, state); @@ -93,7 +93,7 @@ private static void AssertIsCompatibleResourceType(ResourceType actual, Resource } } - private IIdentifiable CreateResource(IResourceIdentity identity, ResourceIdentityRequirements requirements, Type resourceClrType, RequestAdapterState state) + private IIdentifiable CreateResource(ResourceIdentity identity, ResourceIdentityRequirements requirements, Type resourceClrType, RequestAdapterState state) { if (state.Request.Kind != EndpointKind.AtomicOperations) { @@ -120,7 +120,7 @@ private IIdentifiable CreateResource(IResourceIdentity identity, ResourceIdentit return resource; } - private static void AssertHasNoLid(IResourceIdentity identity, RequestAdapterState state) + private static void AssertHasNoLid(ResourceIdentity identity, RequestAdapterState state) { if (identity.Lid != null) { @@ -129,7 +129,7 @@ private static void AssertHasNoLid(IResourceIdentity identity, RequestAdapterSta } } - private static void AssertNoIdWithLid(IResourceIdentity identity, RequestAdapterState state) + private static void AssertNoIdWithLid(ResourceIdentity identity, RequestAdapterState state) { if (identity.Id != null && identity.Lid != null) { @@ -137,7 +137,7 @@ private static void AssertNoIdWithLid(IResourceIdentity identity, RequestAdapter } } - private static void AssertHasIdOrLid(IResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) + private static void AssertHasIdOrLid(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { string? message = null; @@ -160,7 +160,7 @@ private static void AssertHasIdOrLid(IResourceIdentity identity, ResourceIdentit } } - private static void AssertHasNoId(IResourceIdentity identity, RequestAdapterState state) + private static void AssertHasNoId(ResourceIdentity identity, RequestAdapterState state) { if (identity.Id != null) { @@ -169,7 +169,7 @@ private static void AssertHasNoId(IResourceIdentity identity, RequestAdapterStat } } - private static void AssertSameIdValue(IResourceIdentity identity, string? expected, RequestAdapterState state) + private static void AssertSameIdValue(ResourceIdentity identity, string? expected, RequestAdapterState state) { if (expected != null && identity.Id != expected) { @@ -180,7 +180,7 @@ private static void AssertSameIdValue(IResourceIdentity identity, string? expect } } - private static void AssertSameLidValue(IResourceIdentity identity, string? expected, RequestAdapterState state) + private static void AssertSameLidValue(ResourceIdentity identity, string? expected, RequestAdapterState state) { if (expected != null && identity.Lid != expected) { @@ -191,7 +191,7 @@ private static void AssertSameLidValue(IResourceIdentity identity, string? expec } } - private void AssignStringId(IResourceIdentity identity, IIdentifiable resource, RequestAdapterState state) + private void AssignStringId(ResourceIdentity identity, IIdentifiable resource, RequestAdapterState state) { if (identity.Id != null) { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs index 11db5e8ee3..d5498397bf 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Serialization.Request.Adapters; /// -/// Defines requirements to validate an instance against. +/// Defines requirements to validate a instance against. /// [PublicAPI] public sealed class ResourceIdentityRequirements From 792ab16e44eb090465bf2e28ef9d016987aab05d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 21 Aug 2022 13:56:20 +0200 Subject: [PATCH 17/50] Tweak benchmarks --- benchmarks/Benchmarks.csproj | 4 +- .../OperationsDeserializationBenchmarks.cs | 1 + .../ResourceDeserializationBenchmarks.cs | 1 + .../QueryStringParserBenchmarks.cs | 31 +--- .../OperationsSerializationBenchmarks.cs | 1 + .../ResourceSerializationBenchmarks.cs | 1 + .../SerializationBenchmarkBase.cs | 147 +----------------- benchmarks/Tools/FakeLinkBuilder.cs | 39 +++++ .../Tools/FakeRequestQueryStringAccessor.cs | 18 +++ .../Tools/NeverResourceDefinitionAccessor.cs | 103 ++++++++++++ benchmarks/Tools/NoMetaBuilder.cs | 18 +++ 11 files changed, 191 insertions(+), 173 deletions(-) create mode 100644 benchmarks/Tools/FakeLinkBuilder.cs create mode 100644 benchmarks/Tools/FakeRequestQueryStringAccessor.cs create mode 100644 benchmarks/Tools/NeverResourceDefinitionAccessor.cs create mode 100644 benchmarks/Tools/NoMetaBuilder.cs diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj index 4bde435c15..f461a4831b 100644 --- a/benchmarks/Benchmarks.csproj +++ b/benchmarks/Benchmarks.csproj @@ -1,7 +1,8 @@ - + Exe $(TargetFrameworkName) + true @@ -10,6 +11,5 @@ - diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs index d28684e27b..99adce73cb 100644 --- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs @@ -7,6 +7,7 @@ namespace Benchmarks.Deserialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase { diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs index 23a6205bf5..e503a329bb 100644 --- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs @@ -7,6 +7,7 @@ namespace Benchmarks.Deserialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase { diff --git a/benchmarks/QueryString/QueryStringParserBenchmarks.cs b/benchmarks/QueryString/QueryStringParserBenchmarks.cs index efa4f12659..4218c2e3dc 100644 --- a/benchmarks/QueryString/QueryStringParserBenchmarks.cs +++ b/benchmarks/QueryString/QueryStringParserBenchmarks.cs @@ -1,13 +1,12 @@ using System.ComponentModel.Design; using BenchmarkDotNet.Attributes; +using Benchmarks.Tools; using JsonApiDotNetCore; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.QueryStrings; using JsonApiDotNetCore.QueryStrings.Internal; using JsonApiDotNetCore.Resources; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging.Abstractions; namespace Benchmarks.QueryString; @@ -71,31 +70,9 @@ public void DescendingSort() [Benchmark] public void ComplexQuery() { - Run(100, () => - { - const string queryString = - "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name"; - - _queryStringAccessor.SetQueryString(queryString); - _queryStringReader.ReadAll(null); - }); - } - - private void Run(int iterations, Action action) - { - for (int index = 0; index < iterations; index++) - { - action(); - } - } - - private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor - { - public IQueryCollection Query { get; private set; } = new QueryCollection(); + const string queryString = "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name"; - public void SetQueryString(string queryString) - { - Query = new QueryCollection(QueryHelpers.ParseQuery(queryString)); - } + _queryStringAccessor.SetQueryString(queryString); + _queryStringReader.ReadAll(null); } } diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs index 7076ca5cb8..471c9604c7 100644 --- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs +++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs @@ -9,6 +9,7 @@ namespace Benchmarks.Serialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class OperationsSerializationBenchmarks : SerializationBenchmarkBase { diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs index 12f5c2e788..a985bd5936 100644 --- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs +++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs @@ -12,6 +12,7 @@ namespace Benchmarks.Serialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class ResourceSerializationBenchmarks : SerializationBenchmarkBase { diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs index e1bcb10843..d9cfefd0b6 100644 --- a/benchmarks/Serialization/SerializationBenchmarkBase.cs +++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs @@ -1,18 +1,14 @@ -using System.Collections.Immutable; using System.Text.Json; using System.Text.Json.Serialization; +using Benchmarks.Tools; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Queries.Internal; -using JsonApiDotNetCore.QueryStrings; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCore.Serialization.Response; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging.Abstractions; namespace Benchmarks.Serialization; @@ -45,9 +41,9 @@ protected SerializationBenchmarkBase() // ReSharper restore VirtualMemberCallInConstructor var linkBuilder = new FakeLinkBuilder(); - var metaBuilder = new FakeMetaBuilder(); + var metaBuilder = new NoMetaBuilder(); IQueryConstraintProvider[] constraintProviders = Array.Empty(); - var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor(); + var resourceDefinitionAccessor = new NeverResourceDefinitionAccessor(); var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor); var requestQueryStringAccessor = new FakeRequestQueryStringAccessor(); @@ -122,141 +118,4 @@ public sealed class OutgoingResource : Identifiable [HasMany] public ISet Multi5 { get; set; } = null!; } - - private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor - { - public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes) - { - return existingIncludes; - } - - public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter) - { - return existingFilter; - } - - public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort) - { - return existingSort; - } - - public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination) - { - return existingPagination; - } - - public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet) - { - return existingSparseFieldSet; - } - - public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName) - { - return null; - } - - public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance) - { - return null; - } - - public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, - IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.FromResult(rightResourceId); - } - - public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnAddToRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public void OnDeserialize(IIdentifiable resource) - { - } - - public void OnSerialize(IIdentifiable resource) - { - } - } - - private sealed class FakeLinkBuilder : ILinkBuilder - { - public TopLevelLinks GetTopLevelLinks() - { - return new TopLevelLinks - { - Self = "TopLevel:Self" - }; - } - - public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource) - { - return new ResourceLinks - { - Self = "Resource:Self" - }; - } - - public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource) - { - return new RelationshipLinks - { - Self = "Relationship:Self", - Related = "Relationship:Related" - }; - } - } - - private sealed class FakeMetaBuilder : IMetaBuilder - { - public void Add(IDictionary values) - { - } - - public IDictionary? Build() - { - return null; - } - } - - private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor - { - public IQueryCollection Query { get; } = new QueryCollection(0); - } } diff --git a/benchmarks/Tools/FakeLinkBuilder.cs b/benchmarks/Tools/FakeLinkBuilder.cs new file mode 100644 index 0000000000..3468237507 --- /dev/null +++ b/benchmarks/Tools/FakeLinkBuilder.cs @@ -0,0 +1,39 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCore.Serialization.Response; +using Microsoft.AspNetCore.Http; + +namespace Benchmarks.Tools; + +/// +/// Renders hard-coded fake links, without depending on . +/// +internal sealed class FakeLinkBuilder : ILinkBuilder +{ + public TopLevelLinks GetTopLevelLinks() + { + return new TopLevelLinks + { + Self = "TopLevel:Self" + }; + } + + public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource) + { + return new ResourceLinks + { + Self = "Resource:Self" + }; + } + + public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource) + { + return new RelationshipLinks + { + Self = "Relationship:Self", + Related = "Relationship:Related" + }; + } +} diff --git a/benchmarks/Tools/FakeRequestQueryStringAccessor.cs b/benchmarks/Tools/FakeRequestQueryStringAccessor.cs new file mode 100644 index 0000000000..8b2b5540a1 --- /dev/null +++ b/benchmarks/Tools/FakeRequestQueryStringAccessor.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.QueryStrings; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; + +namespace Benchmarks.Tools; + +/// +/// Enables to inject a query string, instead of obtaining it from . +/// +internal sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor +{ + public IQueryCollection Query { get; private set; } = new QueryCollection(); + + public void SetQueryString(string queryString) + { + Query = new QueryCollection(QueryHelpers.ParseQuery(queryString)); + } +} diff --git a/benchmarks/Tools/NeverResourceDefinitionAccessor.cs b/benchmarks/Tools/NeverResourceDefinitionAccessor.cs new file mode 100644 index 0000000000..6e93519dae --- /dev/null +++ b/benchmarks/Tools/NeverResourceDefinitionAccessor.cs @@ -0,0 +1,103 @@ +using System.Collections.Immutable; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace Benchmarks.Tools; + +/// +/// Never calls into instances. +/// +internal sealed class NeverResourceDefinitionAccessor : IResourceDefinitionAccessor +{ + public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes) + { + return existingIncludes; + } + + public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter) + { + return existingFilter; + } + + public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort) + { + return existingSort; + } + + public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination) + { + return existingPagination; + } + + public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet) + { + return existingSparseFieldSet; + } + + public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName) + { + return null; + } + + public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance) + { + return null; + } + + public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, + IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.FromResult(rightResourceId); + } + + public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, + WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnAddToRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, + CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, + CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public void OnDeserialize(IIdentifiable resource) + { + } + + public void OnSerialize(IIdentifiable resource) + { + } +} diff --git a/benchmarks/Tools/NoMetaBuilder.cs b/benchmarks/Tools/NoMetaBuilder.cs new file mode 100644 index 0000000000..db3ed7857e --- /dev/null +++ b/benchmarks/Tools/NoMetaBuilder.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.Serialization.Response; + +namespace Benchmarks.Tools; + +/// +/// Doesn't produce any top-level meta. +/// +internal sealed class NoMetaBuilder : IMetaBuilder +{ + public void Add(IDictionary values) + { + } + + public IDictionary? Build() + { + return null; + } +} From a00ab50528ed6ba3db84d536287450d29b49a92d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 18 Jul 2022 02:01:56 +0200 Subject: [PATCH 18/50] Use System.Text.Json source generator (see https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator) --- .../DeserializationBenchmarkBase.cs | 6 ++-- .../OperationsDeserializationBenchmarks.cs | 2 +- .../ResourceDeserializationBenchmarks.cs | 2 +- .../OperationsSerializationBenchmarks.cs | 2 +- .../ResourceSerializationBenchmarks.cs | 2 +- .../SerializationBenchmarkBase.cs | 6 ++-- .../Configuration/IJsonApiOptions.cs | 17 +++++++++++ .../Configuration/JsonApiOptions.cs | 23 +++++++++------ .../Middleware/JsonApiMiddleware.cs | 28 ++++++++++--------- .../JsonApiSerializationContext.cs | 17 +++++++++++ .../JsonConverters/JsonObjectConverter.cs | 14 +--------- .../Serialization/Objects/Document.cs | 2 ++ .../Serialization/Request/JsonApiReader.cs | 2 +- .../Serialization/Response/JsonApiWriter.cs | 2 +- .../Response/ResponseModelAdapterTests.cs | 6 ++-- 15 files changed, 82 insertions(+), 49 deletions(-) create mode 100644 src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs diff --git a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs index bbf746d1a8..80a9753597 100644 --- a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs +++ b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs @@ -1,10 +1,10 @@ using System.ComponentModel.Design; -using System.Text.Json; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.JsonConverters; using JsonApiDotNetCore.Serialization.Request.Adapters; using Microsoft.Extensions.Logging.Abstractions; @@ -13,7 +13,7 @@ namespace Benchmarks.Deserialization; public abstract class DeserializationBenchmarkBase { - protected readonly JsonSerializerOptions SerializerReadOptions; + protected readonly JsonApiSerializationContext SerializationReadContext; protected readonly DocumentAdapter DocumentAdapter; protected DeserializationBenchmarkBase() @@ -21,7 +21,7 @@ protected DeserializationBenchmarkBase() var options = new JsonApiOptions(); IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build(); options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph)); - SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions; + SerializationReadContext = ((IJsonApiOptions)options).SerializationReadContext; var serviceContainer = new ServiceContainer(); var resourceFactory = new ResourceFactory(serviceContainer); diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs index 99adce73cb..efe0ae568f 100644 --- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs @@ -270,7 +270,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase [Benchmark] public object? DeserializeOperationsRequest() { - var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!; + Document document = JsonSerializer.Deserialize(RequestBody, SerializationReadContext.Document)!; return DocumentAdapter.Convert(document); } diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs index e503a329bb..3d2cdd35af 100644 --- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs @@ -133,7 +133,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase [Benchmark] public object? DeserializeResourceRequest() { - var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!; + Document document = JsonSerializer.Deserialize(RequestBody, SerializationReadContext.Document)!; return DocumentAdapter.Convert(document); } diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs index 471c9604c7..2be9da5da6 100644 --- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs +++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs @@ -116,7 +116,7 @@ private static IEnumerable CreateResponseOperations(IJsonApi public string SerializeOperationsResponse() { Document responseDocument = ResponseModelAdapter.Convert(_responseOperations); - return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions); + return JsonSerializer.Serialize(responseDocument, SerializationWriteContext.Document); } protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph) diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs index a985bd5936..f896846ee2 100644 --- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs +++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs @@ -107,7 +107,7 @@ private static OutgoingResource CreateResponseResource() public string SerializeResourceResponse() { Document responseDocument = ResponseModelAdapter.Convert(ResponseResource); - return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions); + return JsonSerializer.Serialize(responseDocument, SerializationWriteContext.Document); } protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph) diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs index d9cfefd0b6..be1a711ad9 100644 --- a/benchmarks/Serialization/SerializationBenchmarkBase.cs +++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using System.Text.Json.Serialization; using Benchmarks.Tools; using JetBrains.Annotations; @@ -8,6 +7,7 @@ using JsonApiDotNetCore.Queries.Internal; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.Response; using Microsoft.Extensions.Logging.Abstractions; @@ -15,7 +15,7 @@ namespace Benchmarks.Serialization; public abstract class SerializationBenchmarkBase { - protected readonly JsonSerializerOptions SerializerWriteOptions; + protected readonly JsonApiSerializationContext SerializationWriteContext; protected readonly IResponseModelAdapter ResponseModelAdapter; protected readonly IResourceGraph ResourceGraph; @@ -33,7 +33,7 @@ protected SerializationBenchmarkBase() }; ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build(); - SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions; + SerializationWriteContext = ((IJsonApiOptions)options).SerializationWriteContext; // ReSharper disable VirtualMemberCallInConstructor JsonApiRequest request = CreateJsonApiRequest(ResourceGraph); diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 597d22294d..477500d79b 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -1,6 +1,8 @@ using System.Data; using System.Text.Json; +using JetBrains.Annotations; using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.Objects; namespace JsonApiDotNetCore.Configuration; @@ -8,6 +10,7 @@ namespace JsonApiDotNetCore.Configuration; /// /// Global options that configure the behavior of JsonApiDotNetCore. /// +[PublicAPI] public interface IJsonApiOptions { /// @@ -156,13 +159,27 @@ public interface IJsonApiOptions /// JsonSerializerOptions SerializerOptions { get; } + /// + /// Gets the source-generated JSON serialization context used for deserializing request bodies. This value is based on + /// and is intended for internal use. + /// + JsonApiSerializationContext SerializationReadContext { get; } + /// /// Gets the settings used for deserializing request bodies. This value is based on and is intended for internal use. /// + [Obsolete("Use SerializationReadContext.Options instead.")] JsonSerializerOptions SerializerReadOptions { get; } + /// + /// Gets the source-generated JSON serialization context used for serializing response bodies. This value is based on + /// and is intended for internal use. + /// + JsonApiSerializationContext SerializationWriteContext { get; } + /// /// Gets the settings used for serializing response bodies. This value is based on and is intended for internal use. /// + [Obsolete("Use SerializationWriteContext.Options instead.")] JsonSerializerOptions SerializerWriteOptions { get; } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 2e7cc54282..bb167e4db2 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -3,6 +3,7 @@ using System.Text.Json; using JetBrains.Annotations; using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.JsonConverters; namespace JsonApiDotNetCore.Configuration; @@ -11,14 +12,20 @@ namespace JsonApiDotNetCore.Configuration; [PublicAPI] public sealed class JsonApiOptions : IJsonApiOptions { - private readonly Lazy _lazySerializerWriteOptions; - private readonly Lazy _lazySerializerReadOptions; + private readonly Lazy _lazySerializerReadContext; + private readonly Lazy _lazySerializerWriteContext; /// - JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value; + JsonApiSerializationContext IJsonApiOptions.SerializationReadContext => _lazySerializerReadContext.Value; /// - JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => _lazySerializerWriteOptions.Value; + JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => ((IJsonApiOptions)this).SerializationReadContext.Options; + + /// + JsonApiSerializationContext IJsonApiOptions.SerializationWriteContext => _lazySerializerWriteContext.Value; + + /// + JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => ((IJsonApiOptions)this).SerializationWriteContext.Options; /// public string? Namespace { get; set; } @@ -110,16 +117,16 @@ static JsonApiOptions() public JsonApiOptions() { - _lazySerializerReadOptions = - new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.ExecutionAndPublication); + _lazySerializerReadContext = new Lazy(() => new JsonApiSerializationContext(new JsonSerializerOptions(SerializerOptions)), + LazyThreadSafetyMode.ExecutionAndPublication); - _lazySerializerWriteOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions) + _lazySerializerWriteContext = new Lazy(() => new JsonApiSerializationContext(new JsonSerializerOptions(SerializerOptions) { Converters = { new WriteOnlyDocumentConverter(), new WriteOnlyRelationshipObjectConverter() } - }, LazyThreadSafetyMode.ExecutionAndPublication); + }), LazyThreadSafetyMode.ExecutionAndPublication); } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 2e15e6ae9a..6ea3853e92 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Diagnostics; using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -44,7 +45,7 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin using (CodeTimingSessionManager.Current.Measure("JSON:API middleware")) { - if (!await ValidateIfMatchHeaderAsync(httpContext, options.SerializerWriteOptions)) + if (!await ValidateIfMatchHeaderAsync(httpContext, options.SerializationWriteContext)) { return; } @@ -54,8 +55,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin if (primaryResourceType != null) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializerWriteOptions) || - !await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializerWriteOptions)) + if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializationWriteContext) || + !await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializationWriteContext)) { return; } @@ -66,8 +67,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin } else if (IsRouteForOperations(routeValues)) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions) || - !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions)) + if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializationWriteContext) || + !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializationWriteContext)) { return; } @@ -91,11 +92,11 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin } } - private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, JsonSerializerOptions serializerOptions) + private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, JsonApiSerializationContext serializationContext) { if (httpContext.Request.Headers.ContainsKey(HeaderNames.IfMatch)) { - await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.PreconditionFailed) + await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.PreconditionFailed) { Title = "Detection of mid-air edit collisions using ETags is not supported.", Source = new ErrorSource @@ -120,13 +121,14 @@ private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, Jso : null; } - private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, JsonSerializerOptions serializerOptions) + private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, + JsonApiSerializationContext serializationContext) { string? contentType = httpContext.Request.ContentType; if (contentType != null && contentType != allowedContentType) { - await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.UnsupportedMediaType) + await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.UnsupportedMediaType) { Title = "The specified Content-Type header value is not supported.", Detail = $"Please specify '{allowedContentType}' instead of '{contentType}' for the Content-Type header value.", @@ -143,7 +145,7 @@ private static async Task ValidateContentTypeHeaderAsync(string allowedCon } private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue allowedMediaTypeValue, HttpContext httpContext, - JsonSerializerOptions serializerOptions) + JsonApiSerializationContext serializationContext) { string[] acceptHeaders = httpContext.Request.Headers.GetCommaSeparatedValues("Accept"); @@ -176,7 +178,7 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a if (!seenCompatibleMediaType) { - await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.NotAcceptable) + await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.NotAcceptable) { Title = "The specified Accept header value does not contain any supported media types.", Detail = $"Please include '{allowedMediaTypeValue}' in the Accept header values.", @@ -192,7 +194,7 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a return true; } - private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSerializerOptions serializerOptions, ErrorObject error) + private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonApiSerializationContext serializationContext, ErrorObject error) { httpResponse.ContentType = HeaderConstants.MediaType; httpResponse.StatusCode = (int)error.StatusCode; @@ -202,7 +204,7 @@ private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSeri Errors = error.AsList() }; - await JsonSerializer.SerializeAsync(httpResponse.Body, errorDocument, serializerOptions); + await JsonSerializer.SerializeAsync(httpResponse.Body, errorDocument, serializationContext.Document); await httpResponse.Body.FlushAsync(); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs new file mode 100644 index 0000000000..38180d9369 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using JsonApiDotNetCore.Serialization.Objects; + +namespace JsonApiDotNetCore.Serialization; + +// Workaround for https://youtrack.jetbrains.com/issue/RSRP-487028 +partial class JsonApiSerializationContext +{ +} + +/// +/// Provides compile-time metadata about the set of JSON:API types used in JSON serialization of request/response bodies. +/// +[JsonSerializable(typeof(Document))] +public sealed partial class JsonApiSerializationContext : JsonSerializerContext +{ +} diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs index 32e4351e12..97d7589cc6 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs @@ -7,24 +7,12 @@ public abstract class JsonObjectConverter : JsonConverter { protected static TValue? ReadSubTree(ref Utf8JsonReader reader, JsonSerializerOptions options) { - if (typeof(TValue) != typeof(object) && options.GetConverter(typeof(TValue)) is JsonConverter converter) - { - return converter.Read(ref reader, typeof(TValue), options); - } - return JsonSerializer.Deserialize(ref reader, options); } protected static void WriteSubTree(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options) { - if (typeof(TValue) != typeof(object) && options.GetConverter(typeof(TValue)) is JsonConverter converter) - { - converter.Write(writer, value, options); - } - else - { - JsonSerializer.Serialize(writer, value, options); - } + JsonSerializer.Serialize(writer, value, options); } protected static JsonException GetEndOfStreamError() diff --git a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs index 2f40aeb27b..87c3a0acfa 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs @@ -1,10 +1,12 @@ using System.Text.Json.Serialization; +using JetBrains.Annotations; namespace JsonApiDotNetCore.Serialization.Objects; /// /// See https://jsonapi.org/format/1.1/#document-top-level and https://jsonapi.org/ext/atomic/#document-structure. /// +[PublicAPI] public sealed class Document { [JsonPropertyName("jsonapi")] diff --git a/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs index 0942683487..0282ab39bd 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs @@ -80,7 +80,7 @@ private Document DeserializeDocument(string requestBody) using IDisposable _ = CodeTimingSessionManager.Current.Measure("JsonSerializer.Deserialize", MeasurementSettings.ExcludeJsonSerializationInPercentages); - var document = JsonSerializer.Deserialize(requestBody, _options.SerializerReadOptions); + Document? document = JsonSerializer.Deserialize(requestBody, _options.SerializationReadContext.Document); AssertHasDocument(document, requestBody); diff --git a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs index 20f4ad242b..2bfefde98b 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs @@ -125,7 +125,7 @@ private string SerializeDocument(Document document) { using IDisposable _ = CodeTimingSessionManager.Current.Measure("JsonSerializer.Serialize", MeasurementSettings.ExcludeJsonSerializationInPercentages); - return JsonSerializer.Serialize(document, _options.SerializerWriteOptions); + return JsonSerializer.Serialize(document, _options.SerializationWriteContext.Document); } private bool SetETagResponseHeader(HttpRequest request, HttpResponse response, string responseContent) diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs index d9459f7ec1..f0af041de5 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs @@ -38,7 +38,7 @@ public void Resources_in_deeply_nested_circular_chain_are_written_in_relationshi Document document = responseModelAdapter.Convert(article); // Assert - string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + string text = JsonSerializer.Serialize(document, options.SerializationWriteContext.Document); text.Should().BeJson(@"{ ""data"": { @@ -175,7 +175,7 @@ public void Resources_in_deeply_nested_circular_chains_are_written_in_relationsh }); // Assert - string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + string text = JsonSerializer.Serialize(document, options.SerializationWriteContext.Document); text.Should().BeJson(@"{ ""data"": [ @@ -333,7 +333,7 @@ public void Resources_in_overlapping_deeply_nested_circular_chains_are_written_i Document document = responseModelAdapter.Convert(article); // Assert - string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + string text = JsonSerializer.Serialize(document, options.SerializationWriteContext.Document); text.Should().BeJson(@"{ ""data"": { From f760e8f789b044ca35074b3ca5f84e084c422076 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 21 Aug 2022 15:04:02 +0200 Subject: [PATCH 19/50] Revert "Use System.Text.Json source generator (see https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator)" This reverts commit a00ab50528ed6ba3db84d536287450d29b49a92d. --- .../DeserializationBenchmarkBase.cs | 6 ++-- .../OperationsDeserializationBenchmarks.cs | 2 +- .../ResourceDeserializationBenchmarks.cs | 2 +- .../OperationsSerializationBenchmarks.cs | 2 +- .../ResourceSerializationBenchmarks.cs | 2 +- .../SerializationBenchmarkBase.cs | 6 ++-- .../Configuration/IJsonApiOptions.cs | 17 ----------- .../Configuration/JsonApiOptions.cs | 23 ++++++--------- .../Middleware/JsonApiMiddleware.cs | 28 +++++++++---------- .../JsonApiSerializationContext.cs | 17 ----------- .../JsonConverters/JsonObjectConverter.cs | 14 +++++++++- .../Serialization/Objects/Document.cs | 2 -- .../Serialization/Request/JsonApiReader.cs | 2 +- .../Serialization/Response/JsonApiWriter.cs | 2 +- .../Response/ResponseModelAdapterTests.cs | 6 ++-- 15 files changed, 49 insertions(+), 82 deletions(-) delete mode 100644 src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs diff --git a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs index 80a9753597..bbf746d1a8 100644 --- a/benchmarks/Deserialization/DeserializationBenchmarkBase.cs +++ b/benchmarks/Deserialization/DeserializationBenchmarkBase.cs @@ -1,10 +1,10 @@ using System.ComponentModel.Design; +using System.Text.Json; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.JsonConverters; using JsonApiDotNetCore.Serialization.Request.Adapters; using Microsoft.Extensions.Logging.Abstractions; @@ -13,7 +13,7 @@ namespace Benchmarks.Deserialization; public abstract class DeserializationBenchmarkBase { - protected readonly JsonApiSerializationContext SerializationReadContext; + protected readonly JsonSerializerOptions SerializerReadOptions; protected readonly DocumentAdapter DocumentAdapter; protected DeserializationBenchmarkBase() @@ -21,7 +21,7 @@ protected DeserializationBenchmarkBase() var options = new JsonApiOptions(); IResourceGraph resourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build(); options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph)); - SerializationReadContext = ((IJsonApiOptions)options).SerializationReadContext; + SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions; var serviceContainer = new ServiceContainer(); var resourceFactory = new ResourceFactory(serviceContainer); diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs index efe0ae568f..99adce73cb 100644 --- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs @@ -270,7 +270,7 @@ public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase [Benchmark] public object? DeserializeOperationsRequest() { - Document document = JsonSerializer.Deserialize(RequestBody, SerializationReadContext.Document)!; + var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!; return DocumentAdapter.Convert(document); } diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs index 3d2cdd35af..e503a329bb 100644 --- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs @@ -133,7 +133,7 @@ public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase [Benchmark] public object? DeserializeResourceRequest() { - Document document = JsonSerializer.Deserialize(RequestBody, SerializationReadContext.Document)!; + var document = JsonSerializer.Deserialize(RequestBody, SerializerReadOptions)!; return DocumentAdapter.Convert(document); } diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs index 2be9da5da6..471c9604c7 100644 --- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs +++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs @@ -116,7 +116,7 @@ private static IEnumerable CreateResponseOperations(IJsonApi public string SerializeOperationsResponse() { Document responseDocument = ResponseModelAdapter.Convert(_responseOperations); - return JsonSerializer.Serialize(responseDocument, SerializationWriteContext.Document); + return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions); } protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph) diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs index f896846ee2..a985bd5936 100644 --- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs +++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs @@ -107,7 +107,7 @@ private static OutgoingResource CreateResponseResource() public string SerializeResourceResponse() { Document responseDocument = ResponseModelAdapter.Convert(ResponseResource); - return JsonSerializer.Serialize(responseDocument, SerializationWriteContext.Document); + return JsonSerializer.Serialize(responseDocument, SerializerWriteOptions); } protected override JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph) diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs index be1a711ad9..d9cfefd0b6 100644 --- a/benchmarks/Serialization/SerializationBenchmarkBase.cs +++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using System.Text.Json.Serialization; using Benchmarks.Tools; using JetBrains.Annotations; @@ -7,7 +8,6 @@ using JsonApiDotNetCore.Queries.Internal; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.Response; using Microsoft.Extensions.Logging.Abstractions; @@ -15,7 +15,7 @@ namespace Benchmarks.Serialization; public abstract class SerializationBenchmarkBase { - protected readonly JsonApiSerializationContext SerializationWriteContext; + protected readonly JsonSerializerOptions SerializerWriteOptions; protected readonly IResponseModelAdapter ResponseModelAdapter; protected readonly IResourceGraph ResourceGraph; @@ -33,7 +33,7 @@ protected SerializationBenchmarkBase() }; ResourceGraph = new ResourceGraphBuilder(options, NullLoggerFactory.Instance).Add().Build(); - SerializationWriteContext = ((IJsonApiOptions)options).SerializationWriteContext; + SerializerWriteOptions = ((IJsonApiOptions)options).SerializerWriteOptions; // ReSharper disable VirtualMemberCallInConstructor JsonApiRequest request = CreateJsonApiRequest(ResourceGraph); diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 477500d79b..597d22294d 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -1,8 +1,6 @@ using System.Data; using System.Text.Json; -using JetBrains.Annotations; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.Objects; namespace JsonApiDotNetCore.Configuration; @@ -10,7 +8,6 @@ namespace JsonApiDotNetCore.Configuration; /// /// Global options that configure the behavior of JsonApiDotNetCore. /// -[PublicAPI] public interface IJsonApiOptions { /// @@ -159,27 +156,13 @@ public interface IJsonApiOptions /// JsonSerializerOptions SerializerOptions { get; } - /// - /// Gets the source-generated JSON serialization context used for deserializing request bodies. This value is based on - /// and is intended for internal use. - /// - JsonApiSerializationContext SerializationReadContext { get; } - /// /// Gets the settings used for deserializing request bodies. This value is based on and is intended for internal use. /// - [Obsolete("Use SerializationReadContext.Options instead.")] JsonSerializerOptions SerializerReadOptions { get; } - /// - /// Gets the source-generated JSON serialization context used for serializing response bodies. This value is based on - /// and is intended for internal use. - /// - JsonApiSerializationContext SerializationWriteContext { get; } - /// /// Gets the settings used for serializing response bodies. This value is based on and is intended for internal use. /// - [Obsolete("Use SerializationWriteContext.Options instead.")] JsonSerializerOptions SerializerWriteOptions { get; } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index bb167e4db2..2e7cc54282 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -3,7 +3,6 @@ using System.Text.Json; using JetBrains.Annotations; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.JsonConverters; namespace JsonApiDotNetCore.Configuration; @@ -12,20 +11,14 @@ namespace JsonApiDotNetCore.Configuration; [PublicAPI] public sealed class JsonApiOptions : IJsonApiOptions { - private readonly Lazy _lazySerializerReadContext; - private readonly Lazy _lazySerializerWriteContext; + private readonly Lazy _lazySerializerWriteOptions; + private readonly Lazy _lazySerializerReadOptions; /// - JsonApiSerializationContext IJsonApiOptions.SerializationReadContext => _lazySerializerReadContext.Value; + JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value; /// - JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => ((IJsonApiOptions)this).SerializationReadContext.Options; - - /// - JsonApiSerializationContext IJsonApiOptions.SerializationWriteContext => _lazySerializerWriteContext.Value; - - /// - JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => ((IJsonApiOptions)this).SerializationWriteContext.Options; + JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => _lazySerializerWriteOptions.Value; /// public string? Namespace { get; set; } @@ -117,16 +110,16 @@ static JsonApiOptions() public JsonApiOptions() { - _lazySerializerReadContext = new Lazy(() => new JsonApiSerializationContext(new JsonSerializerOptions(SerializerOptions)), - LazyThreadSafetyMode.ExecutionAndPublication); + _lazySerializerReadOptions = + new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.ExecutionAndPublication); - _lazySerializerWriteContext = new Lazy(() => new JsonApiSerializationContext(new JsonSerializerOptions(SerializerOptions) + _lazySerializerWriteOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions) { Converters = { new WriteOnlyDocumentConverter(), new WriteOnlyRelationshipObjectConverter() } - }), LazyThreadSafetyMode.ExecutionAndPublication); + }, LazyThreadSafetyMode.ExecutionAndPublication); } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 6ea3853e92..2e15e6ae9a 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -4,7 +4,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Diagnostics; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -45,7 +44,7 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin using (CodeTimingSessionManager.Current.Measure("JSON:API middleware")) { - if (!await ValidateIfMatchHeaderAsync(httpContext, options.SerializationWriteContext)) + if (!await ValidateIfMatchHeaderAsync(httpContext, options.SerializerWriteOptions)) { return; } @@ -55,8 +54,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin if (primaryResourceType != null) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializationWriteContext) || - !await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializationWriteContext)) + if (!await ValidateContentTypeHeaderAsync(HeaderConstants.MediaType, httpContext, options.SerializerWriteOptions) || + !await ValidateAcceptHeaderAsync(MediaType, httpContext, options.SerializerWriteOptions)) { return; } @@ -67,8 +66,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin } else if (IsRouteForOperations(routeValues)) { - if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializationWriteContext) || - !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializationWriteContext)) + if (!await ValidateContentTypeHeaderAsync(HeaderConstants.AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions) || + !await ValidateAcceptHeaderAsync(AtomicOperationsMediaType, httpContext, options.SerializerWriteOptions)) { return; } @@ -92,11 +91,11 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin } } - private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, JsonApiSerializationContext serializationContext) + private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, JsonSerializerOptions serializerOptions) { if (httpContext.Request.Headers.ContainsKey(HeaderNames.IfMatch)) { - await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.PreconditionFailed) + await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.PreconditionFailed) { Title = "Detection of mid-air edit collisions using ETags is not supported.", Source = new ErrorSource @@ -121,14 +120,13 @@ private async Task ValidateIfMatchHeaderAsync(HttpContext httpContext, Jso : null; } - private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, - JsonApiSerializationContext serializationContext) + private static async Task ValidateContentTypeHeaderAsync(string allowedContentType, HttpContext httpContext, JsonSerializerOptions serializerOptions) { string? contentType = httpContext.Request.ContentType; if (contentType != null && contentType != allowedContentType) { - await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.UnsupportedMediaType) + await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.UnsupportedMediaType) { Title = "The specified Content-Type header value is not supported.", Detail = $"Please specify '{allowedContentType}' instead of '{contentType}' for the Content-Type header value.", @@ -145,7 +143,7 @@ private static async Task ValidateContentTypeHeaderAsync(string allowedCon } private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue allowedMediaTypeValue, HttpContext httpContext, - JsonApiSerializationContext serializationContext) + JsonSerializerOptions serializerOptions) { string[] acceptHeaders = httpContext.Request.Headers.GetCommaSeparatedValues("Accept"); @@ -178,7 +176,7 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a if (!seenCompatibleMediaType) { - await FlushResponseAsync(httpContext.Response, serializationContext, new ErrorObject(HttpStatusCode.NotAcceptable) + await FlushResponseAsync(httpContext.Response, serializerOptions, new ErrorObject(HttpStatusCode.NotAcceptable) { Title = "The specified Accept header value does not contain any supported media types.", Detail = $"Please include '{allowedMediaTypeValue}' in the Accept header values.", @@ -194,7 +192,7 @@ private static async Task ValidateAcceptHeaderAsync(MediaTypeHeaderValue a return true; } - private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonApiSerializationContext serializationContext, ErrorObject error) + private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonSerializerOptions serializerOptions, ErrorObject error) { httpResponse.ContentType = HeaderConstants.MediaType; httpResponse.StatusCode = (int)error.StatusCode; @@ -204,7 +202,7 @@ private static async Task FlushResponseAsync(HttpResponse httpResponse, JsonApiS Errors = error.AsList() }; - await JsonSerializer.SerializeAsync(httpResponse.Body, errorDocument, serializationContext.Document); + await JsonSerializer.SerializeAsync(httpResponse.Body, errorDocument, serializerOptions); await httpResponse.Body.FlushAsync(); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs deleted file mode 100644 index 38180d9369..0000000000 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializationContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; -using JsonApiDotNetCore.Serialization.Objects; - -namespace JsonApiDotNetCore.Serialization; - -// Workaround for https://youtrack.jetbrains.com/issue/RSRP-487028 -partial class JsonApiSerializationContext -{ -} - -/// -/// Provides compile-time metadata about the set of JSON:API types used in JSON serialization of request/response bodies. -/// -[JsonSerializable(typeof(Document))] -public sealed partial class JsonApiSerializationContext : JsonSerializerContext -{ -} diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs index 97d7589cc6..32e4351e12 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/JsonObjectConverter.cs @@ -7,12 +7,24 @@ public abstract class JsonObjectConverter : JsonConverter { protected static TValue? ReadSubTree(ref Utf8JsonReader reader, JsonSerializerOptions options) { + if (typeof(TValue) != typeof(object) && options.GetConverter(typeof(TValue)) is JsonConverter converter) + { + return converter.Read(ref reader, typeof(TValue), options); + } + return JsonSerializer.Deserialize(ref reader, options); } protected static void WriteSubTree(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + if (typeof(TValue) != typeof(object) && options.GetConverter(typeof(TValue)) is JsonConverter converter) + { + converter.Write(writer, value, options); + } + else + { + JsonSerializer.Serialize(writer, value, options); + } } protected static JsonException GetEndOfStreamError() diff --git a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs index 87c3a0acfa..2f40aeb27b 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs @@ -1,12 +1,10 @@ using System.Text.Json.Serialization; -using JetBrains.Annotations; namespace JsonApiDotNetCore.Serialization.Objects; /// /// See https://jsonapi.org/format/1.1/#document-top-level and https://jsonapi.org/ext/atomic/#document-structure. /// -[PublicAPI] public sealed class Document { [JsonPropertyName("jsonapi")] diff --git a/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs index 0282ab39bd..0942683487 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs @@ -80,7 +80,7 @@ private Document DeserializeDocument(string requestBody) using IDisposable _ = CodeTimingSessionManager.Current.Measure("JsonSerializer.Deserialize", MeasurementSettings.ExcludeJsonSerializationInPercentages); - Document? document = JsonSerializer.Deserialize(requestBody, _options.SerializationReadContext.Document); + var document = JsonSerializer.Deserialize(requestBody, _options.SerializerReadOptions); AssertHasDocument(document, requestBody); diff --git a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs index 2bfefde98b..20f4ad242b 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs @@ -125,7 +125,7 @@ private string SerializeDocument(Document document) { using IDisposable _ = CodeTimingSessionManager.Current.Measure("JsonSerializer.Serialize", MeasurementSettings.ExcludeJsonSerializationInPercentages); - return JsonSerializer.Serialize(document, _options.SerializationWriteContext.Document); + return JsonSerializer.Serialize(document, _options.SerializerWriteOptions); } private bool SetETagResponseHeader(HttpRequest request, HttpResponse response, string responseContent) diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs index f0af041de5..d9459f7ec1 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs @@ -38,7 +38,7 @@ public void Resources_in_deeply_nested_circular_chain_are_written_in_relationshi Document document = responseModelAdapter.Convert(article); // Assert - string text = JsonSerializer.Serialize(document, options.SerializationWriteContext.Document); + string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); text.Should().BeJson(@"{ ""data"": { @@ -175,7 +175,7 @@ public void Resources_in_deeply_nested_circular_chains_are_written_in_relationsh }); // Assert - string text = JsonSerializer.Serialize(document, options.SerializationWriteContext.Document); + string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); text.Should().BeJson(@"{ ""data"": [ @@ -333,7 +333,7 @@ public void Resources_in_overlapping_deeply_nested_circular_chains_are_written_i Document document = responseModelAdapter.Convert(article); // Assert - string text = JsonSerializer.Serialize(document, options.SerializationWriteContext.Document); + string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); text.Should().BeJson(@"{ ""data"": { From aedb16496dfa8f4e8861e0bac6cc9512bdbd7e31 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 3 Sep 2022 17:40:09 +0200 Subject: [PATCH 20/50] Package updates --- Directory.Build.props | 8 ++++---- benchmarks/Benchmarks.csproj | 7 +++++-- test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 515d7de1ba..380996c725 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ 6.0.* 6.0.* 6.0.* - 4.2.* + 4.3.* 2.14.1 5.0.3 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset @@ -17,7 +17,7 @@ - + @@ -34,7 +34,7 @@ 3.1.2 - 4.18.1 - 17.2.0 + 4.18.2 + 17.3.1 diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj index f461a4831b..3958713af4 100644 --- a/benchmarks/Benchmarks.csproj +++ b/benchmarks/Benchmarks.csproj @@ -1,4 +1,4 @@ - + Exe $(TargetFrameworkName) @@ -10,6 +10,9 @@ - + + + + diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 2f383d72e9..ed335f630f 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -16,7 +16,7 @@ - + From e4020c629b7165709c5b2bbf598b819f0a68918d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:44:48 +0200 Subject: [PATCH 21/50] Increment version number (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 380996c725..3a3d018a65 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ 6.0.* 4.3.* 2.14.1 - 5.0.3 + 5.0.4 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable From 6b69ed3f8f396348b1665e6395acfb77ab74c4bd Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 9 Sep 2022 14:36:27 +0200 Subject: [PATCH 22/50] Fixed error in documentation example --- docs/usage/extensibility/controllers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md index 0c71f45090..7e54d3fb9c 100644 --- a/docs/usage/extensibility/controllers.md +++ b/docs/usage/extensibility/controllers.md @@ -39,7 +39,7 @@ DELETE http://localhost:14140/articles/1 HTTP/1.1 ```json { "links": { - "self": "/articles" + "self": "/articles/1" }, "errors": [ { From 79d918a7a442de304205a5195cd707215d472a93 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 1 Oct 2022 12:41:40 +0200 Subject: [PATCH 23/50] Update links to JSON:API v1.1-final --- README.md | 2 +- docs/usage/reading/including-relationships.md | 2 +- src/JsonApiDotNetCore/Serialization/Objects/Document.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/ErrorLinks.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/JsonApiObject.cs | 2 +- .../Serialization/Objects/RelationshipLinks.cs | 2 +- .../Serialization/Objects/RelationshipObject.cs | 2 +- .../Serialization/Objects/ResourceIdentifierObject.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/ResourceLinks.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs | 2 +- src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e2291f080e..35adb3fe9f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ These are some steps you can take to help you understand what this project is an - [Embercasts: Full Stack Ember with ASP.NET Core](https://www.embercasts.com/course/full-stack-ember-with-dotnet/watch/whats-in-this-course-cs) (paid course, 2017) ### Official documentation -- [The JSON:API specification](https://jsonapi.org/format/1.1/) +- [The JSON:API specification](https://jsonapi.org/format/) - [JsonApiDotNetCore website](https://www.jsonapi.net/) - [Roadmap](ROADMAP.md) diff --git a/docs/usage/reading/including-relationships.md b/docs/usage/reading/including-relationships.md index f22d2321aa..0b69a007c1 100644 --- a/docs/usage/reading/including-relationships.md +++ b/docs/usage/reading/including-relationships.md @@ -1,6 +1,6 @@ # Including Relationships -JsonApiDotNetCore supports [request include params](http://jsonapi.org/format/#fetching-includes) out of the box, +JsonApiDotNetCore supports [request include params](https://jsonapi.org/format/#fetching-includes) out of the box, for side-loading related resources. ```http diff --git a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs index 2f40aeb27b..e0d7d5def3 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/Document.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/Document.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#document-top-level and https://jsonapi.org/ext/atomic/#document-structure. +/// See https://jsonapi.org/format#document-top-level and https://jsonapi.org/ext/atomic/#document-structure. /// public sealed class Document { diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorLinks.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorLinks.cs index 4b8d4de528..6ba2c2a6f6 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorLinks.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorLinks.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See "links" in https://jsonapi.org/format/1.1/#error-objects. +/// See "links" in https://jsonapi.org/format/#error-objects. /// [PublicAPI] public sealed class ErrorLinks diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs index 87ad1ebefe..68a2a19d3a 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorObject.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#error-objects. +/// See https://jsonapi.org/format/#error-objects. /// [PublicAPI] public sealed class ErrorObject diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs b/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs index 156f734aa2..b9242895f4 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ErrorSource.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See "source" in https://jsonapi.org/format/1.1/#error-objects. +/// See "source" in https://jsonapi.org/format/#error-objects. /// [PublicAPI] public sealed class ErrorSource diff --git a/src/JsonApiDotNetCore/Serialization/Objects/JsonApiObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/JsonApiObject.cs index 4a48f90099..66daec22ff 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/JsonApiObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/JsonApiObject.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#document-jsonapi-object. +/// See https://jsonapi.org/format/#document-jsonapi-object. /// [PublicAPI] public sealed class JsonApiObject diff --git a/src/JsonApiDotNetCore/Serialization/Objects/RelationshipLinks.cs b/src/JsonApiDotNetCore/Serialization/Objects/RelationshipLinks.cs index f3f6c2bf02..4c1095e1a7 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/RelationshipLinks.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/RelationshipLinks.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See "links" in https://jsonapi.org/format/1.1/#document-resource-object-relationships. +/// See "links" in https://jsonapi.org/format/#document-resource-object-relationships. /// [PublicAPI] public sealed class RelationshipLinks diff --git a/src/JsonApiDotNetCore/Serialization/Objects/RelationshipObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/RelationshipObject.cs index 9411ecf83a..c677e9a0fb 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/RelationshipObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/RelationshipObject.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#document-resource-object-relationships. +/// See https://jsonapi.org/format/#document-resource-object-relationships. /// [PublicAPI] public sealed class RelationshipObject diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs index 20c30909ed..e82ebe16bf 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#document-resource-identifier-objects. +/// See https://jsonapi.org/format/#document-resource-identifier-objects. /// [PublicAPI] public class ResourceIdentifierObject : ResourceIdentity diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceLinks.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceLinks.cs index 22c396082d..6f749cca88 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceLinks.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceLinks.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#document-resource-object-links. +/// See https://jsonapi.org/format/#document-resource-object-links. /// [PublicAPI] public sealed class ResourceLinks diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs index ed38a40f9a..fc1ff3d146 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See https://jsonapi.org/format/1.1/#document-resource-objects. +/// See https://jsonapi.org/format/#document-resource-objects. /// [PublicAPI] public sealed class ResourceObject : ResourceIdentifierObject diff --git a/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs b/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs index 14578253c2..f83510fb23 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// -/// See "links" in https://jsonapi.org/format/1.1/#document-top-level. +/// See "links" in https://jsonapi.org/format/#document-top-level. /// [PublicAPI] public sealed class TopLevelLinks From 1dc249bf5c8f46216a871913f6d9931a91c59525 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 28 Sep 2022 04:39:53 +0200 Subject: [PATCH 24/50] Improve error message when duplicate controllers are found, to include the fully qualified type names of the conflicting controllers --- src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs | 3 ++- .../DuplicateResourceControllerTests.cs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index fe95d93446..58f8d18560 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -78,7 +78,8 @@ public void Apply(ApplicationModel application) { if (_controllerPerResourceTypeMap.ContainsKey(resourceType)) { - throw new InvalidConfigurationException($"Multiple controllers found for resource type '{resourceType}'."); + throw new InvalidConfigurationException( + $"Multiple controllers found for resource type '{resourceType}': '{_controllerPerResourceTypeMap[resourceType].ControllerType}' and '{controller.ControllerType}'."); } _resourceTypePerControllerTypeMap.Add(controller.ControllerType, resourceType); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateResourceControllerTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateResourceControllerTests.cs index b130523588..0f1b1178f4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateResourceControllerTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/DuplicateResourceControllerTests.cs @@ -20,6 +20,9 @@ public void Fails_at_startup_when_multiple_controllers_exist_for_same_resource_t Action action = () => _ = Factory; // Assert - action.Should().ThrowExactly().WithMessage("Multiple controllers found for resource type 'knownResources'."); + InvalidConfigurationException exception = action.Should().ThrowExactly().Which!; + exception.Message.Should().StartWith("Multiple controllers found for resource type 'knownResources': "); + exception.Message.Should().Contain($"'{typeof(KnownResourcesController).FullName}'"); + exception.Message.Should().Contain($"'{typeof(DuplicateKnownResourcesController).FullName}'"); } } From 70c06b941d2201c53c8e0940cd4cf0420cfa40df Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 28 Sep 2022 03:14:21 +0200 Subject: [PATCH 25/50] Multi-target Annotations against .NET Standard 1.0 --- JsonApiDotNetCore.sln | 15 +++++++++++++ ...ndpoints.cs => JsonApiEndpoints.shared.cs} | 0 .../JsonApiDotNetCore.Annotations.csproj | 22 +++++++++++++++++-- .../Annotations/AttrAttribute.netstandard.cs | 14 ++++++++++++ ...bilities.cs => AttrCapabilities.shared.cs} | 3 +++ .../EagerLoadAttribute.netstandard.cs | 12 ++++++++++ .../HasManyAttribute.netstandard.cs | 12 ++++++++++ .../HasOneAttribute.netstandard.cs | 12 ++++++++++ .../{LinkTypes.cs => LinkTypes.shared.cs} | 0 ...ibute.cs => NoResourceAttribute.shared.cs} | 0 .../RelationshipAttribute.netstandard.cs | 16 ++++++++++++++ ...tribute.cs => ResourceAttribute.shared.cs} | 0 .../ResourceFieldAttribute.netstandard.cs | 13 +++++++++++ ...te.cs => ResourceLinksAttribute.shared.cs} | 0 ...dentifiable.cs => IIdentifiable.shared.cs} | 4 ++++ .../Resources/Identifiable.netstandard.cs | 16 ++++++++++++++ test/AnnotationTests/AnnotationTests.csproj | 20 +++++++++++++++++ test/AnnotationTests/Models/HiddenNode.cs | 14 ++++++++++++ test/AnnotationTests/Models/TreeNode.cs | 20 +++++++++++++++++ 19 files changed, 191 insertions(+), 2 deletions(-) rename src/JsonApiDotNetCore.Annotations/Controllers/{JsonApiEndpoints.cs => JsonApiEndpoints.shared.cs} (100%) create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs rename src/JsonApiDotNetCore.Annotations/Resources/Annotations/{AttrCapabilities.cs => AttrCapabilities.shared.cs} (96%) create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs rename src/JsonApiDotNetCore.Annotations/Resources/Annotations/{LinkTypes.cs => LinkTypes.shared.cs} (100%) rename src/JsonApiDotNetCore.Annotations/Resources/Annotations/{NoResourceAttribute.cs => NoResourceAttribute.shared.cs} (100%) create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs rename src/JsonApiDotNetCore.Annotations/Resources/Annotations/{ResourceAttribute.cs => ResourceAttribute.shared.cs} (100%) create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs rename src/JsonApiDotNetCore.Annotations/Resources/Annotations/{ResourceLinksAttribute.cs => ResourceLinksAttribute.shared.cs} (100%) rename src/JsonApiDotNetCore.Annotations/Resources/{IIdentifiable.cs => IIdentifiable.shared.cs} (94%) create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs create mode 100644 test/AnnotationTests/AnnotationTests.csproj create mode 100644 test/AnnotationTests/Models/HiddenNode.cs create mode 100644 test/AnnotationTests/Models/TreeNode.cs diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln index 21b1cca7ce..e034595875 100644 --- a/JsonApiDotNetCore.sln +++ b/JsonApiDotNetCore.sln @@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.Annotatio EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabasePerTenantExample", "src\Examples\DatabasePerTenantExample\DatabasePerTenantExample.csproj", "{60334658-BE51-43B3-9C4D-F2BBF56C89CE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnnotationTests", "test\AnnotationTests\AnnotationTests.csproj", "{24B0C12F-38CD-4245-8785-87BEFAD55B00}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -266,6 +268,18 @@ Global {60334658-BE51-43B3-9C4D-F2BBF56C89CE}.Release|x64.Build.0 = Release|Any CPU {60334658-BE51-43B3-9C4D-F2BBF56C89CE}.Release|x86.ActiveCfg = Release|Any CPU {60334658-BE51-43B3-9C4D-F2BBF56C89CE}.Release|x86.Build.0 = Release|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x64.ActiveCfg = Debug|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x64.Build.0 = Debug|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x86.ActiveCfg = Debug|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Debug|x86.Build.0 = Debug|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|Any CPU.Build.0 = Release|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x64.ActiveCfg = Release|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x64.Build.0 = Release|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x86.ActiveCfg = Release|Any CPU + {24B0C12F-38CD-4245-8785-87BEFAD55B00}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -288,6 +302,7 @@ Global {0E0B5C51-F7E2-4F40-A4E4-DED0E9731DC9} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {83FF097C-C8C6-477B-9FAB-DF99B84978B5} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} {60334658-BE51-43B3-9C4D-F2BBF56C89CE} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {24B0C12F-38CD-4245-8785-87BEFAD55B00} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4} diff --git a/src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.cs b/src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.shared.cs similarity index 100% rename from src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.cs rename to src/JsonApiDotNetCore.Annotations/Controllers/JsonApiEndpoints.shared.cs diff --git a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj index 7c78f620ed..5f4d7c1d76 100644 --- a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj +++ b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj @@ -1,9 +1,10 @@ - + - $(TargetFrameworkName) + $(TargetFrameworkName);netstandard1.0 true true JsonApiDotNetCore + latest @@ -27,4 +28,21 @@ + + + + + + + + + + + + + + + + + diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs new file mode 100644 index 0000000000..a7915240dc --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.netstandard.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Property)] +public sealed class AttrAttribute : ResourceFieldAttribute +{ + /// + public AttrCapabilities Capabilities { get; set; } +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs similarity index 96% rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs index c6f849fdff..2812be6d39 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs @@ -1,8 +1,11 @@ +using JetBrains.Annotations; + namespace JsonApiDotNetCore.Resources.Annotations; /// /// Indicates capabilities that can be performed on an . /// +[PublicAPI] [Flags] public enum AttrCapabilities { diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs new file mode 100644 index 0000000000..47052a078c --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/EagerLoadAttribute.netstandard.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Property)] +public sealed class EagerLoadAttribute : Attribute +{ +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs new file mode 100644 index 0000000000..1cdeb9f62f --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Property)] +public sealed class HasManyAttribute : RelationshipAttribute +{ +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs new file mode 100644 index 0000000000..1c16fb01b2 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Property)] +public sealed class HasOneAttribute : RelationshipAttribute +{ +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs similarity index 100% rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.cs rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.shared.cs similarity index 100% rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.cs rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/NoResourceAttribute.shared.cs diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs new file mode 100644 index 0000000000..51517b3d51 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +[PublicAPI] +public abstract class RelationshipAttribute : ResourceFieldAttribute +{ + /// + public LinkTypes Links { get; set; } = LinkTypes.NotConfigured; + + /// + public bool CanInclude { get; set; } = true; +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.shared.cs similarity index 100% rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.cs rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceAttribute.shared.cs diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs new file mode 100644 index 0000000000..958794365d --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.netstandard.cs @@ -0,0 +1,13 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +[PublicAPI] +public abstract class ResourceFieldAttribute : Attribute +{ + /// + public string PublicName { get; set; } = null!; +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs similarity index 100% rename from src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.cs rename to src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs diff --git a/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.cs b/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.shared.cs similarity index 94% rename from src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.cs rename to src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.shared.cs index 2c6dc02025..b6dc5e3b22 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/IIdentifiable.shared.cs @@ -1,8 +1,11 @@ +using JetBrains.Annotations; + namespace JsonApiDotNetCore.Resources; /// /// Defines the basic contract for a JSON:API resource. All resource classes must implement . /// +[PublicAPI] public interface IIdentifiable { /// @@ -22,6 +25,7 @@ public interface IIdentifiable /// /// The resource identifier type. /// +[PublicAPI] public interface IIdentifiable : IIdentifiable { /// diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs new file mode 100644 index 0000000000..a5a7179af6 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Identifiable.netstandard.cs @@ -0,0 +1,16 @@ +namespace JsonApiDotNetCore.Resources; + +/// +/// A simplified version, provided for convenience to multi-target against NetStandard. Does not actually work with JsonApiDotNetCore. +/// +public abstract class Identifiable : IIdentifiable +{ + /// + public virtual TId Id { get; set; } = default!; + + /// + public string? StringId { get; set; } + + /// + public string? LocalId { get; set; } +} diff --git a/test/AnnotationTests/AnnotationTests.csproj b/test/AnnotationTests/AnnotationTests.csproj new file mode 100644 index 0000000000..51df20d735 --- /dev/null +++ b/test/AnnotationTests/AnnotationTests.csproj @@ -0,0 +1,20 @@ + + + $(TargetFrameworkName);netstandard1.0 + latest + + + + + PreserveNewest + + + + + + + + + + + diff --git a/test/AnnotationTests/Models/HiddenNode.cs b/test/AnnotationTests/Models/HiddenNode.cs new file mode 100644 index 0000000000..c348f3b5b0 --- /dev/null +++ b/test/AnnotationTests/Models/HiddenNode.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace AnnotationTests.Models; + +[PublicAPI] +[NoResource] +[ResourceLinks(TopLevelLinks = LinkTypes.None, ResourceLinks = LinkTypes.None, RelationshipLinks = LinkTypes.None)] +public sealed class HiddenNode : Identifiable +{ + [EagerLoad] + public HiddenNode? Parent { get; set; } +} diff --git a/test/AnnotationTests/Models/TreeNode.cs b/test/AnnotationTests/Models/TreeNode.cs new file mode 100644 index 0000000000..955db81720 --- /dev/null +++ b/test/AnnotationTests/Models/TreeNode.cs @@ -0,0 +1,20 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace AnnotationTests.Models; + +[PublicAPI] +[Resource(PublicName = "tree-node", ControllerNamespace = "Models", GenerateControllerEndpoints = JsonApiEndpoints.Query)] +public sealed class TreeNode : Identifiable +{ + [Attr(PublicName = "name", Capabilities = AttrCapabilities.AllowSort)] + public string? DisplayName { get; set; } + + [HasOne(PublicName = "orders", CanInclude = true, Links = LinkTypes.All)] + public TreeNode? Parent { get; set; } + + [HasMany(PublicName = "orders", CanInclude = true, Links = LinkTypes.All)] + public ISet Children { get; set; } = new HashSet(); +} From ba0278b2e678caa94a0003fc951407be2ebb13c4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 00:45:00 +0200 Subject: [PATCH 26/50] Update to latest Resharper version (disabled in Ubuntu cibuild) --- .config/dotnet-tools.json | 2 +- Build.ps1 | 7 +++++-- .../Configuration/ResourceGraphBuilder.cs | 2 +- test/TestBuildingBlocks/IntegrationTest.cs | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3bd80e6084..d914fc369d 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2021.3.4", + "version": "2022.2.3", "commands": [ "jb" ] diff --git a/Build.ps1 b/Build.ps1 index 4a92feed89..daccb81df1 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -105,8 +105,11 @@ CheckLastExitCode dotnet build -c Release CheckLastExitCode -RunInspectCode -RunCleanupCode +# https://youtrack.jetbrains.com/issue/RSRP-488628/Breaking-InspectCode-fails-with-Roslyn-Worker-process-exited-unexpectedly-after-update +if ($env:APPVEYOR_BUILD_WORKER_IMAGE -ne 'Ubuntu') { + RunInspectCode + RunCleanupCode +} dotnet test -c Release --no-build --collect:"XPlat Code Coverage" CheckLastExitCode diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index e07318c7f7..36eb9d37e8 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -317,7 +317,7 @@ private IReadOnlyCollection GetRelationships(Type resourc private void SetPublicName(ResourceFieldAttribute field, PropertyInfo property) { - // ReSharper disable once ConstantNullCoalescingCondition + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract field.PublicName ??= FormatPropertyName(property); } diff --git a/test/TestBuildingBlocks/IntegrationTest.cs b/test/TestBuildingBlocks/IntegrationTest.cs index 99ced55b01..4fb2e5cd26 100644 --- a/test/TestBuildingBlocks/IntegrationTest.cs +++ b/test/TestBuildingBlocks/IntegrationTest.cs @@ -79,8 +79,7 @@ public abstract class IntegrationTest private string? SerializeRequest(object? requestBody) { - return requestBody == null ? null : - requestBody is string stringRequestBody ? stringRequestBody : JsonSerializer.Serialize(requestBody, SerializerOptions); + return requestBody == null ? null : requestBody as string ?? JsonSerializer.Serialize(requestBody, SerializerOptions); } protected abstract HttpClient CreateClient(); From 4c61b4998861deb819b9cde19443f16560029d62 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 05:31:24 +0200 Subject: [PATCH 27/50] Revert workaround for bug that was fixed in recent Resharper version --- Build.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index daccb81df1..d8be7da5e6 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -8,8 +8,7 @@ function CheckLastExitCode { function RunInspectCode { $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') - # passing --build instead of --no-build as workaround for https://youtrack.jetbrains.com/issue/RSRP-487054 - dotnet jb inspectcode JsonApiDotNetCore.sln --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal + dotnet jb inspectcode JsonApiDotNetCore.sln --no-build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal CheckLastExitCode [xml]$xml = Get-Content "$outputPath" From add5bdad36bdd17a563bc7a8aa7302ae9ae9f85e Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 00:56:34 +0200 Subject: [PATCH 28/50] Reduce the severity of 'Introduce optional parameters' for non-private members to Hint level, because they tend to cause backwards-compatibility issues in publicly exposed signatures --- JsonApiDotNetCore.sln.DotSettings | 1 + WarningSeverities.DotSettings | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index 95c0c2b7b2..a40e86f270 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -54,6 +54,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); WARNING WARNING WARNING + HINT WARNING DO_NOT_SHOW HINT diff --git a/WarningSeverities.DotSettings b/WarningSeverities.DotSettings index f4a9ae32e8..0d4eeba96f 100644 --- a/WarningSeverities.DotSettings +++ b/WarningSeverities.DotSettings @@ -87,7 +87,6 @@ WARNING WARNING WARNING - WARNING WARNING WARNING WARNING From 3a1e039674fd556e1dcab05d84e562fc9e688e57 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 01:28:45 +0200 Subject: [PATCH 29/50] Reduce the use of nameof() in parameter validations by using CallerArgumentExpressionAttribute instead --- JsonApiDotNetCore.sln.DotSettings | 19 ++++++- .../ArgumentGuard.cs | 32 ++++------- .../CollectionConverter.cs | 6 +- .../Configuration/ResourceType.cs | 16 +++--- .../Annotations/RelationshipAttribute.cs | 2 +- .../Annotations/ResourceFieldAttribute.cs | 8 +-- .../Internal/RuntimeTypeConverter.cs | 2 +- .../TypeExtensions.cs | 4 +- .../EntityFrameworkCoreTransaction.cs | 4 +- .../EntityFrameworkCoreTransactionFactory.cs | 4 +- .../AtomicOperations/LocalIdTracker.cs | 14 ++--- .../AtomicOperations/LocalIdValidator.cs | 6 +- .../OperationProcessorAccessor.cs | 4 +- .../AtomicOperations/OperationsProcessor.cs | 16 +++--- .../Processors/AddToRelationshipProcessor.cs | 4 +- .../Processors/CreateProcessor.cs | 6 +- .../Processors/DeleteProcessor.cs | 4 +- .../RemoveFromRelationshipProcessor.cs | 4 +- .../Processors/SetRelationshipProcessor.cs | 4 +- .../Processors/UpdateProcessor.cs | 4 +- .../RevertRequestStateOnDispose.cs | 2 +- src/JsonApiDotNetCore/CollectionExtensions.cs | 8 +-- .../ApplicationBuilderExtensions.cs | 2 +- .../InverseNavigationResolver.cs | 4 +- .../JsonApiApplicationBuilder.cs | 8 +-- .../Configuration/JsonApiValidationFilter.cs | 2 +- .../Configuration/ResourceDescriptor.cs | 4 +- .../Configuration/ResourceGraph.cs | 12 ++-- .../Configuration/ResourceGraphBuilder.cs | 8 +-- .../Configuration/ResourceNameFormatter.cs | 2 +- .../ServiceCollectionExtensions.cs | 8 +-- .../Configuration/ServiceDiscoveryFacade.cs | 8 +-- .../Configuration/TypeLocator.cs | 16 +++--- .../DisableQueryStringAttribute.cs | 2 +- .../Controllers/BaseJsonApiController.cs | 24 ++++---- .../BaseJsonApiOperationsController.cs | 14 ++--- .../Controllers/CoreJsonApiController.cs | 4 +- .../Diagnostics/AspNetCodeTimerSession.cs | 4 +- .../Diagnostics/CodeTimingSessionManager.cs | 2 +- .../Errors/InvalidModelStateException.cs | 20 +++---- .../Errors/JsonApiException.cs | 4 +- .../Errors/MissingResourceInRelationship.cs | 6 +- .../UnsuccessfulActionResultException.cs | 2 +- .../AsyncConvertEmptyActionResultFilter.cs | 4 +- .../Middleware/AsyncJsonApiExceptionFilter.cs | 4 +- .../AsyncQueryStringActionFilter.cs | 6 +- .../Middleware/ExceptionHandler.cs | 12 ++-- .../Middleware/HttpContextExtensions.cs | 4 +- .../Middleware/JsonApiInputFormatter.cs | 4 +- .../Middleware/JsonApiMiddleware.cs | 10 ++-- .../Middleware/JsonApiOutputFormatter.cs | 4 +- .../Middleware/JsonApiRequest.cs | 2 +- .../Middleware/JsonApiRoutingConvention.cs | 6 +- .../Queries/ExpressionInScope.cs | 2 +- .../Queries/Expressions/AnyExpression.cs | 4 +- .../Expressions/ComparisonExpression.cs | 4 +- .../Queries/Expressions/CountExpression.cs | 2 +- .../Queries/Expressions/HasExpression.cs | 2 +- .../Expressions/IncludeChainConverter.cs | 2 +- .../Expressions/IncludeElementExpression.cs | 4 +- .../Queries/Expressions/IncludeExpression.cs | 2 +- .../Queries/Expressions/IsTypeExpression.cs | 2 +- .../Expressions/LiteralConstantExpression.cs | 2 +- .../Queries/Expressions/LogicalExpression.cs | 4 +- .../Expressions/MatchTextExpression.cs | 4 +- .../Queries/Expressions/NotExpression.cs | 2 +- .../Expressions/PaginationExpression.cs | 2 +- .../PaginationQueryStringValueExpression.cs | 2 +- .../QueryStringParameterScopeExpression.cs | 2 +- .../Expressions/QueryableHandlerExpression.cs | 2 +- .../ResourceFieldChainExpression.cs | 4 +- .../Expressions/SortElementExpression.cs | 4 +- .../Queries/Expressions/SortExpression.cs | 2 +- .../Expressions/SparseFieldSetExpression.cs | 2 +- .../SparseFieldSetExpressionExtensions.cs | 8 +-- .../Expressions/SparseFieldTableExpression.cs | 2 +- .../Queries/FieldSelection.cs | 2 +- .../Queries/FieldSelectors.cs | 8 +-- .../Queries/Internal/EvaluatedIncludeCache.cs | 2 +- .../Queries/Internal/Parsing/FilterParser.cs | 4 +- .../Queries/Internal/Parsing/IncludeParser.cs | 4 +- .../Internal/Parsing/PaginationParser.cs | 2 +- .../QueryStringParameterScopeParser.cs | 2 +- .../Internal/Parsing/QueryTokenizer.cs | 2 +- .../Queries/Internal/Parsing/SortParser.cs | 2 +- .../Internal/Parsing/SparseFieldSetParser.cs | 2 +- .../Internal/Parsing/SparseFieldTypeParser.cs | 2 +- .../Queries/Internal/QueryLayerComposer.cs | 56 +++++++++---------- .../QueryableBuilding/IncludeClauseBuilder.cs | 6 +- .../LambdaParameterNameFactory.cs | 2 +- .../LambdaParameterNameScope.cs | 4 +- .../Internal/QueryableBuilding/LambdaScope.cs | 6 +- .../QueryableBuilding/LambdaScopeFactory.cs | 4 +- .../QueryableBuilding/OrderClauseBuilder.cs | 6 +- .../QueryableBuilding/QueryClauseBuilder.cs | 6 +- .../QueryableBuilding/QueryableBuilder.cs | 14 ++--- .../QueryableBuilding/SelectClauseBuilder.cs | 14 ++--- .../SkipTakeClauseBuilder.cs | 6 +- .../QueryableBuilding/WhereClauseBuilder.cs | 8 +-- .../Queries/Internal/SparseFieldSetCache.cs | 10 ++-- src/JsonApiDotNetCore/Queries/QueryLayer.cs | 2 +- .../FilterQueryStringParameterReader.cs | 6 +- .../IncludeQueryStringParameterReader.cs | 4 +- .../Internal/LegacyFilterNotationConverter.cs | 6 +- .../PaginationQueryStringParameterReader.cs | 4 +- .../Internal/QueryStringParameterReader.cs | 4 +- .../Internal/QueryStringReader.cs | 8 +-- .../Internal/RequestQueryStringAccessor.cs | 2 +- ...ourceDefinitionQueryableParameterReader.cs | 4 +- .../SortQueryStringParameterReader.cs | 4 +- ...parseFieldSetQueryStringParameterReader.cs | 4 +- .../Repositories/DbContextExtensions.cs | 10 ++-- .../Repositories/DbContextResolver.cs | 2 +- .../EntityFrameworkCoreRepository.cs | 36 ++++++------ .../ResourceRepositoryAccessor.cs | 8 +-- .../Resources/AbstractResourceWrapper.cs | 2 +- .../Resources/IdentifiableExtensions.cs | 4 +- .../Resources/JsonApiResourceDefinition.cs | 4 +- .../Resources/OperationContainer.cs | 8 +-- .../Resources/ResourceChangeTracker.cs | 10 ++-- .../Resources/ResourceDefinitionAccessor.cs | 50 ++++++++--------- .../Resources/ResourceFactory.cs | 6 +- .../SortExpressionLambdaConverter.cs | 4 +- .../JsonConverters/ResourceObjectConverter.cs | 2 +- .../Adapters/AtomicOperationObjectAdapter.cs | 8 +-- .../Adapters/AtomicReferenceAdapter.cs | 6 +- .../Request/Adapters/AtomicReferenceResult.cs | 4 +- .../Request/Adapters/DocumentAdapter.cs | 10 ++-- .../DocumentInOperationsRequestAdapter.cs | 6 +- ...tInResourceOrRelationshipRequestAdapter.cs | 6 +- .../Adapters/RelationshipDataAdapter.cs | 6 +- .../Adapters/RequestAdapterPosition.cs | 2 +- .../Request/Adapters/RequestAdapterState.cs | 4 +- .../Request/Adapters/ResourceDataAdapter.cs | 8 +-- .../ResourceIdentifierObjectAdapter.cs | 6 +- .../Adapters/ResourceIdentityAdapter.cs | 10 ++-- .../Request/Adapters/ResourceObjectAdapter.cs | 10 ++-- .../Serialization/Request/JsonApiReader.cs | 8 +-- .../Request/JsonInvalidAttributeInfo.cs | 4 +- .../Request/ModelConversionException.cs | 2 +- .../Serialization/Response/ETagGenerator.cs | 2 +- .../Response/FingerprintGenerator.cs | 2 +- .../Serialization/Response/JsonApiWriter.cs | 14 ++--- .../Serialization/Response/LinkBuilder.cs | 18 +++--- .../Serialization/Response/MetaBuilder.cs | 8 +-- .../Response/ResourceObjectTreeNode.cs | 14 ++--- .../Response/ResponseModelAdapter.cs | 16 +++--- .../Services/AsyncCollectionExtensions.cs | 6 +- .../Services/JsonApiResourceService.cs | 32 +++++------ .../MusicTrackReleaseDefinition.cs | 2 +- .../EagerLoading/BuildingDefinition.cs | 2 +- .../HitCountingResourceDefinition.cs | 2 +- .../InjectionDbContext.cs | 2 +- .../InjectionFakers.cs | 2 +- .../ResourceTypeCapturingDefinition.cs | 4 +- .../WheelSortDefinition.cs | 2 +- .../DependencyContainerRegistrationTests.cs | 6 +- .../ServiceCollectionExtensions.cs | 2 +- .../ResourceConstructionExpressionTests.cs | 2 +- 159 files changed, 529 insertions(+), 528 deletions(-) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index a40e86f270..1ffdf4a909 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -600,12 +600,12 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); CSHARP False Replace argument null check with Guard clause - JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$)); + JsonApiDotNetCore.ArgumentGuard.NotNull($argument$); $left$ = $right$; $left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$)); WARNING True - Replace classic argument null check with Guard clause + Replace argument == null check with Guard clause True True False @@ -615,7 +615,7 @@ $left$ = $right$; CSHARP False Replace argument null check with Guard clause - JsonApiDotNetCore.ArgumentGuard.NotNull($argument$, nameof($argument$)); + JsonApiDotNetCore.ArgumentGuard.NotNull($argument$); if ($argument$ == null) throw new ArgumentNullException(nameof($argument$)); WARNING True @@ -631,6 +631,19 @@ $left$ = $right$; $collection$.IsNullOrEmpty() $collection$ == null || !$collection$.Any() WARNING + True + Replace argument is null check with Guard clause + True + True + False + + IdentifierPlaceholder + True + CSHARP + False + JsonApiDotNetCore.ArgumentGuard.NotNull($argument$); + if ($argument$ is null) throw new ArgumentNullException(nameof($argument$)); + WARNING True True True diff --git a/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs b/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs index be336e56a0..e0c786a106 100644 --- a/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore.Annotations/ArgumentGuard.cs @@ -1,52 +1,40 @@ +using System.Runtime.CompilerServices; using JetBrains.Annotations; using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; #pragma warning disable AV1008 // Class should not be static +#pragma warning disable AV1553 // Do not use optional parameters with default value null for strings, collections or tasks namespace JsonApiDotNetCore; internal static class ArgumentGuard { [AssertionMethod] - public static void NotNull([NoEnumeration] [SysNotNull] T? value, [InvokerParameterName] string name) + public static void NotNull([NoEnumeration] [SysNotNull] T? value, [CallerArgumentExpression("value")] string? parameterName = null) where T : class { - if (value is null) - { - throw new ArgumentNullException(name); - } - } - - [AssertionMethod] - public static void NotNullNorEmpty([SysNotNull] IEnumerable? value, [InvokerParameterName] string name) - { - NotNull(value, name); - - if (!value.Any()) - { - throw new ArgumentException($"Must have one or more {name}.", name); - } + ArgumentNullException.ThrowIfNull(value, parameterName); } [AssertionMethod] - public static void NotNullNorEmpty([SysNotNull] IEnumerable? value, [InvokerParameterName] string name, string collectionName) + public static void NotNullNorEmpty([SysNotNull] IEnumerable? value, [CallerArgumentExpression("value")] string? parameterName = null) { - NotNull(value, name); + ArgumentNullException.ThrowIfNull(value, parameterName); if (!value.Any()) { - throw new ArgumentException($"Must have one or more {collectionName}.", name); + throw new ArgumentException("Collection cannot be null or empty.", parameterName); } } [AssertionMethod] - public static void NotNullNorEmpty([SysNotNull] string? value, [InvokerParameterName] string name) + public static void NotNullNorEmpty([SysNotNull] string? value, [CallerArgumentExpression("value")] string? parameterName = null) { - NotNull(value, name); + ArgumentNullException.ThrowIfNull(value, parameterName); if (value == string.Empty) { - throw new ArgumentException("String cannot be null or empty.", name); + throw new ArgumentException("String cannot be null or empty.", parameterName); } } } diff --git a/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs b/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs index a308607c3b..fa1c0c90bd 100644 --- a/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs +++ b/src/JsonApiDotNetCore.Annotations/CollectionConverter.cs @@ -26,8 +26,8 @@ internal sealed class CollectionConverter /// public IEnumerable CopyToTypedCollection(IEnumerable source, Type collectionType) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(collectionType, nameof(collectionType)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(collectionType); Type concreteCollectionType = ToConcreteCollectionType(collectionType); dynamic concreteCollectionInstance = Activator.CreateInstance(concreteCollectionType)!; @@ -121,7 +121,7 @@ public IReadOnlyCollection ExtractResources(object? value) /// public bool TypeCanContainHashSet(Type collectionType) { - ArgumentGuard.NotNull(collectionType, nameof(collectionType)); + ArgumentGuard.NotNull(collectionType); if (collectionType.IsGenericType) { diff --git a/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs b/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs index 515dfe8a63..0263958b00 100644 --- a/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs +++ b/src/JsonApiDotNetCore.Annotations/Configuration/ResourceType.cs @@ -100,9 +100,9 @@ public ResourceType(string publicName, Type clrType, Type identityClrType, IRead LinkTypes topLevelLinks = LinkTypes.NotConfigured, LinkTypes resourceLinks = LinkTypes.NotConfigured, LinkTypes relationshipLinks = LinkTypes.NotConfigured) { - ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName)); - ArgumentGuard.NotNull(clrType, nameof(clrType)); - ArgumentGuard.NotNull(identityClrType, nameof(identityClrType)); + ArgumentGuard.NotNullNorEmpty(publicName); + ArgumentGuard.NotNull(clrType); + ArgumentGuard.NotNull(identityClrType); PublicName = publicName; ClrType = clrType; @@ -153,7 +153,7 @@ public AttrAttribute GetAttributeByPublicName(string publicName) public AttrAttribute? FindAttributeByPublicName(string publicName) { - ArgumentGuard.NotNull(publicName, nameof(publicName)); + ArgumentGuard.NotNull(publicName); return _fieldsByPublicName.TryGetValue(publicName, out ResourceFieldAttribute? field) && field is AttrAttribute attribute ? attribute : null; } @@ -167,7 +167,7 @@ public AttrAttribute GetAttributeByPropertyName(string propertyName) public AttrAttribute? FindAttributeByPropertyName(string propertyName) { - ArgumentGuard.NotNull(propertyName, nameof(propertyName)); + ArgumentGuard.NotNull(propertyName); return _fieldsByPropertyName.TryGetValue(propertyName, out ResourceFieldAttribute? field) && field is AttrAttribute attribute ? attribute : null; } @@ -180,7 +180,7 @@ public RelationshipAttribute GetRelationshipByPublicName(string publicName) public RelationshipAttribute? FindRelationshipByPublicName(string publicName) { - ArgumentGuard.NotNull(publicName, nameof(publicName)); + ArgumentGuard.NotNull(publicName); return _fieldsByPublicName.TryGetValue(publicName, out ResourceFieldAttribute? field) && field is RelationshipAttribute relationship ? relationship @@ -197,7 +197,7 @@ public RelationshipAttribute GetRelationshipByPropertyName(string propertyName) public RelationshipAttribute? FindRelationshipByPropertyName(string propertyName) { - ArgumentGuard.NotNull(propertyName, nameof(propertyName)); + ArgumentGuard.NotNull(propertyName); return _fieldsByPropertyName.TryGetValue(propertyName, out ResourceFieldAttribute? field) && field is RelationshipAttribute relationship ? relationship @@ -217,7 +217,7 @@ public IReadOnlySet GetAllConcreteDerivedTypes() /// public ResourceType GetTypeOrDerived(Type clrType) { - ArgumentGuard.NotNull(clrType, nameof(clrType)); + ArgumentGuard.NotNull(clrType); ResourceType? derivedType = FindTypeOrDerived(this, clrType); diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs index 11320a7abc..c1b24e9567 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs @@ -57,7 +57,7 @@ public ResourceType RightType get => _rightType!; internal set { - ArgumentGuard.NotNull(value, nameof(value)); + ArgumentGuard.NotNull(value); _rightType = value; } } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs index 599b17a42a..9f32610dc9 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs @@ -43,7 +43,7 @@ public PropertyInfo Property get => _property!; internal set { - ArgumentGuard.NotNull(value, nameof(value)); + ArgumentGuard.NotNull(value); _property = value; } } @@ -56,7 +56,7 @@ public ResourceType Type get => _type!; internal set { - ArgumentGuard.NotNull(value, nameof(value)); + ArgumentGuard.NotNull(value); _type = value; } } @@ -67,7 +67,7 @@ internal set /// public object? GetValue(object resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); if (Property.GetMethod == null) { @@ -92,7 +92,7 @@ internal set /// public void SetValue(object resource, object? newValue) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); if (Property.SetMethod == null) { diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs b/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs index 8722458938..d79ed4c635 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Internal/RuntimeTypeConverter.cs @@ -10,7 +10,7 @@ public static class RuntimeTypeConverter { public static object? ConvertType(object? value, Type type) { - ArgumentGuard.NotNull(type, nameof(type)); + ArgumentGuard.NotNull(type); if (value == null) { diff --git a/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs b/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs index c28ea84332..b31f82d48e 100644 --- a/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs +++ b/src/JsonApiDotNetCore.Annotations/TypeExtensions.cs @@ -15,7 +15,7 @@ public static bool IsOrImplementsInterface(this Type? source) /// private static bool IsOrImplementsInterface(this Type? source, Type interfaceType) { - ArgumentGuard.NotNull(interfaceType, nameof(interfaceType)); + ArgumentGuard.NotNull(interfaceType); if (source == null) { @@ -41,7 +41,7 @@ private static bool AreTypesEqual(Type left, Type right, bool isLeftGeneric) /// public static string GetFriendlyTypeName(this Type type) { - ArgumentGuard.NotNull(type, nameof(type)); + ArgumentGuard.NotNull(type); // Based on https://stackoverflow.com/questions/2581642/how-do-i-get-the-type-name-of-a-generic-type-argument. diff --git a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs index be5125c414..f59f86162d 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransaction.cs @@ -19,8 +19,8 @@ public sealed class EntityFrameworkCoreTransaction : IOperationsTransaction public EntityFrameworkCoreTransaction(IDbContextTransaction transaction, DbContext dbContext) { - ArgumentGuard.NotNull(transaction, nameof(transaction)); - ArgumentGuard.NotNull(dbContext, nameof(dbContext)); + ArgumentGuard.NotNull(transaction); + ArgumentGuard.NotNull(dbContext); _transaction = transaction; _dbContext = dbContext; diff --git a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs index 96c66e12ab..8ef44cb627 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/EntityFrameworkCoreTransactionFactory.cs @@ -15,8 +15,8 @@ public sealed class EntityFrameworkCoreTransactionFactory : IOperationsTransacti public EntityFrameworkCoreTransactionFactory(IDbContextResolver dbContextResolver, IJsonApiOptions options) { - ArgumentGuard.NotNull(dbContextResolver, nameof(dbContextResolver)); - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(dbContextResolver); + ArgumentGuard.NotNull(options); _dbContextResolver = dbContextResolver; _options = options; diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs index b0a1b3ee2f..2def7bfecc 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdTracker.cs @@ -17,8 +17,8 @@ public void Reset() /// public void Declare(string localId, ResourceType resourceType) { - ArgumentGuard.NotNullNorEmpty(localId, nameof(localId)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNullNorEmpty(localId); + ArgumentGuard.NotNull(resourceType); AssertIsNotDeclared(localId); @@ -36,9 +36,9 @@ private void AssertIsNotDeclared(string localId) /// public void Assign(string localId, ResourceType resourceType, string stringId) { - ArgumentGuard.NotNullNorEmpty(localId, nameof(localId)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - ArgumentGuard.NotNullNorEmpty(stringId, nameof(stringId)); + ArgumentGuard.NotNullNorEmpty(localId); + ArgumentGuard.NotNull(resourceType); + ArgumentGuard.NotNullNorEmpty(stringId); AssertIsDeclared(localId); @@ -57,8 +57,8 @@ public void Assign(string localId, ResourceType resourceType, string stringId) /// public string GetValue(string localId, ResourceType resourceType) { - ArgumentGuard.NotNullNorEmpty(localId, nameof(localId)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNullNorEmpty(localId); + ArgumentGuard.NotNull(resourceType); AssertIsDeclared(localId); diff --git a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs index 9cb463ab10..fb75fe3c7f 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/LocalIdValidator.cs @@ -18,8 +18,8 @@ public sealed class LocalIdValidator public LocalIdValidator(ILocalIdTracker localIdTracker, IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(localIdTracker); + ArgumentGuard.NotNull(resourceGraph); _localIdTracker = localIdTracker; _resourceGraph = resourceGraph; @@ -27,7 +27,7 @@ public LocalIdValidator(ILocalIdTracker localIdTracker, IResourceGraph resourceG public void Validate(IEnumerable operations) { - ArgumentGuard.NotNull(operations, nameof(operations)); + ArgumentGuard.NotNull(operations); _localIdTracker.Reset(); diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs index c032f78f8d..c2397267b3 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationProcessorAccessor.cs @@ -15,7 +15,7 @@ public class OperationProcessorAccessor : IOperationProcessorAccessor public OperationProcessorAccessor(IServiceProvider serviceProvider) { - ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider)); + ArgumentGuard.NotNull(serviceProvider); _serviceProvider = serviceProvider; } @@ -23,7 +23,7 @@ public OperationProcessorAccessor(IServiceProvider serviceProvider) /// public Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); IOperationProcessor processor = ResolveProcessor(operation); return processor.ProcessAsync(operation, cancellationToken); diff --git a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs index 6524252abf..6ecdfd6077 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/OperationsProcessor.cs @@ -25,13 +25,13 @@ public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccesso ILocalIdTracker localIdTracker, IResourceGraph resourceGraph, IJsonApiRequest request, ITargetedFields targetedFields, ISparseFieldSetCache sparseFieldSetCache) { - ArgumentGuard.NotNull(operationProcessorAccessor, nameof(operationProcessorAccessor)); - ArgumentGuard.NotNull(operationsTransactionFactory, nameof(operationsTransactionFactory)); - ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache)); + ArgumentGuard.NotNull(operationProcessorAccessor); + ArgumentGuard.NotNull(operationsTransactionFactory); + ArgumentGuard.NotNull(localIdTracker); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(targetedFields); + ArgumentGuard.NotNull(sparseFieldSetCache); _operationProcessorAccessor = operationProcessorAccessor; _operationsTransactionFactory = operationsTransactionFactory; @@ -46,7 +46,7 @@ public OperationsProcessor(IOperationProcessorAccessor operationProcessorAccesso /// public virtual async Task> ProcessAsync(IList operations, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operations, nameof(operations)); + ArgumentGuard.NotNull(operations); _localIdValidator.Validate(operations); _localIdTracker.Reset(); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs index fc16847eec..c8997be8cd 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/AddToRelationshipProcessor.cs @@ -13,7 +13,7 @@ public class AddToRelationshipProcessor : IAddToRelationshipProc public AddToRelationshipProcessor(IAddToRelationshipService service) { - ArgumentGuard.NotNull(service, nameof(service)); + ArgumentGuard.NotNull(service); _service = service; } @@ -21,7 +21,7 @@ public AddToRelationshipProcessor(IAddToRelationshipService serv /// public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); var leftId = (TId)operation.Resource.GetTypedId(); ISet rightResourceIds = operation.GetSecondaryResources(); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs index b06ebd626e..e105f54a50 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/CreateProcessor.cs @@ -14,8 +14,8 @@ public class CreateProcessor : ICreateProcessor public CreateProcessor(ICreateService service, ILocalIdTracker localIdTracker) { - ArgumentGuard.NotNull(service, nameof(service)); - ArgumentGuard.NotNull(localIdTracker, nameof(localIdTracker)); + ArgumentGuard.NotNull(service); + ArgumentGuard.NotNull(localIdTracker); _service = service; _localIdTracker = localIdTracker; @@ -24,7 +24,7 @@ public CreateProcessor(ICreateService service, ILocalIdTracker l /// public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); TResource? newResource = await _service.CreateAsync((TResource)operation.Resource, cancellationToken); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs index e4001b75c1..356742f9b7 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/DeleteProcessor.cs @@ -13,7 +13,7 @@ public class DeleteProcessor : IDeleteProcessor public DeleteProcessor(IDeleteService service) { - ArgumentGuard.NotNull(service, nameof(service)); + ArgumentGuard.NotNull(service); _service = service; } @@ -21,7 +21,7 @@ public DeleteProcessor(IDeleteService service) /// public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); var id = (TId)operation.Resource.GetTypedId(); await _service.DeleteAsync(id, cancellationToken); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs index 493ed2066f..b308d6935a 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/RemoveFromRelationshipProcessor.cs @@ -13,7 +13,7 @@ public class RemoveFromRelationshipProcessor : IRemoveFromRelati public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService service) { - ArgumentGuard.NotNull(service, nameof(service)); + ArgumentGuard.NotNull(service); _service = service; } @@ -21,7 +21,7 @@ public RemoveFromRelationshipProcessor(IRemoveFromRelationshipService public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); var leftId = (TId)operation.Resource.GetTypedId(); ISet rightResourceIds = operation.GetSecondaryResources(); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs index 5eb09ccbc3..083bd0d0fc 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/SetRelationshipProcessor.cs @@ -15,7 +15,7 @@ public class SetRelationshipProcessor : ISetRelationshipProcesso public SetRelationshipProcessor(ISetRelationshipService service) { - ArgumentGuard.NotNull(service, nameof(service)); + ArgumentGuard.NotNull(service); _service = service; } @@ -23,7 +23,7 @@ public SetRelationshipProcessor(ISetRelationshipService service) /// public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); var leftId = (TId)operation.Resource.GetTypedId(); object? rightValue = GetRelationshipRightValue(operation); diff --git a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs index a02ac2d3ff..5611f8d1c2 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/Processors/UpdateProcessor.cs @@ -13,7 +13,7 @@ public class UpdateProcessor : IUpdateProcessor public UpdateProcessor(IUpdateService service) { - ArgumentGuard.NotNull(service, nameof(service)); + ArgumentGuard.NotNull(service); _service = service; } @@ -21,7 +21,7 @@ public UpdateProcessor(IUpdateService service) /// public virtual async Task ProcessAsync(OperationContainer operation, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); var resource = (TResource)operation.Resource; TResource? updated = await _service.UpdateAsync(resource.Id, resource, cancellationToken); diff --git a/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs b/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs index 453f78f1f2..1951333d0c 100644 --- a/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs +++ b/src/JsonApiDotNetCore/AtomicOperations/RevertRequestStateOnDispose.cs @@ -16,7 +16,7 @@ internal sealed class RevertRequestStateOnDispose : IDisposable public RevertRequestStateOnDispose(IJsonApiRequest request, ITargetedFields? targetedFields) { - ArgumentGuard.NotNull(request, nameof(request)); + ArgumentGuard.NotNull(request); _sourceRequest = request; _backupRequest.CopyFrom(request); diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs index a7f5e72ab6..133231eb23 100644 --- a/src/JsonApiDotNetCore/CollectionExtensions.cs +++ b/src/JsonApiDotNetCore/CollectionExtensions.cs @@ -18,8 +18,8 @@ public static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? so public static int FindIndex(this IReadOnlyList source, Predicate match) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(match, nameof(match)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(match); for (int index = 0; index < source.Count; index++) { @@ -82,8 +82,8 @@ public static IEnumerable WhereNotNull(this IEnumerable source) public static void AddRange(this ICollection source, IEnumerable itemsToAdd) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(itemsToAdd, nameof(itemsToAdd)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(itemsToAdd); foreach (T item in itemsToAdd) { diff --git a/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs index a941e27218..8ed4e42a42 100644 --- a/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ApplicationBuilderExtensions.cs @@ -22,7 +22,7 @@ public static class ApplicationBuilderExtensions /// public static void UseJsonApi(this IApplicationBuilder builder) { - ArgumentGuard.NotNull(builder, nameof(builder)); + ArgumentGuard.NotNull(builder); using (IServiceScope scope = builder.ApplicationServices.CreateScope()) { diff --git a/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs b/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs index 8e5f3f15a3..821be639f9 100644 --- a/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs +++ b/src/JsonApiDotNetCore/Configuration/InverseNavigationResolver.cs @@ -15,8 +15,8 @@ public sealed class InverseNavigationResolver : IInverseNavigationResolver public InverseNavigationResolver(IResourceGraph resourceGraph, IEnumerable dbContextResolvers) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(dbContextResolvers, nameof(dbContextResolvers)); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(dbContextResolvers); _resourceGraph = resourceGraph; _dbContextResolvers = dbContextResolvers; diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 7cd4307ad1..82e0ff52e1 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -39,8 +39,8 @@ internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, ID public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder) { - ArgumentGuard.NotNull(services, nameof(services)); - ArgumentGuard.NotNull(mvcBuilder, nameof(mvcBuilder)); + ArgumentGuard.NotNull(services); + ArgumentGuard.NotNull(mvcBuilder); _services = services; _mvcBuilder = mvcBuilder; @@ -73,7 +73,7 @@ public void ConfigureAutoDiscovery(Action? configureAuto /// public void ConfigureResourceGraph(ICollection dbContextTypes, Action? configureResourceGraph) { - ArgumentGuard.NotNull(dbContextTypes, nameof(dbContextTypes)); + ArgumentGuard.NotNull(dbContextTypes); _serviceDiscoveryFacade.DiscoverResources(); @@ -126,7 +126,7 @@ public void DiscoverInjectables() /// public void ConfigureServiceContainer(ICollection dbContextTypes) { - ArgumentGuard.NotNull(dbContextTypes, nameof(dbContextTypes)); + ArgumentGuard.NotNull(dbContextTypes); if (dbContextTypes.Any()) { diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs index 6b193bdc6f..a27acf8ebd 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs @@ -15,7 +15,7 @@ internal sealed class JsonApiValidationFilter : IPropertyValidationFilter public JsonApiValidationFilter(IHttpContextAccessor httpContextAccessor) { - ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); + ArgumentGuard.NotNull(httpContextAccessor); _httpContextAccessor = httpContextAccessor; } diff --git a/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs b/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs index 8747cdd18f..885f67567c 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceDescriptor.cs @@ -7,8 +7,8 @@ internal sealed class ResourceDescriptor public ResourceDescriptor(Type resourceClrType, Type idClrType) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); - ArgumentGuard.NotNull(idClrType, nameof(idClrType)); + ArgumentGuard.NotNull(resourceClrType); + ArgumentGuard.NotNull(idClrType); ResourceClrType = resourceClrType; IdClrType = idClrType; diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs index d693fa2c3c..0dbaeb9623 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraph.cs @@ -18,7 +18,7 @@ public sealed class ResourceGraph : IResourceGraph public ResourceGraph(IReadOnlySet resourceTypeSet) { - ArgumentGuard.NotNull(resourceTypeSet, nameof(resourceTypeSet)); + ArgumentGuard.NotNull(resourceTypeSet); _resourceTypeSet = resourceTypeSet; @@ -51,7 +51,7 @@ public ResourceType GetResourceType(string publicName) /// public ResourceType? FindResourceType(string publicName) { - ArgumentGuard.NotNull(publicName, nameof(publicName)); + ArgumentGuard.NotNull(publicName); return _resourceTypesByPublicName.TryGetValue(publicName, out ResourceType? resourceType) ? resourceType : null; } @@ -72,7 +72,7 @@ public ResourceType GetResourceType(Type resourceClrType) /// public ResourceType? FindResourceType(Type resourceClrType) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); + ArgumentGuard.NotNull(resourceClrType); Type typeToFind = IsLazyLoadingProxyForResourceType(resourceClrType) ? resourceClrType.BaseType! : resourceClrType; return _resourceTypesByClrType.TryGetValue(typeToFind, out ResourceType? resourceType) ? resourceType : null; @@ -94,7 +94,7 @@ public ResourceType GetResourceType() public IReadOnlyCollection GetFields(Expression> selector) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(selector, nameof(selector)); + ArgumentGuard.NotNull(selector); return FilterFields(selector); } @@ -103,7 +103,7 @@ public IReadOnlyCollection GetFields(Expressi public IReadOnlyCollection GetAttributes(Expression> selector) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(selector, nameof(selector)); + ArgumentGuard.NotNull(selector); return FilterFields(selector); } @@ -112,7 +112,7 @@ public IReadOnlyCollection GetAttributes(Expression GetRelationships(Expression> selector) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(selector, nameof(selector)); + ArgumentGuard.NotNull(selector); return FilterFields(selector); } diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index 36eb9d37e8..2af0e63caf 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -22,8 +22,8 @@ public class ResourceGraphBuilder public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactory) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(loggerFactory); _options = options; _logger = loggerFactory.CreateLogger(); @@ -144,7 +144,7 @@ private static void ValidateRelationshipsInDerivedType(ResourceType resourceType public ResourceGraphBuilder Add(DbContext dbContext) { - ArgumentGuard.NotNull(dbContext, nameof(dbContext)); + ArgumentGuard.NotNull(dbContext); foreach (IEntityType entityType in dbContext.Model.GetEntityTypes()) { @@ -200,7 +200,7 @@ public ResourceGraphBuilder Add(string? publicName = null) public ResourceGraphBuilder Add(Type resourceClrType, Type? idClrType = null, string? publicName = null) #pragma warning restore AV1553 // Do not use optional parameters with default value null for strings, collections or tasks { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); + ArgumentGuard.NotNull(resourceClrType); if (_resourceTypesByClrType.ContainsKey(resourceClrType)) { diff --git a/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs b/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs index 82a54ff010..a6e12951a9 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceNameFormatter.cs @@ -19,7 +19,7 @@ public ResourceNameFormatter(JsonNamingPolicy? namingPolicy) /// public string FormatResourceName(Type resourceClrType) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); + ArgumentGuard.NotNull(resourceClrType); var resourceAttribute = resourceClrType.GetCustomAttribute(true); diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs index 8b5356fdef..7ea42a2470 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs @@ -22,7 +22,7 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services, Ac ICollection? dbContextTypes = null) #pragma warning restore AV1553 // Do not use optional parameters with default value null for strings, collections or tasks { - ArgumentGuard.NotNull(services, nameof(services)); + ArgumentGuard.NotNull(services); SetupApplicationBuilder(services, options, discovery, resources, mvcBuilder, dbContextTypes ?? Array.Empty()); @@ -59,7 +59,7 @@ private static void SetupApplicationBuilder(IServiceCollection services, Action< /// public static IServiceCollection AddResourceService(this IServiceCollection services) { - ArgumentGuard.NotNull(services, nameof(services)); + ArgumentGuard.NotNull(services); RegisterTypeForUnboundInterfaces(services, typeof(TService), ServiceDiscoveryFacade.ServiceUnboundInterfaces); @@ -72,7 +72,7 @@ public static IServiceCollection AddResourceService(this IServiceColle /// public static IServiceCollection AddResourceRepository(this IServiceCollection services) { - ArgumentGuard.NotNull(services, nameof(services)); + ArgumentGuard.NotNull(services); RegisterTypeForUnboundInterfaces(services, typeof(TRepository), ServiceDiscoveryFacade.RepositoryUnboundInterfaces); @@ -85,7 +85,7 @@ public static IServiceCollection AddResourceRepository(this IServic /// public static IServiceCollection AddResourceDefinition(this IServiceCollection services) { - ArgumentGuard.NotNull(services, nameof(services)); + ArgumentGuard.NotNull(services); RegisterTypeForUnboundInterfaces(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces); diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs index 7498391afd..85f95c232f 100644 --- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs @@ -52,9 +52,9 @@ public sealed class ServiceDiscoveryFacade public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, ILoggerFactory loggerFactory) { - ArgumentGuard.NotNull(services, nameof(services)); - ArgumentGuard.NotNull(resourceGraphBuilder, nameof(resourceGraphBuilder)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); + ArgumentGuard.NotNull(services); + ArgumentGuard.NotNull(resourceGraphBuilder); + ArgumentGuard.NotNull(loggerFactory); _logger = loggerFactory.CreateLogger(); _services = services; @@ -74,7 +74,7 @@ public ServiceDiscoveryFacade AddCurrentAssembly() /// public ServiceDiscoveryFacade AddAssembly(Assembly assembly) { - ArgumentGuard.NotNull(assembly, nameof(assembly)); + ArgumentGuard.NotNull(assembly); _assemblyCache.RegisterAssembly(assembly); _logger.LogDebug($"Registering assembly '{assembly.FullName}' for discovery of resources and injectables."); diff --git a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs index 2f004ffdf1..e4b1da8d01 100644 --- a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs @@ -66,9 +66,9 @@ internal sealed class TypeLocator public (Type implementationType, Type serviceInterface)? GetContainerRegistrationFromAssembly(Assembly assembly, Type unboundInterface, params Type[] interfaceTypeArguments) { - ArgumentGuard.NotNull(assembly, nameof(assembly)); - ArgumentGuard.NotNull(unboundInterface, nameof(unboundInterface)); - ArgumentGuard.NotNull(interfaceTypeArguments, nameof(interfaceTypeArguments)); + ArgumentGuard.NotNull(assembly); + ArgumentGuard.NotNull(unboundInterface); + ArgumentGuard.NotNull(interfaceTypeArguments); if (!unboundInterface.IsInterface || !unboundInterface.IsGenericType || unboundInterface != unboundInterface.GetGenericTypeDefinition()) { @@ -129,9 +129,9 @@ private static (Type implementationType, Type serviceInterface)? GetContainerReg /// public IReadOnlyCollection GetDerivedTypesForUnboundType(Assembly assembly, Type unboundType, params Type[] typeArguments) { - ArgumentGuard.NotNull(assembly, nameof(assembly)); - ArgumentGuard.NotNull(unboundType, nameof(unboundType)); - ArgumentGuard.NotNull(typeArguments, nameof(typeArguments)); + ArgumentGuard.NotNull(assembly); + ArgumentGuard.NotNull(unboundType); + ArgumentGuard.NotNull(typeArguments); Type closedType = unboundType.MakeGenericType(typeArguments); return GetDerivedTypes(assembly, closedType).ToArray(); @@ -153,8 +153,8 @@ public IReadOnlyCollection GetDerivedTypesForUnboundType(Assembly assembly /// public IEnumerable GetDerivedTypes(Assembly assembly, Type baseType) { - ArgumentGuard.NotNull(assembly, nameof(assembly)); - ArgumentGuard.NotNull(baseType, nameof(baseType)); + ArgumentGuard.NotNull(assembly); + ArgumentGuard.NotNull(baseType); foreach (Type type in assembly.GetTypes()) { diff --git a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs index 975472ab28..eba4b8340c 100644 --- a/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs +++ b/src/JsonApiDotNetCore/Controllers/Annotations/DisableQueryStringAttribute.cs @@ -46,7 +46,7 @@ public DisableQueryStringAttribute(JsonApiQueryStringParameters parameters) /// public DisableQueryStringAttribute(string parameterNames) { - ArgumentGuard.NotNullNorEmpty(parameterNames, nameof(parameterNames)); + ArgumentGuard.NotNullNorEmpty(parameterNames); ParameterNames = parameterNames.Split(",").ToHashSet(); } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index cabe4d49d8..22efab2840 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -63,9 +63,9 @@ protected BaseJsonApiController(IJsonApiOptions options, IResourceGraph resource IUpdateService? update = null, ISetRelationshipService? setRelationship = null, IDeleteService? delete = null, IRemoveFromRelationshipService? removeFromRelationship = null) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(loggerFactory); _options = options; _resourceGraph = resourceGraph; @@ -139,7 +139,7 @@ public virtual async Task GetSecondaryAsync(TId id, string relati relationshipName }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName); if (_getSecondary == null) { @@ -168,7 +168,7 @@ public virtual async Task GetRelationshipAsync(TId id, string rel relationshipName }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName); if (_getRelationship == null) { @@ -192,7 +192,7 @@ public virtual async Task PostAsync([FromBody] TResource resource resource }); - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); if (_create == null) { @@ -245,8 +245,8 @@ public virtual async Task PostRelationshipAsync(TId id, string re rightResourceIds }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNullNorEmpty(relationshipName); + ArgumentGuard.NotNull(rightResourceIds); if (_addToRelationship == null) { @@ -272,7 +272,7 @@ public virtual async Task PatchAsync(TId id, [FromBody] TResource resource }); - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); if (_update == null) { @@ -320,7 +320,7 @@ public virtual async Task PatchRelationshipAsync(TId id, string r rightValue }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName); if (_setRelationship == null) { @@ -381,8 +381,8 @@ public virtual async Task DeleteRelationshipAsync(TId id, string rightResourceIds }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNullNorEmpty(relationshipName); + ArgumentGuard.NotNull(rightResourceIds); if (_removeFromRelationship == null) { diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs index a948d67edc..2ea8f89a87 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiOperationsController.cs @@ -27,12 +27,12 @@ public abstract class BaseJsonApiOperationsController : CoreJsonApiController protected BaseJsonApiOperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, ITargetedFields targetedFields) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); - ArgumentGuard.NotNull(processor, nameof(processor)); - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(loggerFactory); + ArgumentGuard.NotNull(processor); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(targetedFields); _options = options; _resourceGraph = resourceGraph; @@ -109,7 +109,7 @@ public virtual async Task PostOperationsAsync([FromBody] IList errors) { IReadOnlyList? errorList = ToErrorList(errors); - ArgumentGuard.NotNullNorEmpty(errorList, nameof(errors)); + ArgumentGuard.NotNullNorEmpty(errorList); return new ObjectResult(errorList) { diff --git a/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs b/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs index bef29dba78..a42d734580 100644 --- a/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs +++ b/src/JsonApiDotNetCore/Diagnostics/AspNetCodeTimerSession.cs @@ -37,14 +37,14 @@ public ICodeTimer CodeTimer public AspNetCodeTimerSession(IHttpContextAccessor httpContextAccessor) { - ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); + ArgumentGuard.NotNull(httpContextAccessor); _httpContextAccessor = httpContextAccessor; } public AspNetCodeTimerSession(HttpContext httpContext) { - ArgumentGuard.NotNull(httpContext, nameof(httpContext)); + ArgumentGuard.NotNull(httpContext); _httpContext = httpContext; } diff --git a/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs b/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs index d858aa6f4b..5a862409bc 100644 --- a/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs +++ b/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs @@ -62,7 +62,7 @@ private static void AssertHasActiveSession() public static void Capture(ICodeTimerSession session) { - ArgumentGuard.NotNull(session, nameof(session)); + ArgumentGuard.NotNull(session); AssertNoActiveSession(); diff --git a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs index eede4fed25..be4de87fc8 100644 --- a/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs +++ b/src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs @@ -26,9 +26,9 @@ public InvalidModelStateException(IReadOnlyDictionary private static IEnumerable FromModelStateDictionary(IReadOnlyDictionary modelState, Type modelType, IResourceGraph resourceGraph, bool includeExceptionStackTraceInErrors, Func? getCollectionElementTypeCallback) { - ArgumentGuard.NotNull(modelState, nameof(modelState)); - ArgumentGuard.NotNull(modelType, nameof(modelType)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(modelState); + ArgumentGuard.NotNull(modelType); + ArgumentGuard.NotNull(resourceGraph); List errorObjects = new(); @@ -229,8 +229,8 @@ private abstract class ModelStateKeySegment protected ModelStateKeySegment(Type modelType, bool isInComplexType, string nextKey, string? sourcePointer, ModelStateKeySegment? parent, Func? getCollectionElementTypeCallback) { - ArgumentGuard.NotNull(modelType, nameof(modelType)); - ArgumentGuard.NotNull(nextKey, nameof(nextKey)); + ArgumentGuard.NotNull(modelType); + ArgumentGuard.NotNull(nextKey); ModelType = modelType; IsInComplexType = isInComplexType; @@ -242,15 +242,15 @@ protected ModelStateKeySegment(Type modelType, bool isInComplexType, string next public ModelStateKeySegment? GetNextSegment(Type modelType, bool isInComplexType, string? sourcePointer) { - ArgumentGuard.NotNull(modelType, nameof(modelType)); + ArgumentGuard.NotNull(modelType); return _nextKey == string.Empty ? null : CreateSegment(modelType, _nextKey, isInComplexType, this, sourcePointer, GetCollectionElementTypeCallback); } public static ModelStateKeySegment Create(Type modelType, string key, Func? getCollectionElementTypeCallback) { - ArgumentGuard.NotNull(modelType, nameof(modelType)); - ArgumentGuard.NotNull(key, nameof(key)); + ArgumentGuard.NotNull(modelType); + ArgumentGuard.NotNull(key); return CreateSegment(modelType, key, false, null, null, getCollectionElementTypeCallback); } @@ -359,14 +359,14 @@ public PropertySegment(string propertyName, Type modelType, bool isInComplexType Func? getCollectionElementTypeCallback) : base(modelType, isInComplexType, nextKey, sourcePointer, parent, getCollectionElementTypeCallback) { - ArgumentGuard.NotNull(propertyName, nameof(propertyName)); + ArgumentGuard.NotNull(propertyName); PropertyName = propertyName; } public static string GetPublicNameForProperty(PropertyInfo property) { - ArgumentGuard.NotNull(property, nameof(property)); + ArgumentGuard.NotNull(property); var jsonNameAttribute = property.GetCustomAttribute(true); return jsonNameAttribute?.Name ?? property.Name; diff --git a/src/JsonApiDotNetCore/Errors/JsonApiException.cs b/src/JsonApiDotNetCore/Errors/JsonApiException.cs index 6bb62177dc..4571843e8d 100644 --- a/src/JsonApiDotNetCore/Errors/JsonApiException.cs +++ b/src/JsonApiDotNetCore/Errors/JsonApiException.cs @@ -24,7 +24,7 @@ public class JsonApiException : Exception public JsonApiException(ErrorObject error, Exception? innerException = null) : base(null, innerException) { - ArgumentGuard.NotNull(error, nameof(error)); + ArgumentGuard.NotNull(error); Errors = error.AsArray(); } @@ -33,7 +33,7 @@ public JsonApiException(IEnumerable errors, Exception? innerExcepti : base(null, innerException) { IReadOnlyList? errorList = ToErrorList(errors); - ArgumentGuard.NotNullNorEmpty(errorList, nameof(errors)); + ArgumentGuard.NotNullNorEmpty(errorList); Errors = errorList; } diff --git a/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs b/src/JsonApiDotNetCore/Errors/MissingResourceInRelationship.cs index e80181898d..42082d6126 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.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNullNorEmpty(resourceType, nameof(resourceType)); - ArgumentGuard.NotNullNorEmpty(resourceId, nameof(resourceId)); + ArgumentGuard.NotNullNorEmpty(relationshipName); + ArgumentGuard.NotNullNorEmpty(resourceType); + ArgumentGuard.NotNullNorEmpty(resourceId); RelationshipName = relationshipName; ResourceType = resourceType; diff --git a/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs b/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs index c5c55e4b70..e739ec9cbf 100644 --- a/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs +++ b/src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs @@ -26,7 +26,7 @@ public UnsuccessfulActionResultException(ProblemDetails problemDetails) private static ErrorObject ToError(ProblemDetails problemDetails) { - ArgumentGuard.NotNull(problemDetails, nameof(problemDetails)); + ArgumentGuard.NotNull(problemDetails); HttpStatusCode status = problemDetails.Status != null ? (HttpStatusCode)problemDetails.Status.Value : HttpStatusCode.InternalServerError; diff --git a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs index cf51a82733..82e443a9af 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncConvertEmptyActionResultFilter.cs @@ -10,8 +10,8 @@ public sealed class AsyncConvertEmptyActionResultFilter : IAsyncConvertEmptyActi /// public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { - ArgumentGuard.NotNull(context, nameof(context)); - ArgumentGuard.NotNull(next, nameof(next)); + ArgumentGuard.NotNull(context); + ArgumentGuard.NotNull(next); if (context.HttpContext.IsJsonApiRequest()) { diff --git a/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs index e87fc98389..58d568a3ec 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncJsonApiExceptionFilter.cs @@ -13,7 +13,7 @@ public sealed class AsyncJsonApiExceptionFilter : IAsyncJsonApiExceptionFilter public AsyncJsonApiExceptionFilter(IExceptionHandler exceptionHandler) { - ArgumentGuard.NotNull(exceptionHandler, nameof(exceptionHandler)); + ArgumentGuard.NotNull(exceptionHandler); _exceptionHandler = exceptionHandler; } @@ -21,7 +21,7 @@ public AsyncJsonApiExceptionFilter(IExceptionHandler exceptionHandler) /// public Task OnExceptionAsync(ExceptionContext context) { - ArgumentGuard.NotNull(context, nameof(context)); + ArgumentGuard.NotNull(context); if (context.HttpContext.IsJsonApiRequest()) { diff --git a/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs b/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs index 6f31c28d2a..89164c844f 100644 --- a/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/AsyncQueryStringActionFilter.cs @@ -12,7 +12,7 @@ public sealed class AsyncQueryStringActionFilter : IAsyncQueryStringActionFilter public AsyncQueryStringActionFilter(IQueryStringReader queryStringReader) { - ArgumentGuard.NotNull(queryStringReader, nameof(queryStringReader)); + ArgumentGuard.NotNull(queryStringReader); _queryStringReader = queryStringReader; } @@ -20,8 +20,8 @@ public AsyncQueryStringActionFilter(IQueryStringReader queryStringReader) /// public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - ArgumentGuard.NotNull(context, nameof(context)); - ArgumentGuard.NotNull(next, nameof(next)); + ArgumentGuard.NotNull(context); + ArgumentGuard.NotNull(next); if (context.HttpContext.IsJsonApiRequest()) { diff --git a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs index ea1d67743d..b8690402a5 100644 --- a/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs +++ b/src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs @@ -17,8 +17,8 @@ public class ExceptionHandler : IExceptionHandler public ExceptionHandler(ILoggerFactory loggerFactory, IJsonApiOptions options) { - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(loggerFactory); + ArgumentGuard.NotNull(options); _options = options; _logger = loggerFactory.CreateLogger(); @@ -26,7 +26,7 @@ public ExceptionHandler(ILoggerFactory loggerFactory, IJsonApiOptions options) public IReadOnlyList HandleException(Exception exception) { - ArgumentGuard.NotNull(exception, nameof(exception)); + ArgumentGuard.NotNull(exception); Exception demystified = exception.Demystify(); @@ -45,7 +45,7 @@ private void LogException(Exception exception) protected virtual LogLevel GetLogLevel(Exception exception) { - ArgumentGuard.NotNull(exception, nameof(exception)); + ArgumentGuard.NotNull(exception); if (exception is OperationCanceledException) { @@ -62,14 +62,14 @@ protected virtual LogLevel GetLogLevel(Exception exception) protected virtual string GetLogMessage(Exception exception) { - ArgumentGuard.NotNull(exception, nameof(exception)); + ArgumentGuard.NotNull(exception); return exception is JsonApiException jsonApiException ? jsonApiException.GetSummary() : exception.Message; } protected virtual IReadOnlyList CreateErrorResponse(Exception exception) { - ArgumentGuard.NotNull(exception, nameof(exception)); + ArgumentGuard.NotNull(exception); IReadOnlyList errors = exception is JsonApiException jsonApiException ? jsonApiException.Errors : exception is OperationCanceledException ? new ErrorObject((HttpStatusCode)499) diff --git a/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs b/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs index b6785e8198..a675aeeaff 100644 --- a/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs +++ b/src/JsonApiDotNetCore/Middleware/HttpContextExtensions.cs @@ -13,7 +13,7 @@ public static class HttpContextExtensions /// public static bool IsJsonApiRequest(this HttpContext httpContext) { - ArgumentGuard.NotNull(httpContext, nameof(httpContext)); + ArgumentGuard.NotNull(httpContext); string? value = httpContext.Items[IsJsonApiRequestKey] as string; return value == bool.TrueString; @@ -21,7 +21,7 @@ public static bool IsJsonApiRequest(this HttpContext httpContext) internal static void RegisterJsonApiRequest(this HttpContext httpContext) { - ArgumentGuard.NotNull(httpContext, nameof(httpContext)); + ArgumentGuard.NotNull(httpContext); httpContext.Items[IsJsonApiRequestKey] = bool.TrueString; } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs index 077f0573f0..59563c9268 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs @@ -10,7 +10,7 @@ public sealed class JsonApiInputFormatter : IJsonApiInputFormatter /// public bool CanRead(InputFormatterContext context) { - ArgumentGuard.NotNull(context, nameof(context)); + ArgumentGuard.NotNull(context); return context.HttpContext.IsJsonApiRequest(); } @@ -18,7 +18,7 @@ public bool CanRead(InputFormatterContext context) /// public async Task ReadAsync(InputFormatterContext context) { - ArgumentGuard.NotNull(context, nameof(context)); + ArgumentGuard.NotNull(context); var reader = context.HttpContext.RequestServices.GetRequiredService(); diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 2e15e6ae9a..b38ad986dd 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -36,11 +36,11 @@ public JsonApiMiddleware(RequestDelegate next, IHttpContextAccessor httpContextA public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMapping controllerResourceMapping, IJsonApiOptions options, IJsonApiRequest request, ILogger logger) { - ArgumentGuard.NotNull(httpContext, nameof(httpContext)); - ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(logger, nameof(logger)); + ArgumentGuard.NotNull(httpContext); + ArgumentGuard.NotNull(controllerResourceMapping); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(logger); using (CodeTimingSessionManager.Current.Measure("JSON:API middleware")) { diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs index c32bb9d9f9..8c97a12ea4 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs @@ -10,7 +10,7 @@ public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter /// public bool CanWriteResult(OutputFormatterCanWriteContext context) { - ArgumentGuard.NotNull(context, nameof(context)); + ArgumentGuard.NotNull(context); return context.HttpContext.IsJsonApiRequest(); } @@ -18,7 +18,7 @@ public bool CanWriteResult(OutputFormatterCanWriteContext context) /// public async Task WriteAsync(OutputFormatterWriteContext context) { - ArgumentGuard.NotNull(context, nameof(context)); + ArgumentGuard.NotNull(context); var writer = context.HttpContext.RequestServices.GetRequiredService(); await writer.WriteAsync(context.Object, context.HttpContext); diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs index a28c01fcd6..98e42823a3 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRequest.cs @@ -38,7 +38,7 @@ public sealed class JsonApiRequest : IJsonApiRequest /// public void CopyFrom(IJsonApiRequest other) { - ArgumentGuard.NotNull(other, nameof(other)); + ArgumentGuard.NotNull(other); Kind = other.Kind; PrimaryId = other.PrimaryId; diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index fe95d93446..a05aa979ef 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -36,8 +36,8 @@ public sealed class JsonApiRoutingConvention : IJsonApiRoutingConvention public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(resourceGraph); _options = options; _resourceGraph = resourceGraph; @@ -60,7 +60,7 @@ public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resource /// public void Apply(ApplicationModel application) { - ArgumentGuard.NotNull(application, nameof(application)); + ArgumentGuard.NotNull(application); foreach (ControllerModel controller in application.Controllers) { diff --git a/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs b/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs index 37d40f127b..6ac6f75059 100644 --- a/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs +++ b/src/JsonApiDotNetCore/Queries/ExpressionInScope.cs @@ -15,7 +15,7 @@ public class ExpressionInScope public ExpressionInScope(ResourceFieldChainExpression? scope, QueryExpression expression) { - ArgumentGuard.NotNull(expression, nameof(expression)); + ArgumentGuard.NotNull(expression); Scope = scope; Expression = expression; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs index 980a7846bc..2b855b1bdb 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs @@ -16,8 +16,8 @@ public class AnyExpression : FilterExpression public AnyExpression(ResourceFieldChainExpression targetAttribute, IImmutableSet constants) { - ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); - ArgumentGuard.NotNull(constants, nameof(constants)); + ArgumentGuard.NotNull(targetAttribute); + ArgumentGuard.NotNull(constants); if (constants.Count < 2) { diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs index 9bf1c3bde8..cdae713f3d 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ComparisonExpression.cs @@ -15,8 +15,8 @@ public class ComparisonExpression : FilterExpression public ComparisonExpression(ComparisonOperator @operator, QueryExpression left, QueryExpression right) { - ArgumentGuard.NotNull(left, nameof(left)); - ArgumentGuard.NotNull(right, nameof(right)); + ArgumentGuard.NotNull(left); + ArgumentGuard.NotNull(right); Operator = @operator; Left = left; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs index 5de89ead7c..2eff0a86e9 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/CountExpression.cs @@ -13,7 +13,7 @@ public class CountExpression : FunctionExpression public CountExpression(ResourceFieldChainExpression targetCollection) { - ArgumentGuard.NotNull(targetCollection, nameof(targetCollection)); + ArgumentGuard.NotNull(targetCollection); TargetCollection = targetCollection; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs index c5387106d6..825119fe33 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/HasExpression.cs @@ -15,7 +15,7 @@ public class HasExpression : FilterExpression public HasExpression(ResourceFieldChainExpression targetCollection, FilterExpression? filter) { - ArgumentGuard.NotNull(targetCollection, nameof(targetCollection)); + ArgumentGuard.NotNull(targetCollection); TargetCollection = targetCollection; Filter = filter; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs index b35c48efbd..8b2034a374 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeChainConverter.cs @@ -29,7 +29,7 @@ internal sealed class IncludeChainConverter /// public IReadOnlyCollection GetRelationshipChains(IncludeExpression include) { - ArgumentGuard.NotNull(include, nameof(include)); + ArgumentGuard.NotNull(include); if (!include.Elements.Any()) { diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs index e76aaf0946..01c25dad4e 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs @@ -21,8 +21,8 @@ public IncludeElementExpression(RelationshipAttribute relationship) public IncludeElementExpression(RelationshipAttribute relationship, IImmutableSet children) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); - ArgumentGuard.NotNull(children, nameof(children)); + ArgumentGuard.NotNull(relationship); + ArgumentGuard.NotNull(children); Relationship = relationship; Children = children; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs index a63d87719d..69373c9abf 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IncludeExpression.cs @@ -17,7 +17,7 @@ public class IncludeExpression : QueryExpression public IncludeExpression(IImmutableSet elements) { - ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); + ArgumentGuard.NotNullNorEmpty(elements); Elements = elements; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs index a30e31308b..4e259b358e 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/IsTypeExpression.cs @@ -18,7 +18,7 @@ public class IsTypeExpression : FilterExpression public IsTypeExpression(ResourceFieldChainExpression? targetToOneRelationship, ResourceType derivedType, FilterExpression? child) { - ArgumentGuard.NotNull(derivedType, nameof(derivedType)); + ArgumentGuard.NotNull(derivedType); TargetToOneRelationship = targetToOneRelationship; DerivedType = derivedType; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs index 17c62f230f..578643d5db 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/LiteralConstantExpression.cs @@ -12,7 +12,7 @@ public class LiteralConstantExpression : IdentifierExpression public LiteralConstantExpression(string text) { - ArgumentGuard.NotNull(text, nameof(text)); + ArgumentGuard.NotNull(text); Value = text; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs index c8d8ffb24b..08f970aee5 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/LogicalExpression.cs @@ -21,7 +21,7 @@ public LogicalExpression(LogicalOperator @operator, params FilterExpression[] te public LogicalExpression(LogicalOperator @operator, IImmutableList terms) { - ArgumentGuard.NotNull(terms, nameof(terms)); + ArgumentGuard.NotNull(terms); if (terms.Count < 2) { @@ -34,7 +34,7 @@ public LogicalExpression(LogicalOperator @operator, IImmutableList terms = filters.WhereNotNull().ToImmutableArray(); diff --git a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs index a9c598402b..5d9ed08859 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/MatchTextExpression.cs @@ -16,8 +16,8 @@ public class MatchTextExpression : FilterExpression public MatchTextExpression(ResourceFieldChainExpression targetAttribute, LiteralConstantExpression textValue, TextMatchKind matchKind) { - ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); - ArgumentGuard.NotNull(textValue, nameof(textValue)); + ArgumentGuard.NotNull(targetAttribute); + ArgumentGuard.NotNull(textValue); TargetAttribute = targetAttribute; TextValue = textValue; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs index 4d28c4a9c3..ae198cd3ee 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/NotExpression.cs @@ -13,7 +13,7 @@ public class NotExpression : FilterExpression public NotExpression(FilterExpression child) { - ArgumentGuard.NotNull(child, nameof(child)); + ArgumentGuard.NotNull(child); Child = child; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs index 97ff8b1456..2ecd9901a2 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationExpression.cs @@ -14,7 +14,7 @@ public class PaginationExpression : QueryExpression public PaginationExpression(PageNumber pageNumber, PageSize? pageSize) { - ArgumentGuard.NotNull(pageNumber, nameof(pageNumber)); + ArgumentGuard.NotNull(pageNumber); PageNumber = pageNumber; PageSize = pageSize; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs index 594dab297a..a65e9c0a15 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/PaginationQueryStringValueExpression.cs @@ -13,7 +13,7 @@ public class PaginationQueryStringValueExpression : QueryExpression public PaginationQueryStringValueExpression(IImmutableList elements) { - ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); + ArgumentGuard.NotNullNorEmpty(elements); Elements = elements; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs index e567da8778..bc2d018033 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryStringParameterScopeExpression.cs @@ -13,7 +13,7 @@ public class QueryStringParameterScopeExpression : QueryExpression public QueryStringParameterScopeExpression(LiteralConstantExpression parameterName, ResourceFieldChainExpression? scope) { - ArgumentGuard.NotNull(parameterName, nameof(parameterName)); + ArgumentGuard.NotNull(parameterName); ParameterName = parameterName; Scope = scope; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs index 1d9c910955..872cdb1aac 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/QueryableHandlerExpression.cs @@ -15,7 +15,7 @@ public class QueryableHandlerExpression : QueryExpression public QueryableHandlerExpression(object queryableHandler, StringValues parameterValue) { - ArgumentGuard.NotNull(queryableHandler, nameof(queryableHandler)); + ArgumentGuard.NotNull(queryableHandler); _queryableHandler = queryableHandler; _parameterValue = parameterValue; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs index 7decec6221..9224642133 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/ResourceFieldChainExpression.cs @@ -14,14 +14,14 @@ public class ResourceFieldChainExpression : IdentifierExpression public ResourceFieldChainExpression(ResourceFieldAttribute field) { - ArgumentGuard.NotNull(field, nameof(field)); + ArgumentGuard.NotNull(field); Fields = ImmutableArray.Create(field); } public ResourceFieldChainExpression(IImmutableList fields) { - ArgumentGuard.NotNullNorEmpty(fields, nameof(fields)); + ArgumentGuard.NotNullNorEmpty(fields); Fields = fields; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs index 78de440a42..bfdf30e8d5 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortElementExpression.cs @@ -15,7 +15,7 @@ public class SortElementExpression : QueryExpression public SortElementExpression(ResourceFieldChainExpression targetAttribute, bool isAscending) { - ArgumentGuard.NotNull(targetAttribute, nameof(targetAttribute)); + ArgumentGuard.NotNull(targetAttribute); TargetAttribute = targetAttribute; IsAscending = isAscending; @@ -23,7 +23,7 @@ public SortElementExpression(ResourceFieldChainExpression targetAttribute, bool public SortElementExpression(CountExpression count, bool isAscending) { - ArgumentGuard.NotNull(count, nameof(count)); + ArgumentGuard.NotNull(count); Count = count; IsAscending = isAscending; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs index dc0aebd320..53b067d4e8 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SortExpression.cs @@ -13,7 +13,7 @@ public class SortExpression : QueryExpression public SortExpression(IImmutableList elements) { - ArgumentGuard.NotNullNorEmpty(elements, nameof(elements)); + ArgumentGuard.NotNullNorEmpty(elements); Elements = elements; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs index bc1e611bd8..f36427b2e1 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs @@ -14,7 +14,7 @@ public class SparseFieldSetExpression : QueryExpression public SparseFieldSetExpression(IImmutableSet fields) { - ArgumentGuard.NotNullNorEmpty(fields, nameof(fields)); + ArgumentGuard.NotNullNorEmpty(fields); Fields = fields; } diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs index 53f9ff0eb6..c7c331eb46 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs @@ -14,8 +14,8 @@ public static class SparseFieldSetExpressionExtensions Expression> fieldSelector, IResourceGraph resourceGraph) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(fieldSelector, nameof(fieldSelector)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(fieldSelector); + ArgumentGuard.NotNull(resourceGraph); SparseFieldSetExpression? newSparseFieldSet = sparseFieldSet; @@ -42,8 +42,8 @@ public static class SparseFieldSetExpressionExtensions Expression> fieldSelector, IResourceGraph resourceGraph) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(fieldSelector, nameof(fieldSelector)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(fieldSelector); + ArgumentGuard.NotNull(resourceGraph); SparseFieldSetExpression? newSparseFieldSet = sparseFieldSet; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs index 8e52df9b3b..c69be71292 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/SparseFieldTableExpression.cs @@ -15,7 +15,7 @@ public class SparseFieldTableExpression : QueryExpression public SparseFieldTableExpression(IImmutableDictionary table) { - ArgumentGuard.NotNullNorEmpty(table, nameof(table), "entries"); + ArgumentGuard.NotNullNorEmpty(table); Table = table; } diff --git a/src/JsonApiDotNetCore/Queries/FieldSelection.cs b/src/JsonApiDotNetCore/Queries/FieldSelection.cs index 54c59005bf..7f62db1fcf 100644 --- a/src/JsonApiDotNetCore/Queries/FieldSelection.cs +++ b/src/JsonApiDotNetCore/Queries/FieldSelection.cs @@ -23,7 +23,7 @@ public IReadOnlySet GetResourceTypes() public FieldSelectors GetOrCreateSelectors(ResourceType resourceType) #pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); if (!ContainsKey(resourceType)) { diff --git a/src/JsonApiDotNetCore/Queries/FieldSelectors.cs b/src/JsonApiDotNetCore/Queries/FieldSelectors.cs index a07b4f0c79..ffd95c01bc 100644 --- a/src/JsonApiDotNetCore/Queries/FieldSelectors.cs +++ b/src/JsonApiDotNetCore/Queries/FieldSelectors.cs @@ -30,21 +30,21 @@ public bool ContainsOnlyRelationships public bool ContainsField(ResourceFieldAttribute field) { - ArgumentGuard.NotNull(field, nameof(field)); + ArgumentGuard.NotNull(field); return ContainsKey(field); } public void IncludeAttribute(AttrAttribute attribute) { - ArgumentGuard.NotNull(attribute, nameof(attribute)); + ArgumentGuard.NotNull(attribute); this[attribute] = null; } public void IncludeAttributes(IEnumerable attributes) { - ArgumentGuard.NotNull(attributes, nameof(attributes)); + ArgumentGuard.NotNull(attributes); foreach (AttrAttribute attribute in attributes) { @@ -54,7 +54,7 @@ public void IncludeAttributes(IEnumerable attributes) public void IncludeRelationship(RelationshipAttribute relationship, QueryLayer? queryLayer) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); this[relationship] = queryLayer; } diff --git a/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs b/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs index 509baf73ee..bbd383fa28 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/EvaluatedIncludeCache.cs @@ -10,7 +10,7 @@ internal sealed class EvaluatedIncludeCache : IEvaluatedIncludeCache /// public void Set(IncludeExpression include) { - ArgumentGuard.NotNull(include, nameof(include)); + ArgumentGuard.NotNull(include); _include = include; } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index 705f057bc5..c68e0f77f7 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -18,7 +18,7 @@ public class FilterParser : QueryExpressionParser public FilterParser(IResourceFactory resourceFactory, Action? validateSingleFieldCallback = null) { - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); + ArgumentGuard.NotNull(resourceFactory); _resourceFactory = resourceFactory; _validateSingleFieldCallback = validateSingleFieldCallback; @@ -26,7 +26,7 @@ public FilterParser(IResourceFactory resourceFactory, Action { diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 14d2f1ec15..7418be160f 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -15,7 +15,7 @@ public class IncludeParser : QueryExpressionParser public IncludeExpression Parse(string source, ResourceType resourceTypeInScope, int? maximumDepth) { - ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope)); + ArgumentGuard.NotNull(resourceTypeInScope); Tokenize(source); @@ -266,7 +266,7 @@ private sealed class HiddenRootRelationshipAttribute : RelationshipAttribute { public HiddenRootRelationshipAttribute(ResourceType rightType) { - ArgumentGuard.NotNull(rightType, nameof(rightType)); + ArgumentGuard.NotNull(rightType); RightType = rightType; PublicName = "<>"; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs index 29c7713b11..50b542de6e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/PaginationParser.cs @@ -19,7 +19,7 @@ public PaginationParser(Action? va public PaginationQueryStringValueExpression Parse(string source, ResourceType resourceTypeInScope) { - ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope)); + ArgumentGuard.NotNull(resourceTypeInScope); _resourceTypeInScope = resourceTypeInScope; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs index 3cba8e4515..ef95b3ed92 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryStringParameterScopeParser.cs @@ -22,7 +22,7 @@ public QueryStringParameterScopeParser(FieldChainRequirements chainRequirements, public QueryStringParameterScopeExpression Parse(string source, ResourceType resourceTypeInScope) { - ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope)); + ArgumentGuard.NotNull(resourceTypeInScope); _resourceTypeInScope = resourceTypeInScope; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs index 3f04ce92aa..cd920554c9 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/QueryTokenizer.cs @@ -27,7 +27,7 @@ public sealed class QueryTokenizer public QueryTokenizer(string source) { - ArgumentGuard.NotNull(source, nameof(source)); + ArgumentGuard.NotNull(source); _source = source; } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs index 84782c2b3e..7f4a142ef0 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SortParser.cs @@ -19,7 +19,7 @@ public SortParser(Action? validate public SortExpression Parse(string source, ResourceType resourceTypeInScope) { - ArgumentGuard.NotNull(resourceTypeInScope, nameof(resourceTypeInScope)); + ArgumentGuard.NotNull(resourceTypeInScope); _resourceTypeInScope = resourceTypeInScope; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs index b4e54f0c46..0cabbcf76e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs @@ -19,7 +19,7 @@ public SparseFieldSetParser(Action public SparseFieldSetExpression? Parse(string source, ResourceType resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); _resourceType = resourceType; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs index b23dfdfea1..eceb05d211 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldTypeParser.cs @@ -12,7 +12,7 @@ public class SparseFieldTypeParser : QueryExpressionParser public SparseFieldTypeParser(IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(resourceGraph); _resourceGraph = resourceGraph; } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 40af882044..29e0935954 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -25,13 +25,13 @@ public QueryLayerComposer(IEnumerable constraintProvid IJsonApiOptions options, IPaginationContext paginationContext, ITargetedFields targetedFields, IEvaluatedIncludeCache evaluatedIncludeCache, ISparseFieldSetCache sparseFieldSetCache) { - ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(paginationContext, nameof(paginationContext)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(evaluatedIncludeCache, nameof(evaluatedIncludeCache)); - ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache)); + ArgumentGuard.NotNull(constraintProviders); + ArgumentGuard.NotNull(resourceDefinitionAccessor); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(paginationContext); + ArgumentGuard.NotNull(targetedFields); + ArgumentGuard.NotNull(evaluatedIncludeCache); + ArgumentGuard.NotNull(sparseFieldSetCache); _constraintProviders = constraintProviders; _resourceDefinitionAccessor = resourceDefinitionAccessor; @@ -65,7 +65,7 @@ public QueryLayerComposer(IEnumerable constraintProvid /// public FilterExpression? GetSecondaryFilterFromConstraints(TId primaryId, HasManyAttribute hasManyRelationship) { - ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship)); + ArgumentGuard.NotNull(hasManyRelationship); if (hasManyRelationship.InverseNavigationProperty == null) { @@ -131,7 +131,7 @@ private static FilterExpression GetInverseHasManyRelationshipFilter(TId pri /// public QueryLayer ComposeFromConstraints(ResourceType requestResourceType) { - ArgumentGuard.NotNull(requestResourceType, nameof(requestResourceType)); + ArgumentGuard.NotNull(requestResourceType); ExpressionInScope[] constraints = _constraintProviders.SelectMany(provider => provider.GetConstraints()).ToArray(); @@ -268,7 +268,7 @@ private static IImmutableSet ApplyIncludeElementUpdate /// public QueryLayer ComposeForGetById(TId id, ResourceType primaryResourceType, TopFieldSelection fieldSelection) { - ArgumentGuard.NotNull(primaryResourceType, nameof(primaryResourceType)); + ArgumentGuard.NotNull(primaryResourceType); AttrAttribute idAttribute = GetIdAttribute(primaryResourceType); @@ -296,7 +296,7 @@ public QueryLayer ComposeForGetById(TId id, ResourceType primaryResourceTyp /// public QueryLayer ComposeSecondaryLayerForRelationship(ResourceType secondaryResourceType) { - ArgumentGuard.NotNull(secondaryResourceType, nameof(secondaryResourceType)); + ArgumentGuard.NotNull(secondaryResourceType); QueryLayer secondaryLayer = ComposeFromConstraints(secondaryResourceType); secondaryLayer.Selection = GetSelectionForRelationship(secondaryResourceType); @@ -320,9 +320,9 @@ private FieldSelection GetSelectionForRelationship(ResourceType secondaryResourc public QueryLayer WrapLayerForSecondaryEndpoint(QueryLayer secondaryLayer, ResourceType primaryResourceType, TId primaryId, RelationshipAttribute relationship) { - ArgumentGuard.NotNull(secondaryLayer, nameof(secondaryLayer)); - ArgumentGuard.NotNull(primaryResourceType, nameof(primaryResourceType)); - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(secondaryLayer); + ArgumentGuard.NotNull(primaryResourceType); + ArgumentGuard.NotNull(relationship); IncludeExpression? innerInclude = secondaryLayer.Include; secondaryLayer.Include = null; @@ -377,7 +377,7 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression? /// public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType) { - ArgumentGuard.NotNull(primaryResourceType, nameof(primaryResourceType)); + ArgumentGuard.NotNull(primaryResourceType); IImmutableSet includeElements = _targetedFields.Relationships .Select(relationship => new IncludeElementExpression(relationship)).ToImmutableHashSet(); @@ -397,7 +397,7 @@ public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType /// public IEnumerable<(QueryLayer, RelationshipAttribute)> ComposeForGetTargetedSecondaryResourceIds(IIdentifiable primaryResource) { - ArgumentGuard.NotNull(primaryResource, nameof(primaryResource)); + ArgumentGuard.NotNull(primaryResource); foreach (RelationshipAttribute relationship in _targetedFields.Relationships) { @@ -415,8 +415,8 @@ public QueryLayer ComposeForUpdate(TId id, ResourceType primaryResourceType /// public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relationship, ICollection rightResourceIds) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(relationship); + ArgumentGuard.NotNull(rightResourceIds); AttrAttribute rightIdAttribute = GetIdAttribute(relationship.RightType); @@ -440,8 +440,8 @@ public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relati /// public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, TId leftId, ICollection rightResourceIds) { - ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(hasManyRelationship); + ArgumentGuard.NotNull(rightResourceIds); AttrAttribute leftIdAttribute = GetIdAttribute(hasManyRelationship.LeftType); AttrAttribute rightIdAttribute = GetIdAttribute(hasManyRelationship.RightType); @@ -476,15 +476,15 @@ public QueryLayer ComposeForHasMany(HasManyAttribute hasManyRelationship, T protected virtual IImmutableSet GetIncludeElements(IImmutableSet includeElements, ResourceType resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); return _resourceDefinitionAccessor.OnApplyIncludes(resourceType, includeElements); } protected virtual FilterExpression? GetFilter(IReadOnlyCollection expressionsInScope, ResourceType resourceType) { - ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(expressionsInScope); + ArgumentGuard.NotNull(resourceType); FilterExpression[] filters = expressionsInScope.OfType().ToArray(); FilterExpression? filter = LogicalExpression.Compose(LogicalOperator.And, filters); @@ -494,8 +494,8 @@ protected virtual IImmutableSet GetIncludeElements(IIm protected virtual SortExpression GetSort(IReadOnlyCollection expressionsInScope, ResourceType resourceType) { - ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(expressionsInScope); + ArgumentGuard.NotNull(resourceType); SortExpression? sort = expressionsInScope.OfType().FirstOrDefault(); @@ -513,8 +513,8 @@ protected virtual SortExpression GetSort(IReadOnlyCollection ex protected virtual PaginationExpression GetPagination(IReadOnlyCollection expressionsInScope, ResourceType resourceType) { - ArgumentGuard.NotNull(expressionsInScope, nameof(expressionsInScope)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(expressionsInScope); + ArgumentGuard.NotNull(resourceType); PaginationExpression? pagination = expressionsInScope.OfType().FirstOrDefault(); @@ -529,7 +529,7 @@ protected virtual PaginationExpression GetPagination(IReadOnlyCollection public IncludeClauseBuilder(Expression source, LambdaScope lambdaScope, ResourceType resourceType) : base(lambdaScope) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(resourceType); _source = source; _resourceType = resourceType; @@ -30,7 +30,7 @@ public IncludeClauseBuilder(Expression source, LambdaScope lambdaScope, Resource public Expression ApplyInclude(IncludeExpression include) { - ArgumentGuard.NotNull(include, nameof(include)); + ArgumentGuard.NotNull(include); return Visit(include, null); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs index 864b71c843..32691e05ab 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameFactory.cs @@ -13,7 +13,7 @@ public sealed class LambdaParameterNameFactory public LambdaParameterNameScope Create(string typeName) { - ArgumentGuard.NotNullNorEmpty(typeName, nameof(typeName)); + ArgumentGuard.NotNullNorEmpty(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 2bad41d310..031dae0a0f 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaParameterNameScope.cs @@ -11,8 +11,8 @@ public sealed class LambdaParameterNameScope : IDisposable public LambdaParameterNameScope(string name, LambdaParameterNameFactory owner) { - ArgumentGuard.NotNullNorEmpty(name, nameof(name)); - ArgumentGuard.NotNull(owner, nameof(owner)); + ArgumentGuard.NotNullNorEmpty(name); + ArgumentGuard.NotNull(owner); Name = name; _owner = owner; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs index e5502031a3..52caddbe62 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScope.cs @@ -23,8 +23,8 @@ private LambdaScope(LambdaParameterNameScope parameterNameScope, ParameterExpres public static LambdaScope Create(LambdaParameterNameFactory nameFactory, Type elementType, Expression? accessorExpression) { - ArgumentGuard.NotNull(nameFactory, nameof(nameFactory)); - ArgumentGuard.NotNull(elementType, nameof(elementType)); + ArgumentGuard.NotNull(nameFactory); + ArgumentGuard.NotNull(elementType); LambdaParameterNameScope parameterNameScope = nameFactory.Create(elementType.Name); ParameterExpression parameter = Expression.Parameter(elementType, parameterNameScope.Name); @@ -35,7 +35,7 @@ public static LambdaScope Create(LambdaParameterNameFactory nameFactory, Type el public LambdaScope WithAccessor(Expression accessorExpression) { - ArgumentGuard.NotNull(accessorExpression, nameof(accessorExpression)); + ArgumentGuard.NotNull(accessorExpression); return new LambdaScope(_parameterNameScope, Parameter, accessorExpression); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs index 9c13a63d28..6e4955cf40 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/LambdaScopeFactory.cs @@ -10,14 +10,14 @@ public sealed class LambdaScopeFactory public LambdaScopeFactory(LambdaParameterNameFactory nameFactory) { - ArgumentGuard.NotNull(nameFactory, nameof(nameFactory)); + ArgumentGuard.NotNull(nameFactory); _nameFactory = nameFactory; } public LambdaScope CreateScope(Type elementType, Expression? accessorExpression = null) { - ArgumentGuard.NotNull(elementType, nameof(elementType)); + ArgumentGuard.NotNull(elementType); return LambdaScope.Create(_nameFactory, elementType, accessorExpression); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs index 7ae8dd2392..775893adcc 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/OrderClauseBuilder.cs @@ -17,8 +17,8 @@ public class OrderClauseBuilder : QueryClauseBuilder public OrderClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType) : base(lambdaScope) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(extensionType, nameof(extensionType)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(extensionType); _source = source; _extensionType = extensionType; @@ -26,7 +26,7 @@ public OrderClauseBuilder(Expression source, LambdaScope lambdaScope, Type exten public Expression ApplyOrderBy(SortExpression expression) { - ArgumentGuard.NotNull(expression, nameof(expression)); + ArgumentGuard.NotNull(expression); return Visit(expression, null); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs index d04ff57e9d..fdbb3bc0c3 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryClauseBuilder.cs @@ -14,7 +14,7 @@ public abstract class QueryClauseBuilder : QueryExpressionVisitor(Expression accessorExpression, Func action) { - ArgumentGuard.NotNull(accessorExpression, nameof(accessorExpression)); - ArgumentGuard.NotNull(action, nameof(action)); + ArgumentGuard.NotNull(accessorExpression); + ArgumentGuard.NotNull(action); LambdaScope backupScope = LambdaScope; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs index d571ac1dce..a497846285 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs @@ -24,12 +24,12 @@ public class QueryableBuilder public QueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, IResourceFactory resourceFactory, IModel entityModel, LambdaScopeFactory? lambdaScopeFactory = null) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(elementType, nameof(elementType)); - ArgumentGuard.NotNull(extensionType, nameof(extensionType)); - ArgumentGuard.NotNull(nameFactory, nameof(nameFactory)); - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); - ArgumentGuard.NotNull(entityModel, nameof(entityModel)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(elementType); + ArgumentGuard.NotNull(extensionType); + ArgumentGuard.NotNull(nameFactory); + ArgumentGuard.NotNull(resourceFactory); + ArgumentGuard.NotNull(entityModel); _source = source; _elementType = elementType; @@ -42,7 +42,7 @@ public QueryableBuilder(Expression source, Type elementType, Type extensionType, public virtual Expression ApplyQuery(QueryLayer layer) { - ArgumentGuard.NotNull(layer, nameof(layer)); + ArgumentGuard.NotNull(layer); Expression expression = _source; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs index 690c49de24..1f1c10301a 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs @@ -31,11 +31,11 @@ public SelectClauseBuilder(Expression source, LambdaScope lambdaScope, IModel en IResourceFactory resourceFactory) : base(lambdaScope) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(entityModel, nameof(entityModel)); - ArgumentGuard.NotNull(extensionType, nameof(extensionType)); - ArgumentGuard.NotNull(nameFactory, nameof(nameFactory)); - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(entityModel); + ArgumentGuard.NotNull(extensionType); + ArgumentGuard.NotNull(nameFactory); + ArgumentGuard.NotNull(resourceFactory); _source = source; _entityModel = entityModel; @@ -46,7 +46,7 @@ public SelectClauseBuilder(Expression source, LambdaScope lambdaScope, IModel en public Expression ApplySelect(FieldSelection selection, ResourceType resourceType) { - ArgumentGuard.NotNull(selection, nameof(selection)); + ArgumentGuard.NotNull(selection); Expression bodyInitializer = CreateLambdaBodyInitializer(selection, resourceType, LambdaScope, false); @@ -272,7 +272,7 @@ private sealed class PropertySelector public PropertySelector(PropertyInfo property, QueryLayer? nextLayer = null) { - ArgumentGuard.NotNull(property, nameof(property)); + ArgumentGuard.NotNull(property); Property = property; NextLayer = nextLayer; diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs index 4bb9bfd6f5..90109dbfec 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SkipTakeClauseBuilder.cs @@ -17,8 +17,8 @@ public class SkipTakeClauseBuilder : QueryClauseBuilder public SkipTakeClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType) : base(lambdaScope) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(extensionType, nameof(extensionType)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(extensionType); _source = source; _extensionType = extensionType; @@ -26,7 +26,7 @@ public SkipTakeClauseBuilder(Expression source, LambdaScope lambdaScope, Type ex public Expression ApplySkipTake(PaginationExpression expression) { - ArgumentGuard.NotNull(expression, nameof(expression)); + ArgumentGuard.NotNull(expression); return Visit(expression, null); } diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs index 2806e96da4..1198a488ff 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/WhereClauseBuilder.cs @@ -24,9 +24,9 @@ public class WhereClauseBuilder : QueryClauseBuilder public WhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType, LambdaParameterNameFactory nameFactory) : base(lambdaScope) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(extensionType, nameof(extensionType)); - ArgumentGuard.NotNull(nameFactory, nameof(nameFactory)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(extensionType); + ArgumentGuard.NotNull(nameFactory); _source = source; _extensionType = extensionType; @@ -35,7 +35,7 @@ public WhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type exten public Expression ApplyWhere(FilterExpression filter) { - ArgumentGuard.NotNull(filter, nameof(filter)); + ArgumentGuard.NotNull(filter); LambdaExpression lambda = GetPredicateLambda(filter); diff --git a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs index 0cad7968c4..495af2ebc1 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs @@ -18,8 +18,8 @@ public sealed class SparseFieldSetCache : ISparseFieldSetCache public SparseFieldSetCache(IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor) { - ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); + ArgumentGuard.NotNull(constraintProviders); + ArgumentGuard.NotNull(resourceDefinitionAccessor); _resourceDefinitionAccessor = resourceDefinitionAccessor; _lazySourceTable = new Lazy>>(() => BuildSourceTable(constraintProviders)); @@ -70,7 +70,7 @@ private static void AddSparseFieldsToSet(IImmutableSet s /// public IImmutableSet GetSparseFieldSetForQuery(ResourceType resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); if (!_visitedTable.ContainsKey(resourceType)) { @@ -93,7 +93,7 @@ public IImmutableSet GetSparseFieldSetForQuery(ResourceT /// public IImmutableSet GetIdAttributeSetForRelationshipQuery(ResourceType resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); AttrAttribute idAttribute = resourceType.GetAttributeByPropertyName(nameof(Identifiable.Id)); var inputExpression = new SparseFieldSetExpression(ImmutableHashSet.Create(idAttribute)); @@ -112,7 +112,7 @@ public IImmutableSet GetIdAttributeSetForRelationshipQuery(Resour /// public IImmutableSet GetSparseFieldSetForSerializer(ResourceType resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); if (!_visitedTable.ContainsKey(resourceType)) { diff --git a/src/JsonApiDotNetCore/Queries/QueryLayer.cs b/src/JsonApiDotNetCore/Queries/QueryLayer.cs index c460560a33..95d61fd4b8 100644 --- a/src/JsonApiDotNetCore/Queries/QueryLayer.cs +++ b/src/JsonApiDotNetCore/Queries/QueryLayer.cs @@ -21,7 +21,7 @@ public sealed class QueryLayer public QueryLayer(ResourceType resourceType) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); ResourceType = resourceType; } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index 4fcd3e63d9..18167acbc0 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -31,7 +31,7 @@ public class FilterQueryStringParameterReader : QueryStringParameterReader, IFil public FilterQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph, IResourceFactory resourceFactory, IJsonApiOptions options) : base(request, resourceGraph) { - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(options); _options = options; _scopeParser = new QueryStringParameterScopeParser(FieldChainRequirements.EndsInToMany); @@ -50,7 +50,7 @@ protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType re /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Filter); } @@ -58,7 +58,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr /// public virtual bool CanRead(string parameterName) { - ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(parameterName); bool isNested = parameterName.StartsWith("filter[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); return parameterName == "filter" || isNested; diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs index 6c8bfa2934..299e8b22b2 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs @@ -23,7 +23,7 @@ public class IncludeQueryStringParameterReader : QueryStringParameterReader, IIn public IncludeQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph, IJsonApiOptions options) : base(request, resourceGraph) { - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(options); _options = options; _includeParser = new IncludeParser(); @@ -32,7 +32,7 @@ public IncludeQueryStringParameterReader(IJsonApiRequest request, IResourceGraph /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Include); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs index 68d4555e26..259e4c70f1 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/LegacyFilterNotationConverter.cs @@ -27,7 +27,7 @@ public sealed class LegacyFilterNotationConverter public IEnumerable ExtractConditions(string parameterValue) { - ArgumentGuard.NotNullNorEmpty(parameterValue, nameof(parameterValue)); + ArgumentGuard.NotNullNorEmpty(parameterValue); if (parameterValue.StartsWith(ExpressionPrefix, StringComparison.Ordinal) || parameterValue.StartsWith(InPrefix, StringComparison.Ordinal) || parameterValue.StartsWith(NotInPrefix, StringComparison.Ordinal)) @@ -45,8 +45,8 @@ public IEnumerable ExtractConditions(string parameterValue) public (string parameterName, string parameterValue) Convert(string parameterName, string parameterValue) { - ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); - ArgumentGuard.NotNullNorEmpty(parameterValue, nameof(parameterValue)); + ArgumentGuard.NotNullNorEmpty(parameterName); + ArgumentGuard.NotNullNorEmpty(parameterValue); if (parameterValue.StartsWith(ExpressionPrefix, StringComparison.Ordinal)) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs index 743faee492..416b48f4de 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs @@ -28,7 +28,7 @@ public class PaginationQueryStringParameterReader : QueryStringParameterReader, public PaginationQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph, IJsonApiOptions options) : base(request, resourceGraph) { - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(options); _options = options; _paginationParser = new PaginationParser(); @@ -37,7 +37,7 @@ public PaginationQueryStringParameterReader(IJsonApiRequest request, IResourceGr /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Page); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs index 103429aa81..656cbff0cb 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringParameterReader.cs @@ -16,8 +16,8 @@ public abstract class QueryStringParameterReader protected QueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(resourceGraph); _resourceGraph = resourceGraph; _isCollectionRequest = request.IsCollection; diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs index 78e6fe9e92..b5dda40498 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/QueryStringReader.cs @@ -20,10 +20,10 @@ public class QueryStringReader : IQueryStringReader public QueryStringReader(IJsonApiOptions options, IRequestQueryStringAccessor queryStringAccessor, IEnumerable parameterReaders, ILoggerFactory loggerFactory) { - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(queryStringAccessor, nameof(queryStringAccessor)); - ArgumentGuard.NotNull(parameterReaders, nameof(parameterReaders)); + ArgumentGuard.NotNull(loggerFactory); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(queryStringAccessor); + ArgumentGuard.NotNull(parameterReaders); _options = options; _queryStringAccessor = queryStringAccessor; diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/RequestQueryStringAccessor.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/RequestQueryStringAccessor.cs index 2492f01001..2678627d1c 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/RequestQueryStringAccessor.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/RequestQueryStringAccessor.cs @@ -22,7 +22,7 @@ public IQueryCollection Query public RequestQueryStringAccessor(IHttpContextAccessor httpContextAccessor) { - ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); + ArgumentGuard.NotNull(httpContextAccessor); _httpContextAccessor = httpContextAccessor; } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs index 51c5de0583..de02589807 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/ResourceDefinitionQueryableParameterReader.cs @@ -21,8 +21,8 @@ public class ResourceDefinitionQueryableParameterReader : IResourceDefinitionQue public ResourceDefinitionQueryableParameterReader(IJsonApiRequest request, IResourceDefinitionAccessor resourceDefinitionAccessor) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(resourceDefinitionAccessor); _request = request; _resourceDefinitionAccessor = resourceDefinitionAccessor; diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs index 5fa60e7f66..060d0c9986 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs @@ -40,7 +40,7 @@ protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType re /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Sort); } @@ -48,7 +48,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr /// public virtual bool CanRead(string parameterName) { - ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(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 d8f1e858ea..dadf153f21 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -46,7 +46,7 @@ protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType re /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Fields); } @@ -54,7 +54,7 @@ public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttr /// public virtual bool CanRead(string parameterName) { - ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); + ArgumentGuard.NotNullNorEmpty(parameterName); return parameterName.StartsWith("fields[", StringComparison.Ordinal) && parameterName.EndsWith("]", StringComparison.Ordinal); } diff --git a/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs b/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs index cadbd658a8..f78841defc 100644 --- a/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs +++ b/src/JsonApiDotNetCore/Repositories/DbContextExtensions.cs @@ -13,8 +13,8 @@ public static class DbContextExtensions /// public static IIdentifiable GetTrackedOrAttach(this DbContext dbContext, IIdentifiable resource) { - ArgumentGuard.NotNull(dbContext, nameof(dbContext)); - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(dbContext); + ArgumentGuard.NotNull(resource); var trackedIdentifiable = (IIdentifiable?)dbContext.GetTrackedIdentifiable(resource); @@ -32,8 +32,8 @@ public static IIdentifiable GetTrackedOrAttach(this DbContext dbContext, IIdenti /// public static object? GetTrackedIdentifiable(this DbContext dbContext, IIdentifiable identifiable) { - ArgumentGuard.NotNull(dbContext, nameof(dbContext)); - ArgumentGuard.NotNull(identifiable, nameof(identifiable)); + ArgumentGuard.NotNull(dbContext); + ArgumentGuard.NotNull(identifiable); Type resourceClrType = identifiable.GetClrType(); string? stringId = identifiable.StringId; @@ -53,7 +53,7 @@ private static bool IsResource(EntityEntry entry, Type resourceClrType, string? /// public static void ResetChangeTracker(this DbContext dbContext) { - ArgumentGuard.NotNull(dbContext, nameof(dbContext)); + ArgumentGuard.NotNull(dbContext); dbContext.ChangeTracker.Clear(); } diff --git a/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs b/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs index c8013a5f0a..99decdec66 100644 --- a/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs +++ b/src/JsonApiDotNetCore/Repositories/DbContextResolver.cs @@ -12,7 +12,7 @@ public sealed class DbContextResolver : IDbContextResolver public DbContextResolver(TDbContext dbContext) { - ArgumentGuard.NotNull(dbContext, nameof(dbContext)); + ArgumentGuard.NotNull(dbContext); _dbContext = dbContext; } diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 1b807fd24f..653db6129a 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -41,13 +41,13 @@ public EntityFrameworkCoreRepository(ITargetedFields targetedFields, IDbContextR IResourceFactory resourceFactory, IEnumerable constraintProviders, ILoggerFactory loggerFactory, IResourceDefinitionAccessor resourceDefinitionAccessor) { - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(dbContextResolver, nameof(dbContextResolver)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); - ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); + ArgumentGuard.NotNull(targetedFields); + ArgumentGuard.NotNull(dbContextResolver); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(resourceFactory); + ArgumentGuard.NotNull(constraintProviders); + ArgumentGuard.NotNull(loggerFactory); + ArgumentGuard.NotNull(resourceDefinitionAccessor); _targetedFields = targetedFields; _dbContext = dbContextResolver.GetContext(); @@ -66,7 +66,7 @@ public virtual async Task> GetAsync(QueryLayer qu queryLayer }); - ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); + ArgumentGuard.NotNull(queryLayer); using (CodeTimingSessionManager.Current.Measure("Repository - Get resource(s)")) { @@ -112,7 +112,7 @@ protected virtual IQueryable ApplyQueryLayer(QueryLayer queryLayer) queryLayer }); - ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); + ArgumentGuard.NotNull(queryLayer); using (CodeTimingSessionManager.Current.Measure("Convert QueryLayer to System.Expression")) { @@ -178,8 +178,8 @@ public virtual async Task CreateAsync(TResource resourceFromRequest, TResource r resourceForDatabase }); - ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); - ArgumentGuard.NotNull(resourceForDatabase, nameof(resourceForDatabase)); + ArgumentGuard.NotNull(resourceFromRequest); + ArgumentGuard.NotNull(resourceForDatabase); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Create resource"); @@ -240,7 +240,7 @@ await _resourceDefinitionAccessor.OnSetToManyRelationshipAsync(leftResource, has queryLayer }); - ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); + ArgumentGuard.NotNull(queryLayer); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Get resource for update"); @@ -257,8 +257,8 @@ public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource r resourceFromDatabase }); - ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); - ArgumentGuard.NotNull(resourceFromDatabase, nameof(resourceFromDatabase)); + ArgumentGuard.NotNull(resourceFromRequest); + ArgumentGuard.NotNull(resourceFromDatabase); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Update resource"); @@ -394,7 +394,7 @@ public virtual async Task SetRelationshipAsync(TResource leftResource, object? r rightValue }); - ArgumentGuard.NotNull(leftResource, nameof(leftResource)); + ArgumentGuard.NotNull(leftResource); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Set relationship"); @@ -425,7 +425,7 @@ public virtual async Task AddToToManyRelationshipAsync(TResource? leftResource, rightResourceIds }); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(rightResourceIds); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Add to to-many relationship"); @@ -488,8 +488,8 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TResource leftResour rightResourceIds }); - ArgumentGuard.NotNull(leftResource, nameof(leftResource)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(leftResource); + ArgumentGuard.NotNull(rightResourceIds); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Remove from to-many relationship"); diff --git a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs index 9d42788940..97fcd5ff58 100644 --- a/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore/Repositories/ResourceRepositoryAccessor.cs @@ -19,9 +19,9 @@ public class ResourceRepositoryAccessor : IResourceRepositoryAccessor public ResourceRepositoryAccessor(IServiceProvider serviceProvider, IResourceGraph resourceGraph, IJsonApiRequest request) { - ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(request, nameof(request)); + ArgumentGuard.NotNull(serviceProvider); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(request); _serviceProvider = serviceProvider; _resourceGraph = resourceGraph; @@ -39,7 +39,7 @@ public async Task> GetAsync(QueryLayer /// public async Task> GetAsync(ResourceType resourceType, QueryLayer queryLayer, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic repository = ResolveReadRepository(resourceType); return (IReadOnlyCollection)await repository.GetAsync(queryLayer, cancellationToken); diff --git a/src/JsonApiDotNetCore/Resources/AbstractResourceWrapper.cs b/src/JsonApiDotNetCore/Resources/AbstractResourceWrapper.cs index 0d294feb57..c3a193d3cd 100644 --- a/src/JsonApiDotNetCore/Resources/AbstractResourceWrapper.cs +++ b/src/JsonApiDotNetCore/Resources/AbstractResourceWrapper.cs @@ -8,7 +8,7 @@ internal sealed class AbstractResourceWrapper : Identifiable, IAbstrac public AbstractResourceWrapper(Type abstractType) { - ArgumentGuard.NotNull(abstractType, nameof(abstractType)); + ArgumentGuard.NotNull(abstractType); AbstractType = abstractType; } diff --git a/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs b/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs index 8c9d4b6a36..9a1c025214 100644 --- a/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs +++ b/src/JsonApiDotNetCore/Resources/IdentifiableExtensions.cs @@ -9,7 +9,7 @@ internal static class IdentifiableExtensions public static object GetTypedId(this IIdentifiable identifiable) { - ArgumentGuard.NotNull(identifiable, nameof(identifiable)); + ArgumentGuard.NotNull(identifiable); PropertyInfo? property = identifiable.GetClrType().GetProperty(IdPropertyName); @@ -37,7 +37,7 @@ public static object GetTypedId(this IIdentifiable identifiable) public static Type GetClrType(this IIdentifiable identifiable) { - ArgumentGuard.NotNull(identifiable, nameof(identifiable)); + ArgumentGuard.NotNull(identifiable); return identifiable is IAbstractResourceWrapper abstractResource ? abstractResource.AbstractType : identifiable.GetType(); } diff --git a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs index dbb90bf6fe..fa693d205c 100644 --- a/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Resources/JsonApiResourceDefinition.cs @@ -26,7 +26,7 @@ public class JsonApiResourceDefinition : IResourceDefinition(); @@ -65,7 +65,7 @@ public virtual IImmutableSet OnApplyIncludes(IImmutabl /// protected SortExpression CreateSortExpressionFromLambda(PropertySortOrder keySelectors) { - ArgumentGuard.NotNullNorEmpty(keySelectors, nameof(keySelectors)); + ArgumentGuard.NotNullNorEmpty(keySelectors); ImmutableArray.Builder elementsBuilder = ImmutableArray.CreateBuilder(keySelectors.Count); var lambdaConverter = new SortExpressionLambdaConverter(ResourceGraph); diff --git a/src/JsonApiDotNetCore/Resources/OperationContainer.cs b/src/JsonApiDotNetCore/Resources/OperationContainer.cs index d2fa2c0d3e..a066943c82 100644 --- a/src/JsonApiDotNetCore/Resources/OperationContainer.cs +++ b/src/JsonApiDotNetCore/Resources/OperationContainer.cs @@ -18,9 +18,9 @@ public sealed class OperationContainer public OperationContainer(IIdentifiable resource, ITargetedFields targetedFields, IJsonApiRequest request) { - ArgumentGuard.NotNull(resource, nameof(resource)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(request, nameof(request)); + ArgumentGuard.NotNull(resource); + ArgumentGuard.NotNull(targetedFields); + ArgumentGuard.NotNull(request); Resource = resource; TargetedFields = targetedFields; @@ -34,7 +34,7 @@ public void SetTransactionId(string transactionId) public OperationContainer WithResource(IIdentifiable resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); return new OperationContainer(resource, TargetedFields, Request); } diff --git a/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs b/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs index 658e5e2c5b..89ba115a64 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceChangeTracker.cs @@ -19,8 +19,8 @@ public sealed class ResourceChangeTracker : IResourceChangeTracker public void SetInitiallyStoredAttributeValues(TResource resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); _initiallyStoredAttributeValues = CreateAttributeDictionary(resource, _request.PrimaryResourceType!.Attributes); } @@ -37,7 +37,7 @@ public void SetInitiallyStoredAttributeValues(TResource resource) /// public void SetRequestAttributeValues(TResource resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); _requestAttributeValues = CreateAttributeDictionary(resource, _targetedFields.Attributes); } @@ -45,7 +45,7 @@ public void SetRequestAttributeValues(TResource resource) /// public void SetFinallyStoredAttributeValues(TResource resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); _finallyStoredAttributeValues = CreateAttributeDictionary(resource, _request.PrimaryResourceType!.Attributes); } diff --git a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs index 9f8dfdddeb..79b48c99eb 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceDefinitionAccessor.cs @@ -17,8 +17,8 @@ public class ResourceDefinitionAccessor : IResourceDefinitionAccessor public ResourceDefinitionAccessor(IResourceGraph resourceGraph, IServiceProvider serviceProvider) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider)); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(serviceProvider); _resourceGraph = resourceGraph; _serviceProvider = serviceProvider; @@ -27,7 +27,7 @@ public ResourceDefinitionAccessor(IResourceGraph resourceGraph, IServiceProvider /// public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); return resourceDefinition.OnApplyIncludes(existingIncludes); @@ -36,7 +36,7 @@ public IImmutableSet OnApplyIncludes(ResourceType reso /// public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); return resourceDefinition.OnApplyFilter(existingFilter); @@ -45,7 +45,7 @@ public IImmutableSet OnApplyIncludes(ResourceType reso /// public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); return resourceDefinition.OnApplySort(existingSort); @@ -54,7 +54,7 @@ public IImmutableSet OnApplyIncludes(ResourceType reso /// public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); return resourceDefinition.OnApplyPagination(existingPagination); @@ -63,7 +63,7 @@ public IImmutableSet OnApplyIncludes(ResourceType reso /// public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); return resourceDefinition.OnApplySparseFieldSet(existingSparseFieldSet); @@ -72,8 +72,8 @@ public IImmutableSet OnApplyIncludes(ResourceType reso /// public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); - ArgumentGuard.NotNullNorEmpty(parameterName, nameof(parameterName)); + ArgumentGuard.NotNull(resourceClrType); + ArgumentGuard.NotNullNorEmpty(parameterName); dynamic resourceDefinition = ResolveResourceDefinition(resourceClrType); dynamic handlers = resourceDefinition.OnRegisterQueryableHandlersForQueryStringParameters(); @@ -92,7 +92,7 @@ public IImmutableSet OnApplyIncludes(ResourceType reso /// public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resourceType); dynamic resourceDefinition = ResolveResourceDefinition(resourceType); return resourceDefinition.GetMeta((dynamic)resourceInstance); @@ -102,7 +102,7 @@ public IImmutableSet OnApplyIncludes(ResourceType reso public async Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); dynamic resourceDefinition = ResolveResourceDefinition(resource.GetClrType()); await resourceDefinition.OnPrepareWriteAsync((dynamic)resource, writeOperation, cancellationToken); @@ -113,8 +113,8 @@ public async Task OnPrepareWriteAsync(TResource resource, WriteOperat IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(leftResource, nameof(leftResource)); - ArgumentGuard.NotNull(hasOneRelationship, nameof(hasOneRelationship)); + ArgumentGuard.NotNull(leftResource); + ArgumentGuard.NotNull(hasOneRelationship); dynamic resourceDefinition = ResolveResourceDefinition(leftResource.GetClrType()); @@ -127,9 +127,9 @@ public async Task OnSetToManyRelationshipAsync(TResource leftResource ISet rightResourceIds, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(leftResource, nameof(leftResource)); - ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(leftResource); + ArgumentGuard.NotNull(hasManyRelationship); + ArgumentGuard.NotNull(rightResourceIds); dynamic resourceDefinition = ResolveResourceDefinition(leftResource.GetClrType()); await resourceDefinition.OnSetToManyRelationshipAsync((dynamic)leftResource, hasManyRelationship, rightResourceIds, writeOperation, cancellationToken); @@ -140,8 +140,8 @@ public async Task OnAddToRelationshipAsync(TResource leftResource, Ha CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(hasManyRelationship); + ArgumentGuard.NotNull(rightResourceIds); dynamic resourceDefinition = ResolveResourceDefinition(leftResource.GetClrType()); await resourceDefinition.OnAddToRelationshipAsync((dynamic)leftResource, hasManyRelationship, rightResourceIds, cancellationToken); @@ -152,9 +152,9 @@ public async Task OnRemoveFromRelationshipAsync(TResource leftResourc ISet rightResourceIds, CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(leftResource, nameof(leftResource)); - ArgumentGuard.NotNull(hasManyRelationship, nameof(hasManyRelationship)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNull(leftResource); + ArgumentGuard.NotNull(hasManyRelationship); + ArgumentGuard.NotNull(rightResourceIds); dynamic resourceDefinition = ResolveResourceDefinition(leftResource.GetClrType()); await resourceDefinition.OnRemoveFromRelationshipAsync((dynamic)leftResource, hasManyRelationship, rightResourceIds, cancellationToken); @@ -164,7 +164,7 @@ public async Task OnRemoveFromRelationshipAsync(TResource leftResourc public async Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); dynamic resourceDefinition = ResolveResourceDefinition(resource.GetClrType()); await resourceDefinition.OnWritingAsync((dynamic)resource, writeOperation, cancellationToken); @@ -174,7 +174,7 @@ public async Task OnWritingAsync(TResource resource, WriteOperationKi public async Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) where TResource : class, IIdentifiable { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); dynamic resourceDefinition = ResolveResourceDefinition(resource.GetClrType()); await resourceDefinition.OnWriteSucceededAsync((dynamic)resource, writeOperation, cancellationToken); @@ -183,7 +183,7 @@ public async Task OnWriteSucceededAsync(TResource resource, WriteOper /// public void OnDeserialize(IIdentifiable resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); dynamic resourceDefinition = ResolveResourceDefinition(resource.GetClrType()); resourceDefinition.OnDeserialize((dynamic)resource); @@ -192,7 +192,7 @@ public void OnDeserialize(IIdentifiable resource) /// public void OnSerialize(IIdentifiable resource) { - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); dynamic resourceDefinition = ResolveResourceDefinition(resource.GetClrType()); resourceDefinition.OnSerialize((dynamic)resource); diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index c276c073cd..27ddc317d8 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -15,7 +15,7 @@ internal sealed class ResourceFactory : IResourceFactory public ResourceFactory(IServiceProvider serviceProvider) { - ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider)); + ArgumentGuard.NotNull(serviceProvider); _serviceProvider = serviceProvider; } @@ -23,7 +23,7 @@ public ResourceFactory(IServiceProvider serviceProvider) /// public IIdentifiable CreateInstance(Type resourceClrType) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); + ArgumentGuard.NotNull(resourceClrType); if (!resourceClrType.IsAssignableTo(typeof(IIdentifiable))) { @@ -85,7 +85,7 @@ private static IIdentifiable InnerCreateInstance(Type type, IServiceProvider ser /// public NewExpression CreateNewExpression(Type resourceClrType) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); + ArgumentGuard.NotNull(resourceClrType); if (HasSingleConstructorWithoutParameters(resourceClrType)) { diff --git a/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs b/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs index 36a6d97aec..f1efa204d4 100644 --- a/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs +++ b/src/JsonApiDotNetCore/Resources/SortExpressionLambdaConverter.cs @@ -15,14 +15,14 @@ internal sealed class SortExpressionLambdaConverter public SortExpressionLambdaConverter(IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(resourceGraph); _resourceGraph = resourceGraph; } public SortElementExpression FromLambda(Expression> keySelector, ListSortDirection sortDirection) { - ArgumentGuard.NotNull(keySelector, nameof(keySelector)); + ArgumentGuard.NotNull(keySelector); _fields.Clear(); diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs index 005e423add..25218b1ba9 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs @@ -27,7 +27,7 @@ public sealed class ResourceObjectConverter : JsonObjectConverter public AtomicReferenceResult Convert(AtomicReference atomicReference, ResourceIdentityRequirements requirements, RequestAdapterState state) { - ArgumentGuard.NotNull(atomicReference, nameof(atomicReference)); - ArgumentGuard.NotNull(requirements, nameof(requirements)); - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(atomicReference); + ArgumentGuard.NotNull(requirements); + ArgumentGuard.NotNull(state); using IDisposable _ = state.Position.PushElement("ref"); (IIdentifiable resource, ResourceType resourceType) = ConvertResourceIdentity(atomicReference, requirements, state); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceResult.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceResult.cs index 9e6d982e21..a15ecd8e0c 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceResult.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceResult.cs @@ -17,8 +17,8 @@ public sealed class AtomicReferenceResult public AtomicReferenceResult(IIdentifiable resource, ResourceType resourceType, RelationshipAttribute? relationship) { - ArgumentGuard.NotNull(resource, nameof(resource)); - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(resource); + ArgumentGuard.NotNull(resourceType); Resource = resource; ResourceType = resourceType; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentAdapter.cs index 369f9076d2..43e4a7a78b 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentAdapter.cs @@ -16,10 +16,10 @@ public DocumentAdapter(IJsonApiRequest request, ITargetedFields targetedFields, IDocumentInResourceOrRelationshipRequestAdapter documentInResourceOrRelationshipRequestAdapter, IDocumentInOperationsRequestAdapter documentInOperationsRequestAdapter) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(documentInResourceOrRelationshipRequestAdapter, nameof(documentInResourceOrRelationshipRequestAdapter)); - ArgumentGuard.NotNull(documentInOperationsRequestAdapter, nameof(documentInOperationsRequestAdapter)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(targetedFields); + ArgumentGuard.NotNull(documentInResourceOrRelationshipRequestAdapter); + ArgumentGuard.NotNull(documentInOperationsRequestAdapter); _request = request; _targetedFields = targetedFields; @@ -30,7 +30,7 @@ public DocumentAdapter(IJsonApiRequest request, ITargetedFields targetedFields, /// public object? Convert(Document document) { - ArgumentGuard.NotNull(document, nameof(document)); + ArgumentGuard.NotNull(document); using var adapterState = new RequestAdapterState(_request, _targetedFields); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInOperationsRequestAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInOperationsRequestAdapter.cs index 8a50db9fec..2a272c4cfd 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInOperationsRequestAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInOperationsRequestAdapter.cs @@ -13,8 +13,8 @@ public sealed class DocumentInOperationsRequestAdapter : BaseAdapter, IDocumentI public DocumentInOperationsRequestAdapter(IJsonApiOptions options, IAtomicOperationObjectAdapter atomicOperationObjectAdapter) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(atomicOperationObjectAdapter, nameof(atomicOperationObjectAdapter)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(atomicOperationObjectAdapter); _options = options; _atomicOperationObjectAdapter = atomicOperationObjectAdapter; @@ -23,7 +23,7 @@ public DocumentInOperationsRequestAdapter(IJsonApiOptions options, IAtomicOperat /// public IReadOnlyList Convert(Document document, RequestAdapterState state) { - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(state); AssertHasOperations(document.Operations, state); using IDisposable _ = state.Position.PushElement("atomic:operations"); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs index aaf5b813c8..f5d4cb088c 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs @@ -15,9 +15,9 @@ public sealed class DocumentInResourceOrRelationshipRequestAdapter : IDocumentIn public DocumentInResourceOrRelationshipRequestAdapter(IJsonApiOptions options, IResourceDataAdapter resourceDataAdapter, IRelationshipDataAdapter relationshipDataAdapter) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(resourceDataAdapter, nameof(resourceDataAdapter)); - ArgumentGuard.NotNull(relationshipDataAdapter, nameof(relationshipDataAdapter)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(resourceDataAdapter); + ArgumentGuard.NotNull(relationshipDataAdapter); _options = options; _resourceDataAdapter = resourceDataAdapter; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs index 89cf3caa18..ac1e25746b 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs @@ -14,7 +14,7 @@ public sealed class RelationshipDataAdapter : BaseAdapter, IRelationshipDataAdap public RelationshipDataAdapter(IResourceIdentifierObjectAdapter resourceIdentifierObjectAdapter) { - ArgumentGuard.NotNull(resourceIdentifierObjectAdapter, nameof(resourceIdentifierObjectAdapter)); + ArgumentGuard.NotNull(resourceIdentifierObjectAdapter); _resourceIdentifierObjectAdapter = resourceIdentifierObjectAdapter; } @@ -61,8 +61,8 @@ private static SingleOrManyData ToIdentifierData(Singl public object? Convert(SingleOrManyData data, RelationshipAttribute relationship, bool useToManyElementType, RequestAdapterState state) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(relationship); + ArgumentGuard.NotNull(state); AssertHasData(data, state); using IDisposable _ = state.Position.PushElement("data"); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs index 3ae7caa6af..644383e711 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterPosition.cs @@ -19,7 +19,7 @@ public RequestAdapterPosition() public IDisposable PushElement(string name) { - ArgumentGuard.NotNullNorEmpty(name, nameof(name)); + ArgumentGuard.NotNullNorEmpty(name); _stack.Push($"/{name}"); return _disposable; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterState.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterState.cs index 88cf686f51..c7d40e1794 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterState.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RequestAdapterState.cs @@ -24,8 +24,8 @@ public sealed class RequestAdapterState : IDisposable public RequestAdapterState(IJsonApiRequest request, ITargetedFields targetedFields) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(targetedFields); InjectableRequest = request; InjectableTargetedFields = targetedFields; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataAdapter.cs index dc84fbad3d..ffe971f42f 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceDataAdapter.cs @@ -12,8 +12,8 @@ public class ResourceDataAdapter : BaseAdapter, IResourceDataAdapter public ResourceDataAdapter(IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectAdapter resourceObjectAdapter) { - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); - ArgumentGuard.NotNull(resourceObjectAdapter, nameof(resourceObjectAdapter)); + ArgumentGuard.NotNull(resourceDefinitionAccessor); + ArgumentGuard.NotNull(resourceObjectAdapter); _resourceDefinitionAccessor = resourceDefinitionAccessor; _resourceObjectAdapter = resourceObjectAdapter; @@ -22,8 +22,8 @@ public ResourceDataAdapter(IResourceDefinitionAccessor resourceDefinitionAccesso /// public IIdentifiable Convert(SingleOrManyData data, ResourceIdentityRequirements requirements, RequestAdapterState state) { - ArgumentGuard.NotNull(requirements, nameof(requirements)); - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(requirements); + ArgumentGuard.NotNull(state); AssertHasData(data, state); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs index d0e1b54856..8032c6c60c 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentifierObjectAdapter.cs @@ -15,9 +15,9 @@ public ResourceIdentifierObjectAdapter(IResourceGraph resourceGraph, IResourceFa /// public IIdentifiable Convert(ResourceIdentifierObject resourceIdentifierObject, ResourceIdentityRequirements requirements, RequestAdapterState state) { - ArgumentGuard.NotNull(resourceIdentifierObject, nameof(resourceIdentifierObject)); - ArgumentGuard.NotNull(requirements, nameof(requirements)); - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(resourceIdentifierObject); + ArgumentGuard.NotNull(requirements); + ArgumentGuard.NotNull(state); (IIdentifiable resource, _) = ConvertResourceIdentity(resourceIdentifierObject, requirements, state); return resource; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs index 61c6cc1857..9df5215da9 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs @@ -18,8 +18,8 @@ public abstract class ResourceIdentityAdapter : BaseAdapter protected ResourceIdentityAdapter(IResourceGraph resourceGraph, IResourceFactory resourceFactory) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(resourceFactory); _resourceGraph = resourceGraph; _resourceFactory = resourceFactory; @@ -28,9 +28,9 @@ protected ResourceIdentityAdapter(IResourceGraph resourceGraph, IResourceFactory protected (IIdentifiable resource, ResourceType resourceType) ConvertResourceIdentity(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { - ArgumentGuard.NotNull(identity, nameof(identity)); - ArgumentGuard.NotNull(requirements, nameof(requirements)); - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(identity); + ArgumentGuard.NotNull(requirements); + ArgumentGuard.NotNull(state); ResourceType resourceType = ResolveType(identity, requirements, state); IIdentifiable resource = CreateResource(identity, requirements, resourceType.ClrType, state); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs index 2199782a3a..b489665a41 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs @@ -17,8 +17,8 @@ public ResourceObjectAdapter(IResourceGraph resourceGraph, IResourceFactory reso IRelationshipDataAdapter relationshipDataAdapter) : base(resourceGraph, resourceFactory) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(relationshipDataAdapter, nameof(relationshipDataAdapter)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(relationshipDataAdapter); _options = options; _relationshipDataAdapter = relationshipDataAdapter; @@ -28,9 +28,9 @@ public ResourceObjectAdapter(IResourceGraph resourceGraph, IResourceFactory reso public (IIdentifiable resource, ResourceType resourceType) Convert(ResourceObject resourceObject, ResourceIdentityRequirements requirements, RequestAdapterState state) { - ArgumentGuard.NotNull(resourceObject, nameof(resourceObject)); - ArgumentGuard.NotNull(requirements, nameof(requirements)); - ArgumentGuard.NotNull(state, nameof(state)); + ArgumentGuard.NotNull(resourceObject); + ArgumentGuard.NotNull(requirements); + ArgumentGuard.NotNull(state); (IIdentifiable resource, ResourceType resourceType) = ConvertResourceIdentity(resourceObject, requirements, state); diff --git a/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs index 0942683487..30e4508894 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/JsonApiReader.cs @@ -25,9 +25,9 @@ public sealed class JsonApiReader : IJsonApiReader public JsonApiReader(IJsonApiOptions options, IDocumentAdapter documentAdapter, ILoggerFactory loggerFactory) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(documentAdapter, nameof(documentAdapter)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(documentAdapter); + ArgumentGuard.NotNull(loggerFactory); _options = options; _documentAdapter = documentAdapter; @@ -37,7 +37,7 @@ public JsonApiReader(IJsonApiOptions options, IDocumentAdapter documentAdapter, /// public async Task ReadAsync(HttpRequest httpRequest) { - ArgumentGuard.NotNull(httpRequest, nameof(httpRequest)); + ArgumentGuard.NotNull(httpRequest); string requestBody = await ReceiveRequestBodyAsync(httpRequest); diff --git a/src/JsonApiDotNetCore/Serialization/Request/JsonInvalidAttributeInfo.cs b/src/JsonApiDotNetCore/Serialization/Request/JsonInvalidAttributeInfo.cs index 02ef36aa53..ed0afee834 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/JsonInvalidAttributeInfo.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/JsonInvalidAttributeInfo.cs @@ -16,8 +16,8 @@ internal sealed class JsonInvalidAttributeInfo public JsonInvalidAttributeInfo(string attributeName, Type attributeType, string? jsonValue, JsonValueKind jsonType) { - ArgumentGuard.NotNullNorEmpty(attributeName, nameof(attributeName)); - ArgumentGuard.NotNull(attributeType, nameof(attributeType)); + ArgumentGuard.NotNullNorEmpty(attributeName); + ArgumentGuard.NotNull(attributeType); AttributeName = attributeName; AttributeType = attributeType; diff --git a/src/JsonApiDotNetCore/Serialization/Request/ModelConversionException.cs b/src/JsonApiDotNetCore/Serialization/Request/ModelConversionException.cs index b43af538e5..cf2428129d 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/ModelConversionException.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/ModelConversionException.cs @@ -18,7 +18,7 @@ public sealed class ModelConversionException : Exception public ModelConversionException(RequestAdapterPosition position, string? genericMessage, string? specificMessage, HttpStatusCode? statusCode = null) : base(genericMessage) { - ArgumentGuard.NotNull(position, nameof(position)); + ArgumentGuard.NotNull(position); GenericMessage = genericMessage; SpecificMessage = specificMessage; diff --git a/src/JsonApiDotNetCore/Serialization/Response/ETagGenerator.cs b/src/JsonApiDotNetCore/Serialization/Response/ETagGenerator.cs index 1352317575..6fc6b7af61 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ETagGenerator.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ETagGenerator.cs @@ -9,7 +9,7 @@ internal sealed class ETagGenerator : IETagGenerator public ETagGenerator(IFingerprintGenerator fingerprintGenerator) { - ArgumentGuard.NotNull(fingerprintGenerator, nameof(fingerprintGenerator)); + ArgumentGuard.NotNull(fingerprintGenerator); _fingerprintGenerator = fingerprintGenerator; } diff --git a/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs b/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs index 0eaee430c3..5f9f7eeb6c 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/FingerprintGenerator.cs @@ -18,7 +18,7 @@ private static uint ToLookupEntry(int index) /// public string Generate(IEnumerable elements) { - ArgumentGuard.NotNull(elements, nameof(elements)); + ArgumentGuard.NotNull(elements); using var hasher = IncrementalHash.CreateHash(HashAlgorithmName.MD5); diff --git a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs index 20f4ad242b..8f77a2ff5c 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/JsonApiWriter.cs @@ -28,12 +28,12 @@ public sealed class JsonApiWriter : IJsonApiWriter public JsonApiWriter(IJsonApiRequest request, IJsonApiOptions options, IResponseModelAdapter responseModelAdapter, IExceptionHandler exceptionHandler, IETagGenerator eTagGenerator, ILoggerFactory loggerFactory) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(responseModelAdapter, nameof(responseModelAdapter)); - ArgumentGuard.NotNull(exceptionHandler, nameof(exceptionHandler)); - ArgumentGuard.NotNull(eTagGenerator, nameof(eTagGenerator)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(responseModelAdapter); + ArgumentGuard.NotNull(exceptionHandler); + ArgumentGuard.NotNull(eTagGenerator); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(loggerFactory); _request = request; _options = options; @@ -46,7 +46,7 @@ public JsonApiWriter(IJsonApiRequest request, IJsonApiOptions options, IResponse /// public async Task WriteAsync(object? model, HttpContext httpContext) { - ArgumentGuard.NotNull(httpContext, nameof(httpContext)); + ArgumentGuard.NotNull(httpContext); if (model == null && !CanWriteBody((HttpStatusCode)httpContext.Response.StatusCode)) { diff --git a/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs index 0bad02066b..ea0eb197df 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs @@ -50,11 +50,11 @@ private HttpContext HttpContext public LinkBuilder(IJsonApiOptions options, IJsonApiRequest request, IPaginationContext paginationContext, IHttpContextAccessor httpContextAccessor, LinkGenerator linkGenerator, IControllerResourceMapping controllerResourceMapping) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(paginationContext, nameof(paginationContext)); - ArgumentGuard.NotNull(linkGenerator, nameof(linkGenerator)); - ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(paginationContext); + ArgumentGuard.NotNull(linkGenerator); + ArgumentGuard.NotNull(controllerResourceMapping); _options = options; _request = request; @@ -225,8 +225,8 @@ private string GetQueryStringInPaginationLink(int pageOffset, string? pageSizeVa /// public ResourceLinks? GetResourceLinks(ResourceType resourceType, IIdentifiable resource) { - ArgumentGuard.NotNull(resourceType, nameof(resourceType)); - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resourceType); + ArgumentGuard.NotNull(resource); var links = new ResourceLinks(); @@ -263,8 +263,8 @@ private bool ShouldIncludeResourceLink(LinkTypes linkType, ResourceType resource /// public RelationshipLinks? GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); - ArgumentGuard.NotNull(leftResource, nameof(leftResource)); + ArgumentGuard.NotNull(relationship); + ArgumentGuard.NotNull(leftResource); var links = new RelationshipLinks(); diff --git a/src/JsonApiDotNetCore/Serialization/Response/MetaBuilder.cs b/src/JsonApiDotNetCore/Serialization/Response/MetaBuilder.cs index 91ec62387c..0da0ebe14b 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/MetaBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/MetaBuilder.cs @@ -16,9 +16,9 @@ public sealed class MetaBuilder : IMetaBuilder public MetaBuilder(IPaginationContext paginationContext, IJsonApiOptions options, IResponseMeta responseMeta) { - ArgumentGuard.NotNull(paginationContext, nameof(paginationContext)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(responseMeta, nameof(responseMeta)); + ArgumentGuard.NotNull(paginationContext); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(responseMeta); _paginationContext = paginationContext; _options = options; @@ -28,7 +28,7 @@ public MetaBuilder(IPaginationContext paginationContext, IJsonApiOptions options /// public void Add(IDictionary values) { - ArgumentGuard.NotNull(values, nameof(values)); + ArgumentGuard.NotNull(values); _meta = values.Keys.Union(_meta.Keys).ToDictionary(key => key, key => values.ContainsKey(key) ? values[key] : _meta[key]); } diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs index aed19d9097..01743168be 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs @@ -37,9 +37,9 @@ internal sealed class ResourceObjectTreeNode : IEquatable(); _directChildren.Add(treeNode); @@ -61,7 +61,7 @@ public void AttachDirectChild(ResourceObjectTreeNode treeNode) public void EnsureHasRelationship(RelationshipAttribute relationship) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); _childrenByRelationship ??= new Dictionary>(); @@ -73,8 +73,8 @@ public void EnsureHasRelationship(RelationshipAttribute relationship) public void AttachRelationshipChild(RelationshipAttribute relationship, ResourceObjectTreeNode rightNode) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); - ArgumentGuard.NotNull(rightNode, nameof(rightNode)); + ArgumentGuard.NotNull(relationship); + ArgumentGuard.NotNull(rightNode); if (_childrenByRelationship == null) { diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs b/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs index 223166e59e..5dc97f8052 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs @@ -37,14 +37,14 @@ public ResponseModelAdapter(IJsonApiRequest request, IJsonApiOptions options, IL IResourceDefinitionAccessor resourceDefinitionAccessor, IEvaluatedIncludeCache evaluatedIncludeCache, ISparseFieldSetCache sparseFieldSetCache, IRequestQueryStringAccessor requestQueryStringAccessor) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(linkBuilder, nameof(linkBuilder)); - ArgumentGuard.NotNull(metaBuilder, nameof(metaBuilder)); - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); - ArgumentGuard.NotNull(evaluatedIncludeCache, nameof(evaluatedIncludeCache)); - ArgumentGuard.NotNull(sparseFieldSetCache, nameof(sparseFieldSetCache)); - ArgumentGuard.NotNull(requestQueryStringAccessor, nameof(requestQueryStringAccessor)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(linkBuilder); + ArgumentGuard.NotNull(metaBuilder); + ArgumentGuard.NotNull(resourceDefinitionAccessor); + ArgumentGuard.NotNull(evaluatedIncludeCache); + ArgumentGuard.NotNull(sparseFieldSetCache); + ArgumentGuard.NotNull(requestQueryStringAccessor); _request = request; _options = options; diff --git a/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs b/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs index 3924999f63..11e483b419 100644 --- a/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Services/AsyncCollectionExtensions.cs @@ -7,8 +7,8 @@ public static class AsyncCollectionExtensions { public static async Task AddRangeAsync(this ICollection source, IAsyncEnumerable elementsToAdd, CancellationToken cancellationToken = default) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(elementsToAdd, nameof(elementsToAdd)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(elementsToAdd); await foreach (T missingResource in elementsToAdd.WithCancellation(cancellationToken)) { @@ -18,7 +18,7 @@ public static async Task AddRangeAsync(this ICollection source, IAsyncEnum public static async Task> ToListAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) { - ArgumentGuard.NotNull(source, nameof(source)); + ArgumentGuard.NotNull(source); var list = new List(); diff --git a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs index 0cca8b92b9..0d7280e1b4 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiResourceService.cs @@ -34,14 +34,14 @@ public JsonApiResourceService(IResourceRepositoryAccessor repositoryAccessor, IQ IJsonApiOptions options, ILoggerFactory loggerFactory, IJsonApiRequest request, IResourceChangeTracker resourceChangeTracker, IResourceDefinitionAccessor resourceDefinitionAccessor) { - ArgumentGuard.NotNull(repositoryAccessor, nameof(repositoryAccessor)); - ArgumentGuard.NotNull(queryLayerComposer, nameof(queryLayerComposer)); - ArgumentGuard.NotNull(paginationContext, nameof(paginationContext)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory)); - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(resourceChangeTracker, nameof(resourceChangeTracker)); - ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); + ArgumentGuard.NotNull(repositoryAccessor); + ArgumentGuard.NotNull(queryLayerComposer); + ArgumentGuard.NotNull(paginationContext); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(loggerFactory); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(resourceChangeTracker); + ArgumentGuard.NotNull(resourceDefinitionAccessor); _repositoryAccessor = repositoryAccessor; _queryLayerComposer = queryLayerComposer; @@ -145,7 +145,7 @@ public virtual async Task GetAsync(TId id, CancellationToken cancella relationshipName }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Service - Get relationship"); @@ -195,7 +195,7 @@ private async Task RetrieveResourceCountForNonPrimaryEndpointAsync(TId id, HasMa resource }); - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Service - Create resource"); @@ -339,8 +339,8 @@ public virtual async Task AddToToManyRelationshipAsync(TId leftId, string relati rightResourceIds }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNullNorEmpty(relationshipName); + ArgumentGuard.NotNull(rightResourceIds); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Service - Add to to-many relationship"); @@ -446,7 +446,7 @@ private async Task GetForHasManyUpdateAsync(HasManyAttribute hasManyR resource }); - ArgumentGuard.NotNull(resource, nameof(resource)); + ArgumentGuard.NotNull(resource); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Service - Update resource"); @@ -490,7 +490,7 @@ public virtual async Task SetRelationshipAsync(TId leftId, string relationshipNa rightValue }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); + ArgumentGuard.NotNullNorEmpty(relationshipName); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Service - Set relationship"); @@ -562,8 +562,8 @@ public virtual async Task RemoveFromToManyRelationshipAsync(TId leftId, string r rightResourceIds }); - ArgumentGuard.NotNullNorEmpty(relationshipName, nameof(relationshipName)); - ArgumentGuard.NotNull(rightResourceIds, nameof(rightResourceIds)); + ArgumentGuard.NotNullNorEmpty(relationshipName); + ArgumentGuard.NotNull(rightResourceIds); using IDisposable _ = CodeTimingSessionManager.Current.Measure("Repository - Remove from to-many relationship"); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs index fdf91e9744..41aa048ecb 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs @@ -15,7 +15,7 @@ public sealed class MusicTrackReleaseDefinition : JsonApiResourceDefinition : JsonApiRes protected HitCountingResourceDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) : base(resourceGraph) { - ArgumentGuard.NotNull(hitCounter, nameof(hitCounter)); + ArgumentGuard.NotNull(hitCounter); _hitCounter = hitCounter; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs index 6516100641..faa496aef9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs @@ -16,7 +16,7 @@ public sealed class InjectionDbContext : DbContext public InjectionDbContext(DbContextOptions options, ISystemClock systemClock) : base(options) { - ArgumentGuard.NotNull(systemClock, nameof(systemClock)); + ArgumentGuard.NotNull(systemClock); SystemClock = systemClock; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionFakers.cs index 6aea10c7ea..ed1a69569c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionFakers.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionFakers.cs @@ -20,7 +20,7 @@ internal sealed class InjectionFakers : FakerContainer public InjectionFakers(IServiceProvider serviceProvider) { - ArgumentGuard.NotNull(serviceProvider, nameof(serviceProvider)); + ArgumentGuard.NotNull(serviceProvider); _serviceProvider = serviceProvider; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceTypeCapturingDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceTypeCapturingDefinition.cs index da32a75e22..3b92b278da 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceTypeCapturingDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceTypeCapturingDefinition.cs @@ -20,8 +20,8 @@ public sealed class ResourceTypeCapturingDefinition : JsonApiRes public ResourceTypeCapturingDefinition(IResourceGraph resourceGraph, IJsonApiRequest request, ResourceTypeCaptureStore captureStore) : base(resourceGraph) { - ArgumentGuard.NotNull(request, nameof(request)); - ArgumentGuard.NotNull(captureStore, nameof(captureStore)); + ArgumentGuard.NotNull(request); + ArgumentGuard.NotNull(captureStore); _request = request; _captureStore = captureStore; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/WheelSortDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/WheelSortDefinition.cs index e5ae558489..e66e05bc64 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/WheelSortDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/WheelSortDefinition.cs @@ -19,7 +19,7 @@ public sealed class WheelSortDefinition : JsonApiResourceDefinition public WheelSortDefinition(IResourceGraph resourceGraph, IHttpContextAccessor httpContextAccessor) : base(resourceGraph) { - ArgumentGuard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); + ArgumentGuard.NotNull(httpContextAccessor); _httpContextAccessor = httpContextAccessor; } diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs index 785c2ebd5c..d7d6484d2a 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs @@ -94,7 +94,7 @@ private sealed class SomeSingletonService // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local public SomeSingletonService(SomeScopedService scopedService) { - ArgumentGuard.NotNull(scopedService, nameof(scopedService)); + ArgumentGuard.NotNull(scopedService); } } @@ -109,7 +109,7 @@ private sealed class CircularServiceA // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local public CircularServiceA(CircularServiceB serviceB) { - ArgumentGuard.NotNull(serviceB, nameof(serviceB)); + ArgumentGuard.NotNull(serviceB); } } @@ -119,7 +119,7 @@ private sealed class CircularServiceB // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local public CircularServiceB(CircularServiceA serviceA) { - ArgumentGuard.NotNull(serviceA, nameof(serviceA)); + ArgumentGuard.NotNull(serviceA); } } diff --git a/test/TestBuildingBlocks/ServiceCollectionExtensions.cs b/test/TestBuildingBlocks/ServiceCollectionExtensions.cs index 51430c92f8..2a11397f69 100644 --- a/test/TestBuildingBlocks/ServiceCollectionExtensions.cs +++ b/test/TestBuildingBlocks/ServiceCollectionExtensions.cs @@ -10,7 +10,7 @@ internal static class ServiceCollectionExtensions { public static void ReplaceControllers(this IServiceCollection services, TestControllerProvider provider) { - ArgumentGuard.NotNull(services, nameof(services)); + ArgumentGuard.NotNull(services); services.AddMvcCore().ConfigureApplicationPartManager(manager => { diff --git a/test/UnitTests/Models/ResourceConstructionExpressionTests.cs b/test/UnitTests/Models/ResourceConstructionExpressionTests.cs index d4ab3dc841..aca0d91db1 100644 --- a/test/UnitTests/Models/ResourceConstructionExpressionTests.cs +++ b/test/UnitTests/Models/ResourceConstructionExpressionTests.cs @@ -52,7 +52,7 @@ private sealed class ResourceWithStringConstructor : Identifiable public ResourceWithStringConstructor(string text) { - ArgumentGuard.NotNullNorEmpty(text, nameof(text)); + ArgumentGuard.NotNullNorEmpty(text); Text = text; } From a5217e6f010baede700cbf5f29f81e2c3f9e89f0 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 02:14:14 +0200 Subject: [PATCH 30/50] Remove end_of_line from .editorconfig to avoid dialog in Visual Studio (git handles this automatically if omitted) --- .editorconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index b6d9a8990c..999c890fb1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,6 @@ indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true -end_of_line = lf insert_final_newline = true [*.{csproj,json}] From 4b683087c3ffb2640dabf8a4a3b61b3f04653994 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 02:53:45 +0200 Subject: [PATCH 31/50] Update .editorconfig to better align with Resharper settings --- .editorconfig | 61 ++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/.editorconfig b/.editorconfig index 999c890fb1..ca191cf90e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,14 +8,18 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.{csproj,json}] +[*.{config,csproj,css,js,json,props,ruleset,xslt}] indent_size = 2 [*.{cs}] -#### .NET Coding Conventions #### +#### C#/.NET Coding Conventions #### -# Organize usings +# 'using' directive preferences dotnet_sort_system_directives_first = true +csharp_using_directive_placement = outside_namespace:suggestion + +# Namespace declarations +csharp_style_namespace_declarations = file_scoped:suggestion # this. preferences dotnet_style_qualification_for_field = false:suggestion @@ -29,6 +33,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion # Modifier preferences dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion csharp_style_pattern_local_over_anonymous_function = false:silent # Expression-level preferences @@ -36,6 +41,7 @@ dotnet_style_operator_placement_when_wrapping = end_of_line dotnet_style_prefer_auto_properties = true:suggestion dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_return = true:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion # Parameter preferences dotnet_code_quality_unused_parameters = non_public:suggestion @@ -53,38 +59,38 @@ csharp_style_expression_bodied_properties = true:suggestion # Code-block preferences csharp_prefer_braces = true:suggestion -# Expression-level preferences -csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:suggestion - - -#### C# Formatting Rules #### - # Indentation preferences csharp_indent_case_contents_when_block = false # Wrapping preferences csharp_preserve_single_line_statements = false +# 'var' usage preferences +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = false:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion -#### Naming styles #### +#### Naming Style #### dotnet_diagnostic.IDE1006.severity = warning # Naming rules -dotnet_naming_rule.private_const_fields_should_be_pascal_case.symbols = private_const_fields -dotnet_naming_rule.private_const_fields_should_be_pascal_case.style = pascal_case -dotnet_naming_rule.private_const_fields_should_be_pascal_case.severity = warning +dotnet_naming_rule.const_fields_should_be_pascal_case.symbols = const_fields +dotnet_naming_rule.const_fields_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.const_fields_should_be_pascal_case.severity = warning dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.symbols = private_static_readonly_fields dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.style = pascal_case dotnet_naming_rule.private_static_readonly_fields_should_be_pascal_case.severity = warning -dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.symbols = private_static_or_readonly_fields -dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.style = camel_case_prefix_with_underscore -dotnet_naming_rule.private_static_or_readonly_fields_should_start_with_underscore.severity = warning +dotnet_naming_rule.private_fields_should_start_with_underscore.symbols = private_fields +dotnet_naming_rule.private_fields_should_start_with_underscore.style = camel_case_prefix_with_underscore +dotnet_naming_rule.private_fields_should_start_with_underscore.severity = warning dotnet_naming_rule.locals_and_parameters_should_be_camel_case.symbols = locals_and_parameters dotnet_naming_rule.locals_and_parameters_should_be_camel_case.style = camel_case @@ -95,25 +101,24 @@ dotnet_naming_rule.types_and_members_should_be_pascal_case.style = pascal_case dotnet_naming_rule.types_and_members_should_be_pascal_case.severity = warning # Symbol specifications -dotnet_naming_symbols.private_const_fields.applicable_kinds = field -dotnet_naming_symbols.private_const_fields.applicable_accessibilities = private -dotnet_naming_symbols.private_const_fields.required_modifiers = const +dotnet_naming_symbols.const_fields.applicable_kinds = field +dotnet_naming_symbols.const_fields.applicable_accessibilities = * +dotnet_naming_symbols.const_fields.required_modifiers = const dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private -dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly -dotnet_naming_symbols.private_static_or_readonly_fields.applicable_kinds = field -dotnet_naming_symbols.private_static_or_readonly_fields.applicable_accessibilities = private -dotnet_naming_symbols.private_static_or_readonly_fields.required_modifiers = static readonly +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private -dotnet_naming_symbols.locals_and_parameters.applicable_kinds = local,parameter +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = local, parameter dotnet_naming_symbols.locals_and_parameters.applicable_accessibilities = * dotnet_naming_symbols.types_and_members.applicable_kinds = * dotnet_naming_symbols.types_and_members.applicable_accessibilities = * -# Naming styles +# Style specifications dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.camel_case_prefix_with_underscore.required_prefix = _ From 2aae8446f4af51da8ed27c7545ef16a5ee37ba16 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 24 Sep 2022 04:16:27 +0200 Subject: [PATCH 32/50] Include ruleset in solution, so it gets reformatted --- JsonApiDotNetCore.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln index 21b1cca7ce..043377403f 100644 --- a/JsonApiDotNetCore.sln +++ b/JsonApiDotNetCore.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore + CodingGuidelines.ruleset = CodingGuidelines.ruleset CSharpGuidelinesAnalyzer.config = CSharpGuidelinesAnalyzer.config Directory.Build.props = Directory.Build.props EndProjectSection From 8008e5e914e47492f180bea38be5351b217311c4 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 28 Sep 2022 04:07:59 +0200 Subject: [PATCH 33/50] Refresh .gitignore from source, preserving our additions --- .gitignore | 58 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 2bd200a72d..5fad128e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.rsuser @@ -90,6 +90,7 @@ StyleCopReport.xml *.tmp_proj *_wpftmp.csproj *.log +*.tlog *.vspscc *.vssscc .builds @@ -97,9 +98,6 @@ StyleCopReport.xml *.svclog *.scc -# MacOS file systems -**/.DS_STORE - # Chutzpah Test files _Chutzpah* @@ -134,9 +132,6 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JetBrains Rider -.idea/ - # TeamCity is a build add-in _TeamCity* @@ -148,7 +143,9 @@ _TeamCity* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool -coverage*[.json, .xml, .info] +coverage*.json +coverage*.xml +coverage*.info # Visual Studio code coverage results *.coverage @@ -297,6 +294,17 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -353,6 +361,9 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ +# Visual Studio History (VSHistory) files +.vshistory/ + # BeatPulse healthcheck temp database healthchecksdb @@ -365,5 +376,36 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +############################################# +### Additions specific to this repository ### +############################################# + +# MacOS file systems +**/.DS_STORE + # Sqlite example databases *.db + +# JetBrains Rider +.idea/ From 292fb45c160327009028df84cb730656a7612d7b Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 28 Sep 2022 04:21:42 +0200 Subject: [PATCH 34/50] Add Rider-specific formatter settings --- .gitignore | 18 +++++++++++++++-- .../.idea.JsonApiDotNetCore/.idea/.gitignore | 1 + .../.idea/codeStyles/Project.xml | 20 +++++++++++++++++++ .../.idea/codeStyles/codeStyleConfig.xml | 5 +++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 .idea/.idea.JsonApiDotNetCore/.idea/.gitignore create mode 100644 .idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml create mode 100644 .idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml diff --git a/.gitignore b/.gitignore index 5fad128e2d..85bd0f1080 100644 --- a/.gitignore +++ b/.gitignore @@ -407,5 +407,19 @@ FodyWeavers.xsd # Sqlite example databases *.db -# JetBrains Rider -.idea/ +# JetBrains IDEs Rider/IntelliJ (based on https://intellij-support.jetbrains.com/hc/en-us/articles/206544839) +**/.idea/**/*.xml +**/.idea/**/*.iml +**/.idea/**/*.ids +**/.idea/**/*.ipr +**/.idea/**/*.iws +**/.idea/**/*.name +**/.idea/**/*.properties +**/.idea/**/*.ser +**/.idea/**/shelf/ +**/.idea/**/dictionaries/ +**/.idea/**/libraries/ +**/.idea/**/artifacts/ +**/.idea/**/httpRequests/ +**/.idea/**/dataSources/ +!**/.idea/**/codeStyles/* diff --git a/.idea/.idea.JsonApiDotNetCore/.idea/.gitignore b/.idea/.idea.JsonApiDotNetCore/.idea/.gitignore new file mode 100644 index 0000000000..3933e947a2 --- /dev/null +++ b/.idea/.idea.JsonApiDotNetCore/.idea/.gitignore @@ -0,0 +1 @@ +# Empty .gitignore file to prevent Rider from adding one diff --git a/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..902b9f865f --- /dev/null +++ b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/Project.xml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000..405cd65360 --- /dev/null +++ b/.idea/.idea.JsonApiDotNetCore/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + From 6c11baed406f07e213ffafc0d57a7eeb1ef78a01 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 30 Sep 2022 18:33:15 +0200 Subject: [PATCH 35/50] Add missing CDATA sections to code fragments --- .../Resources/Annotations/AttrAttribute.cs | 4 ++-- .../TypeWithAttributeSyntaxReceiver.cs | 3 +-- .../Configuration/IJsonApiOptions.cs | 12 +++++++----- src/JsonApiDotNetCore/Configuration/TypeLocator.cs | 4 ++-- .../JsonConverters/WriteOnlyDocumentConverter.cs | 2 +- .../WriteOnlyRelationshipObjectConverter.cs | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs index d3d6133f6e..448d6e8ab2 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs @@ -18,13 +18,13 @@ public sealed class AttrAttribute : ResourceFieldAttribute /// is used. /// /// - /// + /// + /// ]]> /// public AttrCapabilities Capabilities { diff --git a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs index b23de19cc9..ad7b0d6ad5 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/TypeWithAttributeSyntaxReceiver.cs @@ -22,8 +22,7 @@ namespace JsonApiDotNetCore.SourceGenerators; /// /// [AlternateTypeName] /// public class ExampleResource4 : Identifiable { } -/// ]]> -/// +/// ]]> /// internal sealed class TypeWithAttributeSyntaxReceiver : ISyntaxReceiver { diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 597d22294d..3c32594ea8 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -14,7 +14,9 @@ public interface IJsonApiOptions /// The URL prefix to use for exposed endpoints. /// /// - /// options.Namespace = "api/v1"; + /// /// string? Namespace { get; } @@ -42,10 +44,10 @@ public interface IJsonApiOptions /// Use relative links for all resources. False by default. /// /// - /// + /// - /// + /// ]]> + /// + /// ]]> /// bool UseRelativeLinks { get; } diff --git a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs index e4b1da8d01..981c50c4e4 100644 --- a/src/JsonApiDotNetCore/Configuration/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Configuration/TypeLocator.cs @@ -147,9 +147,9 @@ public IReadOnlyCollection GetDerivedTypesForUnboundType(Assembly assembly /// The inherited type. /// /// - /// + /// + /// ]]> /// public IEnumerable GetDerivedTypes(Assembly assembly, Type baseType) { diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs index 623857f5ff..a48b8bd4be 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyDocumentConverter.cs @@ -25,7 +25,7 @@ public override Document Read(ref Utf8JsonReader reader, Type typeToConvert, Jso } /// - /// Conditionally writes "data": null or omits it, depending on . + /// Conditionally writes or omits it, depending on . /// public override void Write(Utf8JsonWriter writer, Document value, JsonSerializerOptions options) { diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyRelationshipObjectConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyRelationshipObjectConverter.cs index 047e0737c5..b740642868 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyRelationshipObjectConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/WriteOnlyRelationshipObjectConverter.cs @@ -20,7 +20,7 @@ public override RelationshipObject Read(ref Utf8JsonReader reader, Type typeToCo } /// - /// Conditionally writes "data": null or omits it, depending on . + /// Conditionally writes or omits it, depending on . /// public override void Write(Utf8JsonWriter writer, RelationshipObject value, JsonSerializerOptions options) { From 78b1f511a35d2ed4b28c8483a38fa128f671db77 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 1 Oct 2022 16:18:29 +0200 Subject: [PATCH 36/50] Add capabilities for relationships --- .../extensibility/resource-definitions.md | 4 +- docs/usage/resources/attributes.md | 43 ++++--- docs/usage/resources/relationships.md | 106 +++++++++++++++++- .../Models/TodoItem.cs | 4 +- .../Resources/Annotations/AttrAttribute.cs | 7 +- .../Annotations/AttrCapabilities.shared.cs | 19 ++-- .../Resources/Annotations/HasManyAttribute.cs | 45 ++++++++ .../HasManyAttribute.netstandard.cs | 2 + .../Annotations/HasManyCapabilities.shared.cs | 51 +++++++++ .../Resources/Annotations/HasOneAttribute.cs | 45 ++++++++ .../HasOneAttribute.netstandard.cs | 2 + .../Annotations/HasOneCapabilities.shared.cs | 35 ++++++ .../Annotations/RelationshipAttribute.cs | 27 +++-- .../RelationshipAttribute.netstandard.cs | 1 + .../Annotations/ResourceFieldAttribute.cs | 2 +- .../Configuration/IJsonApiOptions.cs | 12 +- .../Configuration/JsonApiOptions.cs | 6 + .../Configuration/ResourceGraphBuilder.cs | 47 ++++++++ .../Queries/Internal/Parsing/IncludeParser.cs | 4 +- .../Queries/Internal/SparseFieldSetCache.cs | 6 +- .../FilterQueryStringParameterReader.cs | 8 +- ...parseFieldSetQueryStringParameterReader.cs | 8 +- .../JsonApiQueryStringParameters.cs | 8 +- .../Annotations/CapabilitiesExtensions.cs | 45 ++++++++ .../Adapters/AtomicReferenceAdapter.cs | 1 + ...tInResourceOrRelationshipRequestAdapter.cs | 1 + .../Adapters/ResourceIdentityAdapter.cs | 49 ++++++++ .../Request/Adapters/ResourceObjectAdapter.cs | 9 +- .../Response/ResponseModelAdapter.cs | 7 ++ test/AnnotationTests/Models/TreeNode.cs | 4 +- .../Creating/AtomicCreateResourceTests.cs | 84 +++++++------- ...eateResourceWithToManyRelationshipTests.cs | 52 +++++++++ ...reateResourceWithToOneRelationshipTests.cs | 49 ++++++++ .../AtomicOperations/Lyric.cs | 2 +- .../AtomicOperations/MusicTrack.cs | 2 +- .../AtomicAddToToManyRelationshipTests.cs | 53 +++++++++ ...AtomicRemoveFromToManyRelationshipTests.cs | 53 +++++++++ .../AtomicReplaceToManyRelationshipTests.cs | 53 +++++++++ .../AtomicUpdateToOneRelationshipTests.cs | 53 +++++++++ .../AtomicReplaceToManyRelationshipTests.cs | 61 ++++++++++ .../Resources/AtomicUpdateResourceTests.cs | 102 ++++++++--------- .../AtomicUpdateToOneRelationshipTests.cs | 58 ++++++++++ .../IntegrationTests/QueryStrings/BlogPost.cs | 2 + .../IntegrationTests/QueryStrings/Calendar.cs | 5 +- .../QueryStrings/Filtering/FilterTests.cs | 23 ++++ .../QueryStrings/Includes/IncludeTests.cs | 84 +++++++++++++- .../SerializerIgnoreConditionTests.cs | 10 +- .../SparseFieldSets/SparseFieldSetTests.cs | 49 ++++++++ .../QueryStrings/WebAccount.cs | 4 +- .../ReadWrite/Creating/CreateResourceTests.cs | 70 ++++++------ ...eateResourceWithToManyRelationshipTests.cs | 46 ++++++++ ...reateResourceWithToOneRelationshipTests.cs | 42 +++++++ .../AddToToManyRelationshipTests.cs | 43 +++++++ .../RemoveFromToManyRelationshipTests.cs | 43 +++++++ .../ReplaceToManyRelationshipTests.cs | 43 +++++++ .../UpdateToOneRelationshipTests.cs | 39 +++++++ .../ReplaceToManyRelationshipTests.cs | 55 +++++++++ .../Updating/Resources/UpdateResourceTests.cs | 88 +++++++-------- .../Resources/UpdateToOneRelationshipTests.cs | 51 +++++++++ .../IntegrationTests/ReadWrite/WorkItem.cs | 4 +- .../ReadWrite/WorkItemGroup.cs | 2 +- 61 files changed, 1682 insertions(+), 251 deletions(-) create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs create mode 100644 src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs create mode 100644 src/JsonApiDotNetCore/Resources/Annotations/CapabilitiesExtensions.cs diff --git a/docs/usage/extensibility/resource-definitions.md b/docs/usage/extensibility/resource-definitions.md index af4f8a27c5..6bc16b869e 100644 --- a/docs/usage/extensibility/resource-definitions.md +++ b/docs/usage/extensibility/resource-definitions.md @@ -34,10 +34,10 @@ from Entity Framework Core `IQueryable` execution. ### Excluding fields -There are some cases where you want attributes (or relationships) conditionally excluded from your resource response. +There are some cases where you want attributes or relationships conditionally excluded from your resource response. For example, you may accept some sensitive data that should only be exposed to administrators after creation. -**Note:** to exclude attributes unconditionally, use `[Attr(Capabilities = ~AttrCapabilities.AllowView)]` on a resource class property. +**Note:** to exclude fields unconditionally, [attribute capabilities](~/usage/resources/attributes.md#capabilities) and [relationship capabilities](~/usage/resources/relationships.md#capabilities) can be used instead. ```c# public class UserDefinition : JsonApiResourceDefinition diff --git a/docs/usage/resources/attributes.md b/docs/usage/resources/attributes.md index 669dba0892..77c6ff9566 100644 --- a/docs/usage/resources/attributes.md +++ b/docs/usage/resources/attributes.md @@ -43,9 +43,10 @@ options.DefaultAttrCapabilities = AttrCapabilities.None; // default: All This can be overridden per attribute. -### Viewability +### AllowView -Attributes can be marked to allow returning their value in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response. +Indicates whether the attribute value can be returned in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response. +Otherwise, the attribute is silently omitted. ```c# #nullable enable @@ -57,45 +58,59 @@ public class User : Identifiable } ``` -### Creatability +### AllowFilter -Attributes can be marked as creatable, which will allow `POST` requests to assign a value to them. When sent but not allowed, an HTTP 422 response is returned. +Indicates whether the attribute can be filtered on. When not allowed and used in `?filter=`, an HTTP 400 is returned. ```c# #nullable enable public class Person : Identifiable { - [Attr(Capabilities = AttrCapabilities.AllowCreate)] - public string? CreatorName { get; set; } + [Attr(Capabilities = AttrCapabilities.AllowFilter)] + public string? FirstName { get; set; } } ``` -### Changeability +### AllowSort -Attributes can be marked as changeable, which will allow `PATCH` requests to update them. When sent but not allowed, an HTTP 422 response is returned. +Indicates whether the attribute can be sorted on. When not allowed and used in `?sort=`, an HTTP 400 is returned. ```c# #nullable enable public class Person : Identifiable { - [Attr(Capabilities = AttrCapabilities.AllowChange)] - public string? FirstName { get; set; }; + [Attr(Capabilities = ~AttrCapabilities.AllowSort)] + public string? FirstName { get; set; } } ``` -### Filter/Sort-ability +### AllowCreate -Attributes can be marked to allow filtering and/or sorting. When not allowed, it results in an HTTP 400 response. +Indicates whether POST requests can assign the attribute value. When sent but not allowed, an HTTP 422 response is returned. ```c# #nullable enable public class Person : Identifiable { - [Attr(Capabilities = AttrCapabilities.AllowSort | AttrCapabilities.AllowFilter)] - public string? FirstName { get; set; } + [Attr(Capabilities = AttrCapabilities.AllowCreate)] + public string? CreatorName { get; set; } +} +``` + +### AllowChange + +Indicates whether PATCH requests can update the attribute value. When sent but not allowed, an HTTP 422 response is returned. + +```c# +#nullable enable + +public class Person : Identifiable +{ + [Attr(Capabilities = AttrCapabilities.AllowChange)] + public string? FirstName { get; set; }; } ``` diff --git a/docs/usage/resources/relationships.md b/docs/usage/resources/relationships.md index 8776041e98..14ff2eb7f5 100644 --- a/docs/usage/resources/relationships.md +++ b/docs/usage/resources/relationships.md @@ -160,7 +160,111 @@ public class TodoItem : Identifiable } ``` -## Includibility +## Capabilities + +_since v5.1_ + +Default JSON:API relationship capabilities are specified in +@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasOneCapabilities and +@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasManyCapabilities: + +```c# +options.DefaultHasOneCapabilities = HasOneCapabilities.None; // default: All +options.DefaultHasManyCapabilities = HasManyCapabilities.None; // default: All +``` + +This can be overridden per relationship. + +### AllowView + +Indicates whether the relationship can be returned in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response. +Otherwise, the relationship (and its related resources, when included) are silently omitted. + +Note this setting does not affect retrieving the related resources directly. + +```c# +#nullable enable + +public class User : Identifiable +{ + [HasOne(Capabilities = ~HasOneCapabilities.AllowView)] + public LoginAccount Account { get; set; } = null!; +} +``` + +### AllowInclude + +Indicates whether the relationship can be included. When not allowed and used in `?include=`, an HTTP 400 is returned. + +```c# +#nullable enable + +public class User : Identifiable +{ + [HasMany(Capabilities = ~HasManyCapabilities.AllowInclude)] + public ISet Groups { get; set; } = new HashSet(); +} +``` + +### AllowFilter + +For to-many relationships only. Indicates whether it can be used in the `count()` and `has()` filter functions. When not allowed and used in `?filter=`, an HTTP 400 is returned. + +```c# +#nullable enable + +public class User : Identifiable +{ + [HasMany(Capabilities = HasManyCapabilities.AllowFilter)] + public ISet Groups { get; set; } = new HashSet(); +} +``` + +### AllowSet + +Indicates whether POST and PATCH requests can replace the relationship. When sent but not allowed, an HTTP 422 response is returned. + +```c# +#nullable enable + +public class User : Identifiable +{ + [HasOne(Capabilities = ~HasOneCapabilities.AllowSet)] + public LoginAccount Account { get; set; } = null!; +} +``` + +### AllowAdd + +For to-many relationships only. Indicates whether POST requests can add resources to the relationship. When sent but not allowed, an HTTP 422 response is returned. + +```c# +#nullable enable + +public class User : Identifiable +{ + [HasMany(Capabilities = ~HasManyCapabilities.AllowAdd)] + public ISet Groups { get; set; } = new HashSet(); +} +``` + +### AllowRemove + +For to-many relationships only. Indicates whether DELETE requests can remove resources from the relationship. When sent but not allowed, an HTTP 422 response is returned. + +```c# +#nullable enable + +public class User : Identifiable +{ + [HasMany(Capabilities = ~HasManyCapabilities.AllowRemove)] + public ISet Groups { get; set; } = new HashSet(); +} +``` + +## CanInclude + +_obsolete since v5.1_ Relationships can be marked to disallow including them using the `?include=` query string parameter. When not allowed, it results in an HTTP 400 response. diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs index 9be7e6e64e..5fe508f7f2 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/TodoItem.cs @@ -25,9 +25,9 @@ public sealed class TodoItem : Identifiable [HasOne] public Person Owner { get; set; } = null!; - [HasOne] + [HasOne(Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowSet)] public Person? Assignee { get; set; } - [HasMany] + [HasMany(Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowFilter)] public ISet Tags { get; set; } = new HashSet(); } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs index 448d6e8ab2..7a6cbd960f 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs @@ -14,15 +14,14 @@ public sealed class AttrAttribute : ResourceFieldAttribute internal bool HasExplicitCapabilities => _capabilities != null; /// - /// The set of capabilities that are allowed to be performed on this attribute. When not explicitly assigned, the configured default set of capabilities - /// is used. + /// The set of allowed capabilities on this attribute. When not explicitly set, the configured default set of capabilities is used. /// /// /// /// { /// [Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort)] - /// public string Name { get; set; } + /// public string Name { get; set; } = null!; /// } /// ]]> /// diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs index 2812be6d39..0951010b3b 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCore.Resources.Annotations; /// -/// Indicates capabilities that can be performed on an . +/// Indicates what can be performed on an . /// [PublicAPI] [Flags] @@ -12,29 +12,32 @@ public enum AttrCapabilities None = 0, /// - /// Whether or not GET requests can retrieve the attribute. Attempts to retrieve when disabled will return an HTTP 400 response. + /// Whether or not the attribute value can be returned in responses. Attempts to explicitly request it via the fields query string parameter when + /// disabled will return an HTTP 400 response. Otherwise, the attribute is silently omitted. /// AllowView = 1, /// /// Whether or not POST requests can assign the attribute value. Attempts to assign when disabled will return an HTTP 422 response. /// - AllowCreate = 2, + AllowCreate = 1 << 1, /// /// Whether or not PATCH requests can update the attribute value. Attempts to update when disabled will return an HTTP 422 response. /// - AllowChange = 4, + AllowChange = 1 << 2, /// - /// Whether or not an attribute can be filtered on via a query string parameter. Attempts to filter when disabled will return an HTTP 400 response. + /// Whether or not the attribute can be filtered on. Attempts to use it in the filter query string parameter when disabled will return an HTTP 400 + /// response. /// - AllowFilter = 8, + AllowFilter = 1 << 3, /// - /// Whether or not an attribute can be sorted on via a query string parameter. Attempts to sort when disabled will return an HTTP 400 response. + /// Whether or not the attribute can be sorted on. Attempts to use it in the sort query string parameter when disabled will return an HTTP 400 + /// response. /// - AllowSort = 16, + AllowSort = 1 << 4, All = AllowView | AllowCreate | AllowChange | AllowFilter | AllowSort } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs index 39bcf34b3f..5792744d5c 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.cs @@ -1,5 +1,7 @@ using JetBrains.Annotations; +// ReSharper disable NonReadonlyMemberInGetHashCode + namespace JsonApiDotNetCore.Resources.Annotations; /// @@ -20,12 +22,33 @@ namespace JsonApiDotNetCore.Resources.Annotations; public sealed class HasManyAttribute : RelationshipAttribute { private readonly Lazy _lazyIsManyToMany; + private HasManyCapabilities? _capabilities; /// /// Inspects to determine if this is a many-to-many relationship. /// internal bool IsManyToMany => _lazyIsManyToMany.Value; + internal bool HasExplicitCapabilities => _capabilities != null; + + /// + /// The set of allowed capabilities on this to-many relationship. When not explicitly set, the configured default set of capabilities is used. + /// + /// + /// + /// { + /// [HasMany(Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowInclude)] + /// public ISet Chapters { get; set; } = new HashSet(); + /// } + /// ]]> + /// + public HasManyCapabilities Capabilities + { + get => _capabilities ?? default; + set => _capabilities = value; + } + public HasManyAttribute() { _lazyIsManyToMany = new Lazy(EvaluateIsManyToMany, LazyThreadSafetyMode.PublicationOnly); @@ -41,4 +64,26 @@ private bool EvaluateIsManyToMany() return false; } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is null || GetType() != obj.GetType()) + { + return false; + } + + var other = (HasManyAttribute)obj; + + return _capabilities == other._capabilities && base.Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_capabilities, base.GetHashCode()); + } } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs index 1cdeb9f62f..cf83f0ce17 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.netstandard.cs @@ -9,4 +9,6 @@ namespace JsonApiDotNetCore.Resources.Annotations; [AttributeUsage(AttributeTargets.Property)] public sealed class HasManyAttribute : RelationshipAttribute { + /// + public HasManyCapabilities Capabilities { get; set; } } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs new file mode 100644 index 0000000000..cf65951321 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyCapabilities.shared.cs @@ -0,0 +1,51 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// Indicates what can be performed on a . +/// +[PublicAPI] +[Flags] +public enum HasManyCapabilities +{ + None = 0, + + /// + /// Whether or not the relationship can be returned in responses. Attempts to explicitly request it via the fields query string parameter when + /// disabled will return an HTTP 400 response. Otherwise, the relationship (and its related resources, when included) are silently omitted. + /// + /// + /// Note this setting does not affect retrieving the related resources directly. + /// + AllowView = 1, + + /// + /// Whether or not the relationship can be included. Attempts to use it in the include query string parameter when disabled will return an HTTP + /// 400 response. + /// + AllowInclude = 1 << 1, + + /// + /// Whether or not the to-many relationship can be used in the count() and has() functions as part of the filter query string + /// parameter. Attempts to use it when disabled will return an HTTP 400 response. + /// + AllowFilter = 1 << 2, + + /// + /// Whether or not POST and PATCH requests can replace the relationship. Attempts to replace when disabled will return an HTTP 422 response. + /// + AllowSet = 1 << 3, + + /// + /// Whether or not POST requests can add to the to-many relationship. Attempts to add when disabled will return an HTTP 422 response. + /// + AllowAdd = 1 << 4, + + /// + /// Whether or not DELETE requests can remove from the to-many relationship. Attempts to remove when disabled will return an HTTP 422 response. + /// + AllowRemove = 1 << 5, + + All = AllowView | AllowInclude | AllowFilter | AllowSet | AllowAdd | AllowRemove +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs index 0a68f702d3..c0416c92fb 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.cs @@ -1,5 +1,7 @@ using JetBrains.Annotations; +// ReSharper disable NonReadonlyMemberInGetHashCode + namespace JsonApiDotNetCore.Resources.Annotations; /// @@ -19,12 +21,33 @@ namespace JsonApiDotNetCore.Resources.Annotations; public sealed class HasOneAttribute : RelationshipAttribute { private readonly Lazy _lazyIsOneToOne; + private HasOneCapabilities? _capabilities; /// /// Inspects to determine if this is a one-to-one relationship. /// internal bool IsOneToOne => _lazyIsOneToOne.Value; + internal bool HasExplicitCapabilities => _capabilities != null; + + /// + /// The set of allowed capabilities on this to-one relationship. When not explicitly set, the configured default set of capabilities is used. + /// + /// + /// + /// { + /// [HasOne(Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowInclude)] + /// public Person? Author { get; set; } + /// } + /// ]]> + /// + public HasOneCapabilities Capabilities + { + get => _capabilities ?? default; + set => _capabilities = value; + } + public HasOneAttribute() { _lazyIsOneToOne = new Lazy(EvaluateIsOneToOne, LazyThreadSafetyMode.PublicationOnly); @@ -40,4 +63,26 @@ private bool EvaluateIsOneToOne() return false; } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is null || GetType() != obj.GetType()) + { + return false; + } + + var other = (HasOneAttribute)obj; + + return _capabilities == other._capabilities && base.Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_capabilities, base.GetHashCode()); + } } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs index 1c16fb01b2..42be2f3c5f 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.netstandard.cs @@ -9,4 +9,6 @@ namespace JsonApiDotNetCore.Resources.Annotations; [AttributeUsage(AttributeTargets.Property)] public sealed class HasOneAttribute : RelationshipAttribute { + /// + public HasOneCapabilities Capabilities { get; set; } } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs new file mode 100644 index 0000000000..a001e39407 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneCapabilities.shared.cs @@ -0,0 +1,35 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// Indicates what can be performed on a . +/// +[PublicAPI] +[Flags] +public enum HasOneCapabilities +{ + None = 0, + + /// + /// Whether or not the relationship can be returned in responses. Attempts to explicitly request it via the fields query string parameter when + /// disabled will return an HTTP 400 response. Otherwise, the relationship (and its related resources, when included) are silently omitted. + /// + /// + /// Note this setting does not affect retrieving the related resources directly. + /// + AllowView = 1, + + /// + /// Whether or not the relationship can be included. Attempts to use it in the include query string parameter when disabled will return an HTTP + /// 400 response. + /// + AllowInclude = 1 << 1, + + /// + /// Whether or not POST and PATCH requests can replace the relationship. Attempts to replace when disabled will return an HTTP 422 response. + /// + AllowSet = 1 << 2, + + All = AllowView | AllowInclude | AllowSet +} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs index c1b24e9567..dd94bab221 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs @@ -14,9 +14,13 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute { private protected static readonly CollectionConverter CollectionConverter = new(); - // These are definitely assigned after building the resource graph, which is why their public equivalents are declared as non-nullable. + // This field is definitely assigned after building the resource graph, which is why its public equivalent is declared as non-nullable. private ResourceType? _rightType; + private bool? _canInclude; + + internal bool HasExplicitCanInclude => _canInclude != null; + /// /// The of the Entity Framework Core inverse navigation, which may or may not exist. Even if it exists, it may not be exposed /// as a JSON:API relationship. @@ -69,13 +73,18 @@ internal set public LinkTypes Links { get; set; } = LinkTypes.NotConfigured; /// - /// Whether or not this relationship can be included using the - /// - /// ?include=publicName - /// - /// query string parameter. This is true by default. + /// Whether or not this relationship can be included using the include query string parameter. This is true by default. /// - public bool CanInclude { get; set; } = true; + /// + /// When explicitly set, this value takes precedence over Capabilities for backwards-compatibility. Capabilities are adjusted accordingly when building + /// the resource graph. + /// + [Obsolete("Use AllowInclude in Capabilities instead.")] + public bool CanInclude + { + get => _canInclude ?? true; + set => _canInclude = value; + } public override bool Equals(object? obj) { @@ -91,11 +100,11 @@ public override bool Equals(object? obj) var other = (RelationshipAttribute)obj; - return _rightType?.ClrType == other._rightType?.ClrType && Links == other.Links && CanInclude == other.CanInclude && base.Equals(other); + return _rightType?.ClrType == other._rightType?.ClrType && Links == other.Links && base.Equals(other); } public override int GetHashCode() { - return HashCode.Combine(_rightType?.ClrType, Links, CanInclude, base.GetHashCode()); + return HashCode.Combine(_rightType?.ClrType, Links, base.GetHashCode()); } } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs index 51517b3d51..d7af592564 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs @@ -12,5 +12,6 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute public LinkTypes Links { get; set; } = LinkTypes.NotConfigured; /// + [Obsolete("Use AllowInclude in Capabilities instead.")] public bool CanInclude { get; set; } = true; } diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs index 9f32610dc9..e8e1d17aca 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs @@ -19,7 +19,7 @@ public abstract class ResourceFieldAttribute : Attribute private ResourceType? _type; /// - /// The publicly exposed name of this JSON:API field. When not explicitly assigned, the configured naming convention is applied on the property name. + /// The publicly exposed name of this JSON:API field. When not explicitly set, the configured naming convention is applied on the property name. /// public string PublicName { diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 3c32594ea8..bc7d17d89f 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -21,10 +21,20 @@ public interface IJsonApiOptions string? Namespace { get; } /// - /// Specifies the default query string capabilities that can be used on exposed JSON:API attributes. Defaults to . + /// Specifies the default set of allowed capabilities on JSON:API attributes. Defaults to . /// AttrCapabilities DefaultAttrCapabilities { get; } + /// + /// Specifies the default set of allowed capabilities on JSON:API to-one relationships. Defaults to . + /// + HasOneCapabilities DefaultHasOneCapabilities { get; } + + /// + /// Specifies the default set of allowed capabilities on JSON:API to-many relationships. Defaults to . + /// + HasManyCapabilities DefaultHasManyCapabilities { get; } + /// /// Indicates whether responses should contain a jsonapi object that contains the highest JSON:API version supported. False by default. /// diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 46603260cd..778ded8d59 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -26,6 +26,12 @@ public sealed class JsonApiOptions : IJsonApiOptions /// public AttrCapabilities DefaultAttrCapabilities { get; set; } = AttrCapabilities.All; + /// + public HasOneCapabilities DefaultHasOneCapabilities { get; set; } = HasOneCapabilities.All; + + /// + public HasManyCapabilities DefaultHasManyCapabilities { get; set; } = HasManyCapabilities.All; + /// public bool IncludeJsonApiVersion { get; set; } diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index 2af0e63caf..2b6f19acd3 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -307,6 +307,7 @@ private IReadOnlyCollection GetRelationships(Type resourc { relationship.Property = property; SetPublicName(relationship, property); + SetRelationshipCapabilities(relationship); IncludeField(relationshipsByName, relationship); } @@ -321,6 +322,52 @@ private void SetPublicName(ResourceFieldAttribute field, PropertyInfo property) field.PublicName ??= FormatPropertyName(property); } + private void SetRelationshipCapabilities(RelationshipAttribute relationship) + { +#pragma warning disable CS0618 // Type or member is obsolete + bool canInclude = relationship.CanInclude; +#pragma warning restore CS0618 // Type or member is obsolete + + if (relationship is HasOneAttribute hasOneRelationship) + { + SetHasOneRelationshipCapabilities(hasOneRelationship, canInclude); + } + else if (relationship is HasManyAttribute hasManyRelationship) + { + SetHasManyRelationshipCapabilities(hasManyRelationship, canInclude); + } + } + + private void SetHasOneRelationshipCapabilities(HasOneAttribute hasOneRelationship, bool canInclude) + { + if (!hasOneRelationship.HasExplicitCapabilities) + { + hasOneRelationship.Capabilities = _options.DefaultHasOneCapabilities; + } + + if (hasOneRelationship.HasExplicitCanInclude) + { + hasOneRelationship.Capabilities = canInclude + ? hasOneRelationship.Capabilities | HasOneCapabilities.AllowInclude + : hasOneRelationship.Capabilities & ~HasOneCapabilities.AllowInclude; + } + } + + private void SetHasManyRelationshipCapabilities(HasManyAttribute hasManyRelationship, bool canInclude) + { + if (!hasManyRelationship.HasExplicitCapabilities) + { + hasManyRelationship.Capabilities = _options.DefaultHasManyCapabilities; + } + + if (hasManyRelationship.HasExplicitCanInclude) + { + hasManyRelationship.Capabilities = canInclude + ? hasManyRelationship.Capabilities | HasManyCapabilities.AllowInclude + : hasManyRelationship.Capabilities & ~HasManyCapabilities.AllowInclude; + } + } + private IReadOnlyCollection GetEagerLoads(Type resourceClrType, int recursionDepth = 0) { AssertNoInfiniteRecursion(recursionDepth); diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 7418be160f..38ee1ffa83 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -106,7 +106,7 @@ private ICollection LookupRelationshipName(string relationshipN { relationshipsFound.AddRange(relationships); - RelationshipAttribute[] relationshipsToInclude = relationships.Where(relationship => relationship.CanInclude).ToArray(); + RelationshipAttribute[] relationshipsToInclude = relationships.Where(relationship => !relationship.IsIncludeBlocked()).ToArray(); ICollection affectedChildren = parent.EnsureChildren(relationshipsToInclude); children.AddRange(affectedChildren); } @@ -139,7 +139,7 @@ private static void AssertRelationshipsFound(ISet relatio private static void AssertAtLeastOneCanBeIncluded(ISet relationshipsFound, string relationshipName, ICollection parents) { - if (relationshipsFound.All(relationship => !relationship.CanInclude)) + if (relationshipsFound.All(relationship => relationship.IsIncludeBlocked())) { string parentPath = parents.First().Path; ResourceType resourceType = relationshipsFound.First().LeftType; diff --git a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs index 495af2ebc1..ab1edf9f9e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs @@ -148,13 +148,11 @@ private static IImmutableSet GetViewableFields(ResourceT { ImmutableHashSet.Builder fieldSetBuilder = ImmutableHashSet.CreateBuilder(); - foreach (AttrAttribute attribute in resourceType.Attributes.Where(attr => attr.Capabilities.HasFlag(AttrCapabilities.AllowView))) + foreach (ResourceFieldAttribute field in resourceType.Fields.Where(nextField => !nextField.IsViewBlocked())) { - fieldSetBuilder.Add(attribute); + fieldSetBuilder.Add(field); } - fieldSetBuilder.AddRange(resourceType.Relationships); - return fieldSetBuilder.ToImmutable(); } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index 18167acbc0..60c7f6b75c 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -40,10 +40,12 @@ public FilterQueryStringParameterReader(IJsonApiRequest request, IResourceGraph protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path) { - if (field is AttrAttribute attribute && !attribute.Capabilities.HasFlag(AttrCapabilities.AllowFilter)) + if (field.IsFilterBlocked()) { - throw new InvalidQueryStringParameterException(_lastParameterName!, "Filtering on the requested attribute is not allowed.", - $"Filtering on attribute '{attribute.PublicName}' is not allowed."); + string kind = field is AttrAttribute ? "attribute" : "relationship"; + + throw new InvalidQueryStringParameterException(_lastParameterName!, $"Filtering on the requested {kind} is not allowed.", + $"Filtering on {kind} '{field.PublicName}' is not allowed."); } } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index dadf153f21..fb4f665873 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -36,10 +36,12 @@ public SparseFieldSetQueryStringParameterReader(IJsonApiRequest request, IResour protected void ValidateSingleField(ResourceFieldAttribute field, ResourceType resourceType, string path) { - if (field is AttrAttribute attribute && !attribute.Capabilities.HasFlag(AttrCapabilities.AllowView)) + if (field.IsViewBlocked()) { - throw new InvalidQueryStringParameterException(_lastParameterName!, "Retrieving the requested attribute is not allowed.", - $"Retrieving the attribute '{attribute.PublicName}' is not allowed."); + string kind = field is AttrAttribute ? "attribute" : "relationship"; + + throw new InvalidQueryStringParameterException(_lastParameterName!, $"Retrieving the requested {kind} is not allowed.", + $"Retrieving the {kind} '{field.PublicName}' is not allowed."); } } diff --git a/src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs b/src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs index c1f59ceab6..e7c418d8a7 100644 --- a/src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs +++ b/src/JsonApiDotNetCore/QueryStrings/JsonApiQueryStringParameters.cs @@ -10,9 +10,9 @@ public enum JsonApiQueryStringParameters { None = 0, Filter = 1, - Sort = 2, - Include = 4, - Page = 8, - Fields = 16, + Sort = 1 << 1, + Include = 1 << 2, + Page = 1 << 3, + Fields = 1 << 4, All = Filter | Sort | Include | Page | Fields } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/CapabilitiesExtensions.cs b/src/JsonApiDotNetCore/Resources/Annotations/CapabilitiesExtensions.cs new file mode 100644 index 0000000000..84352f2206 --- /dev/null +++ b/src/JsonApiDotNetCore/Resources/Annotations/CapabilitiesExtensions.cs @@ -0,0 +1,45 @@ +namespace JsonApiDotNetCore.Resources.Annotations; + +internal static class CapabilitiesExtensions +{ + public static bool IsViewBlocked(this ResourceFieldAttribute field) + { + return field switch + { + AttrAttribute attrAttribute => !attrAttribute.Capabilities.HasFlag(AttrCapabilities.AllowView), + HasOneAttribute hasOneRelationship => !hasOneRelationship.Capabilities.HasFlag(HasOneCapabilities.AllowView), + HasManyAttribute hasManyRelationship => !hasManyRelationship.Capabilities.HasFlag(HasManyCapabilities.AllowView), + _ => false + }; + } + + public static bool IsIncludeBlocked(this RelationshipAttribute relationship) + { + return relationship switch + { + HasOneAttribute hasOneRelationship => !hasOneRelationship.Capabilities.HasFlag(HasOneCapabilities.AllowInclude), + HasManyAttribute hasManyRelationship => !hasManyRelationship.Capabilities.HasFlag(HasManyCapabilities.AllowInclude), + _ => false + }; + } + + public static bool IsFilterBlocked(this ResourceFieldAttribute field) + { + return field switch + { + AttrAttribute attrAttribute => !attrAttribute.Capabilities.HasFlag(AttrCapabilities.AllowFilter), + HasManyAttribute hasManyRelationship => !hasManyRelationship.Capabilities.HasFlag(HasManyCapabilities.AllowFilter), + _ => false + }; + } + + public static bool IsSetBlocked(this RelationshipAttribute relationship) + { + return relationship switch + { + HasOneAttribute hasOneRelationship => !hasOneRelationship.Capabilities.HasFlag(HasOneCapabilities.AllowSet), + HasManyAttribute hasManyRelationship => !hasManyRelationship.Capabilities.HasFlag(HasManyCapabilities.AllowSet), + _ => false + }; + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs index fd2c274dab..f7a5ad82fa 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs @@ -39,6 +39,7 @@ private RelationshipAttribute ConvertRelationship(string relationshipName, Resou AssertIsKnownRelationship(relationship, relationshipName, resourceType, state); AssertToManyInAddOrRemoveRelationship(relationship, state); + AssertRelationshipChangeNotBlocked(relationship, state); return relationship; } diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs index f5d4cb088c..38a8ce0a29 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/DocumentInResourceOrRelationshipRequestAdapter.cs @@ -48,6 +48,7 @@ public DocumentInResourceOrRelationshipRequestAdapter(IJsonApiOptions options, I } ResourceIdentityAdapter.AssertToManyInAddOrRemoveRelationship(state.Request.Relationship, state); + ResourceIdentityAdapter.AssertRelationshipChangeNotBlocked(state.Request.Relationship, state); state.WritableTargetedFields.Relationships.Add(state.Request.Relationship); return _relationshipDataAdapter.Convert(document.Data, state.Request.Relationship, false, state); diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs index 9df5215da9..e4c0df21df 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs @@ -231,4 +231,53 @@ protected internal static void AssertToManyInAddOrRemoveRelationship(Relationshi HttpStatusCode.Forbidden); } } + + internal static void AssertRelationshipChangeNotBlocked(RelationshipAttribute relationship, RequestAdapterState state) + { + switch (state.Request.WriteOperation) + { + case WriteOperationKind.AddToRelationship: + { + AssertAddToRelationshipNotBlocked((HasManyAttribute)relationship, state); + break; + } + case WriteOperationKind.RemoveFromRelationship: + { + AssertRemoveFromRelationshipNotBlocked((HasManyAttribute)relationship, state); + break; + } + default: + { + AssertSetRelationshipNotBlocked(relationship, state); + break; + } + } + } + + private static void AssertSetRelationshipNotBlocked(RelationshipAttribute relationship, RequestAdapterState state) + { + if (relationship.IsSetBlocked()) + { + throw new ModelConversionException(state.Position, "Relationship cannot be assigned.", + $"The relationship '{relationship.PublicName}' on resource type '{relationship.LeftType.PublicName}' cannot be assigned to."); + } + } + + private static void AssertAddToRelationshipNotBlocked(HasManyAttribute relationship, RequestAdapterState state) + { + if (!relationship.Capabilities.HasFlag(HasManyCapabilities.AllowAdd)) + { + throw new ModelConversionException(state.Position, "Relationship cannot be added to.", + $"The relationship '{relationship.PublicName}' on resource type '{relationship.LeftType.PublicName}' cannot be added to."); + } + } + + private static void AssertRemoveFromRelationshipNotBlocked(HasManyAttribute relationship, RequestAdapterState state) + { + if (!relationship.Capabilities.HasFlag(HasManyCapabilities.AllowRemove)) + { + throw new ModelConversionException(state.Position, "Relationship cannot be removed from.", + $"The relationship '{relationship.PublicName}' on resource type '{relationship.LeftType.PublicName}' cannot be removed from."); + } + } } diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs index b489665a41..1b85b35336 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceObjectAdapter.cs @@ -63,8 +63,8 @@ private void ConvertAttribute(IIdentifiable resource, string attributeName, obje AssertIsKnownAttribute(attr, attributeName, resourceType, state); AssertNoInvalidAttribute(attributeValue, state); - AssertNoBlockedCreate(attr, resourceType, state); - AssertNoBlockedChange(attr, resourceType, state); + AssertSetAttributeInCreateResourceNotBlocked(attr, resourceType, state); + AssertSetAttributeInUpdateResourceNotBlocked(attr, resourceType, state); AssertNotReadOnly(attr, resourceType, state); attr.SetValue(resource, attributeValue); @@ -96,7 +96,7 @@ private static void AssertNoInvalidAttribute(object? attributeValue, RequestAdap } } - private static void AssertNoBlockedCreate(AttrAttribute attr, ResourceType resourceType, RequestAdapterState state) + private static void AssertSetAttributeInCreateResourceNotBlocked(AttrAttribute attr, ResourceType resourceType, RequestAdapterState state) { if (state.Request.WriteOperation == WriteOperationKind.CreateResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate)) { @@ -105,7 +105,7 @@ private static void AssertNoBlockedCreate(AttrAttribute attr, ResourceType resou } } - private static void AssertNoBlockedChange(AttrAttribute attr, ResourceType resourceType, RequestAdapterState state) + private static void AssertSetAttributeInUpdateResourceNotBlocked(AttrAttribute attr, ResourceType resourceType, RequestAdapterState state) { if (state.Request.WriteOperation == WriteOperationKind.UpdateResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange)) { @@ -148,6 +148,7 @@ private void ConvertRelationship(string relationshipName, RelationshipObject? re } AssertIsKnownRelationship(relationship, relationshipName, resourceType, state); + AssertRelationshipChangeNotBlocked(relationship, state); object? rightValue = _relationshipDataAdapter.Convert(relationshipObject.Data, relationship, true, state); diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs b/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs index 5dc97f8052..b1398b7cfb 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs @@ -287,6 +287,13 @@ private void TraverseRelationship(RelationshipAttribute relationship, IIdentifia ? leftTreeNode.ResourceType.GetRelationshipByPropertyName(relationship.Property.Name) : relationship; + if (effectiveRelationship.IsViewBlocked()) + { + // Hide related resources when blocked. According to JSON:API, breaking full linkage is only allowed + // when the client explicitly requested it by sending a sparse fieldset. + return; + } + object? rightValue = effectiveRelationship.GetValue(leftResource); IReadOnlyCollection rightResources = CollectionConverter.ExtractResources(rightValue); diff --git a/test/AnnotationTests/Models/TreeNode.cs b/test/AnnotationTests/Models/TreeNode.cs index 955db81720..9002773680 100644 --- a/test/AnnotationTests/Models/TreeNode.cs +++ b/test/AnnotationTests/Models/TreeNode.cs @@ -12,9 +12,9 @@ public sealed class TreeNode : Identifiable [Attr(PublicName = "name", Capabilities = AttrCapabilities.AllowSort)] public string? DisplayName { get; set; } - [HasOne(PublicName = "orders", CanInclude = true, Links = LinkTypes.All)] + [HasOne(PublicName = "orders", Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowInclude, Links = LinkTypes.All)] public TreeNode? Parent { get; set; } - [HasMany(PublicName = "orders", CanInclude = true, Links = LinkTypes.All)] + [HasMany(PublicName = "orders", Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowFilter, Links = LinkTypes.All)] public ISet Children { get; set; } = new HashSet(); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index ffae461fb0..048ef5506f 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -751,48 +751,6 @@ public async Task Cannot_create_resource_for_unknown_type() error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } - [Fact] - public async Task Cannot_create_resource_attribute_with_blocked_capability() - { - // Arrange - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "lyrics", - attributes = new - { - createdAt = 12.July(1980) - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.ShouldHaveCount(1); - - ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when creating resource."); - error.Detail.Should().Be("The attribute 'createdAt' on resource type 'lyrics' cannot be assigned to."); - error.Source.ShouldNotBeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/createdAt"); - error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); - } - [Fact] public async Task Cannot_create_resource_with_readonly_attribute() { @@ -990,4 +948,46 @@ await _testContext.RunOnDatabaseAsync(async dbContext => trackInDatabase.Performers[0].Id.Should().Be(existingPerformer.Id); }); } + + [Fact] + public async Task Cannot_assign_attribute_with_blocked_capability() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "lyrics", + attributes = new + { + createdAt = 12.July(1980) + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when creating resource."); + error.Detail.Should().Be("The attribute 'createdAt' on resource type 'lyrics' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/createdAt"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs index be9700c3d6..61a1db5164 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs @@ -684,4 +684,56 @@ public async Task Cannot_create_with_object_data_in_ManyToMany_relationship() error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/tracks/data"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "musicTracks", + relationships = new + { + occursIn = new + { + data = new[] + { + new + { + type = "playlists", + id = Unknown.StringId.For() + } + } + } + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'occursIn' on resource type 'musicTracks' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/occursIn"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs index e7ba0d5288..0112dc9ed4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs @@ -734,4 +734,53 @@ await _testContext.RunOnDatabaseAsync(async dbContext => trackInDatabase.OwnedBy.Id.Should().Be(existingCompany.Id); }); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + data = new + { + type = "lyrics", + relationships = new + { + language = new + { + data = new + { + type = "textLanguages", + id = Unknown.StringId.For() + } + } + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'language' on resource type 'lyrics' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/language"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Lyric.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Lyric.cs index 2baa9ac431..af1ac9e18b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Lyric.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Lyric.cs @@ -17,7 +17,7 @@ public sealed class Lyric : Identifiable [Attr(Capabilities = AttrCapabilities.None)] public DateTimeOffset CreatedAt { get; set; } - [HasOne] + [HasOne(Capabilities = HasOneCapabilities.All & ~HasOneCapabilities.AllowSet)] public TextLanguage? Language { get; set; } [HasOne] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/MusicTrack.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/MusicTrack.cs index 42bbbdfd3b..646a0d9ed9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/MusicTrack.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/MusicTrack.cs @@ -34,6 +34,6 @@ public sealed class MusicTrack : Identifiable [HasMany] public IList Performers { get; set; } = new List(); - [HasMany] + [HasMany(Capabilities = HasManyCapabilities.All & ~(HasManyCapabilities.AllowSet | HasManyCapabilities.AllowAdd | HasManyCapabilities.AllowRemove))] public IList OccursIn { get; set; } = new List(); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs index fc13524e8a..92f1bc638b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs @@ -1081,4 +1081,57 @@ await _testContext.RunOnDatabaseAsync(async dbContext => trackInDatabase.Performers[0].Id.Should().Be(existingTrack.Performers[0].Id); }); } + + [Fact] + public async Task Cannot_add_with_blocked_capability() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.MusicTracks.Add(existingTrack); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "add", + @ref = new + { + type = "musicTracks", + id = existingTrack.StringId, + relationship = "occursIn" + }, + data = new + { + type = "playlists", + id = Unknown.StringId.For() + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be added to."); + error.Detail.Should().Be("The relationship 'occursIn' on resource type 'musicTracks' cannot be added to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/ref/relationship"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs index d015cae3fd..bed9b62d99 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs @@ -1042,4 +1042,57 @@ await _testContext.RunOnDatabaseAsync(async dbContext => trackInDatabase.Performers[0].Id.Should().Be(existingTrack.Performers[0].Id); }); } + + [Fact] + public async Task Cannot_remove_with_blocked_capability() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.MusicTracks.Add(existingTrack); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "remove", + @ref = new + { + type = "musicTracks", + id = existingTrack.StringId, + relationship = "occursIn" + }, + data = new + { + type = "playlists", + id = Unknown.StringId.For() + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be removed from."); + error.Detail.Should().Be("The relationship 'occursIn' on resource type 'musicTracks' cannot be removed from."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/ref/relationship"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs index faf8a9cb8c..be0da69ad9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs @@ -1140,4 +1140,57 @@ await _testContext.RunOnDatabaseAsync(async dbContext => error.Source.Pointer.Should().Be("/atomic:operations[0]/data[0]/type"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.MusicTracks.Add(existingTrack); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "update", + @ref = new + { + type = "musicTracks", + id = existingTrack.StringId, + relationship = "occursIn" + }, + data = new + { + type = "playlists", + id = Unknown.StringId.For() + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'occursIn' on resource type 'musicTracks' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/ref/relationship"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs index 4b650a3d85..31b62a7c8e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs @@ -1305,4 +1305,57 @@ await _testContext.RunOnDatabaseAsync(async dbContext => error.Source.Pointer.Should().Be("/atomic:operations[0]/data/type"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + Lyric existingLyric = _fakers.Lyric.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Lyrics.Add(existingLyric); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "update", + @ref = new + { + type = "lyrics", + id = existingLyric.StringId, + relationship = "language" + }, + data = new + { + type = "textLanguages", + id = Unknown.StringId.For() + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'language' on resource type 'lyrics' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/ref/relationship"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs index fa801c67c1..0d8c5e1d80 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs @@ -799,4 +799,65 @@ await _testContext.RunOnDatabaseAsync(async dbContext => error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/performers/data[0]/type"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.MusicTracks.Add(existingTrack); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "update", + data = new + { + type = "musicTracks", + id = existingTrack.StringId, + relationships = new + { + occursIn = new + { + data = new[] + { + new + { + type = "playlists", + id = Unknown.StringId.For() + } + } + } + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'occursIn' on resource type 'musicTracks' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/occursIn"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs index 336b7d5621..eb9f81b6e6 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs @@ -1503,57 +1503,6 @@ public async Task Cannot_update_resource_for_incompatible_ID() error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } - [Fact] - public async Task Cannot_update_resource_attribute_with_blocked_capability() - { - // Arrange - Lyric existingLyric = _fakers.Lyric.Generate(); - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - dbContext.Lyrics.Add(existingLyric); - await dbContext.SaveChangesAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "lyrics", - id = existingLyric.StringId, - attributes = new - { - createdAt = 12.July(1980) - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.ShouldHaveCount(1); - - ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when updating resource."); - error.Detail.Should().Be("The attribute 'createdAt' on resource type 'lyrics' cannot be assigned to."); - error.Source.ShouldNotBeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/createdAt"); - error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); - } - [Fact] public async Task Cannot_update_resource_with_readonly_attribute() { @@ -1815,4 +1764,55 @@ await _testContext.RunOnDatabaseAsync(async dbContext => trackInDatabase.Performers[0].Id.Should().Be(existingPerformer.Id); }); } + + [Fact] + public async Task Cannot_assign_attribute_with_blocked_capability() + { + // Arrange + Lyric existingLyric = _fakers.Lyric.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Lyrics.Add(existingLyric); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "update", + data = new + { + type = "lyrics", + id = existingLyric.StringId, + attributes = new + { + createdAt = 12.July(1980) + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when updating resource."); + error.Detail.Should().Be("The attribute 'createdAt' on resource type 'lyrics' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/createdAt"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs index 3996983042..931e75e789 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs @@ -1046,4 +1046,62 @@ await _testContext.RunOnDatabaseAsync(async dbContext => error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/lyric/data/type"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + Lyric existingLyric = _fakers.Lyric.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Lyrics.Add(existingLyric); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + atomic__operations = new[] + { + new + { + op = "update", + data = new + { + type = "lyrics", + id = existingLyric.StringId, + relationships = new + { + language = new + { + data = new + { + type = "textLanguages", + id = Unknown.StringId.For() + } + } + } + } + } + } + }; + + const string route = "/operations"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'language' on resource type 'lyrics' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]/data/relationships/language"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/BlogPost.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/BlogPost.cs index a628cf9355..1840f5674d 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/BlogPost.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/BlogPost.cs @@ -29,6 +29,8 @@ public sealed class BlogPost : Identifiable [HasMany] public ISet Comments { get; set; } = new HashSet(); +#pragma warning disable CS0618 // Type or member is obsolete [HasOne(CanInclude = false)] +#pragma warning restore CS0618 // Type or member is obsolete public Blog? Parent { get; set; } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Calendar.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Calendar.cs index 46d82cb7fb..eaf091ec07 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Calendar.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Calendar.cs @@ -17,6 +17,9 @@ public sealed class Calendar : Identifiable [Attr] public int DefaultAppointmentDurationInMinutes { get; set; } - [HasMany] + [HasOne] + public Appointment? MostRecentAppointment { get; set; } + + [HasMany(Capabilities = HasManyCapabilities.All & ~(HasManyCapabilities.AllowView | HasManyCapabilities.AllowFilter))] public ISet Appointments { get; set; } = new HashSet(); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs index 1af2251c6b..104f5a4f79 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs @@ -18,6 +18,7 @@ public FilterTests(IntegrationTestContext, _testContext = testContext; testContext.UseController(); + testContext.UseController(); var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.EnableLegacyFilterNotation = false; @@ -89,6 +90,28 @@ public async Task Cannot_filter_on_attribute_with_blocked_capability() error.Source.Parameter.Should().Be("filter"); } + [Fact] + public async Task Cannot_filter_on_ToMany_relationship_with_blocked_capability() + { + // Arrange + const string route = "/calendars?filter=has(appointments)"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Filtering on the requested relationship is not allowed."); + error.Detail.Should().Be("Filtering on relationship 'appointments' is not allowed."); + error.Source.ShouldNotBeNull(); + error.Source.Parameter.Should().Be("filter"); + } + [Fact] public async Task Can_filter_on_ID() { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs index c835263fc6..a3fbfe54c7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs @@ -21,6 +21,7 @@ public IncludeTests(IntegrationTestContext testContext.UseController(); testContext.UseController(); testContext.UseController(); + testContext.UseController(); var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.MaximumIncludeDepth = null; @@ -872,7 +873,7 @@ public async Task Cannot_include_unknown_nested_relationship() } [Fact] - public async Task Cannot_include_relationship_with_blocked_capability() + public async Task Cannot_include_relationship_when_inclusion_blocked() { // Arrange const string route = "/blogPosts?include=parent"; @@ -894,7 +895,7 @@ public async Task Cannot_include_relationship_with_blocked_capability() } [Fact] - public async Task Cannot_include_relationship_with_nested_blocked_capability() + public async Task Cannot_include_relationship_when_nested_inclusion_blocked() { // Arrange const string route = "/blogs?include=posts.parent"; @@ -915,6 +916,85 @@ public async Task Cannot_include_relationship_with_nested_blocked_capability() error.Source.Parameter.Should().Be("include"); } + [Fact] + public async Task Hides_relationship_and_related_resources_when_viewing_blocked() + { + // Arrange + Calendar calendar = _fakers.Calendar.Generate(); + calendar.Appointments = _fakers.Appointment.Generate(2).ToHashSet(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Calendars.Add(calendar); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/calendars/{calendar.StringId}?include=appointments"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("calendars"); + responseDocument.Data.SingleValue.Id.Should().Be(calendar.StringId); + + responseDocument.Data.SingleValue.Relationships.ShouldNotBeEmpty(); + responseDocument.Data.SingleValue.Relationships.Should().NotContainKey("appointments"); + + responseDocument.Included.Should().BeEmpty(); + } + + [Fact] + public async Task Hides_relationship_but_includes_related_resource_when_viewing_blocked_but_accessible_via_other_path() + { + // Arrange + Calendar calendar = _fakers.Calendar.Generate(); + calendar.MostRecentAppointment = _fakers.Appointment.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Calendars.Add(calendar); + await dbContext.SaveChangesAsync(); + + calendar.Appointments = new[] + { + _fakers.Appointment.Generate(), + calendar.MostRecentAppointment + }.ToHashSet(); + + await dbContext.SaveChangesAsync(); + }); + + string route = $"/calendars/{calendar.StringId}?include=appointments,mostRecentAppointment"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("calendars"); + responseDocument.Data.SingleValue.Id.Should().Be(calendar.StringId); + + responseDocument.Data.SingleValue.Relationships.ShouldContainKey("mostRecentAppointment").With(value => + { + value.ShouldNotBeNull(); + value.Data.SingleValue.ShouldNotBeNull(); + value.Data.SingleValue.Type.Should().Be("appointments"); + value.Data.SingleValue.Id.Should().Be(calendar.MostRecentAppointment.StringId); + }); + + responseDocument.Data.SingleValue.Relationships.Should().NotContainKey("appointments"); + + responseDocument.Included.ShouldHaveCount(1); + responseDocument.Included[0].Type.Should().Be("appointments"); + responseDocument.Included[0].Id.Should().Be(calendar.MostRecentAppointment.StringId); + } + [Fact] public async Task Ignores_null_parent_in_nested_include() { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SerializerIgnoreConditionTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SerializerIgnoreConditionTests.cs index 6e72d6923a..0a6204cce4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SerializerIgnoreConditionTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SerializerIgnoreConditionTests.cs @@ -34,10 +34,10 @@ public async Task Applies_configuration_for_ignore_condition(JsonIgnoreCondition calendar.TimeZone = null; calendar.DefaultAppointmentDurationInMinutes = default; calendar.ShowWeekNumbers = true; - calendar.Appointments = _fakers.Appointment.Generate(1).ToHashSet(); - calendar.Appointments.Single().Description = null; - calendar.Appointments.Single().StartTime = default; - calendar.Appointments.Single().EndTime = 1.January(2001).AsUtc(); + calendar.MostRecentAppointment = _fakers.Appointment.Generate(); + calendar.MostRecentAppointment.Description = null; + calendar.MostRecentAppointment.StartTime = default; + calendar.MostRecentAppointment.EndTime = 1.January(2001).AsUtc(); await RunOnDatabaseAsync(async dbContext => { @@ -45,7 +45,7 @@ await RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - string route = $"/calendars/{calendar.StringId}?include=appointments"; + string route = $"/calendars/{calendar.StringId}?include=mostRecentAppointment"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs index bbcf2cd17a..4036c62f13 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs @@ -20,6 +20,7 @@ public SparseFieldSetTests(IntegrationTestContext(); testContext.UseController(); testContext.UseController(); + testContext.UseController(); testContext.ConfigureServicesAfterStartup(services => { @@ -741,6 +742,54 @@ public async Task Cannot_select_attribute_with_blocked_capability() error.Source.Parameter.Should().Be("fields[webAccounts]"); } + [Fact] + public async Task Cannot_select_ToOne_relationship_with_blocked_capability() + { + // Arrange + WebAccount account = _fakers.WebAccount.Generate(); + + string route = $"/webAccounts/{account.Id}?fields[webAccounts]=person"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Retrieving the requested relationship is not allowed."); + error.Detail.Should().Be("Retrieving the relationship 'person' is not allowed."); + error.Source.ShouldNotBeNull(); + error.Source.Parameter.Should().Be("fields[webAccounts]"); + } + + [Fact] + public async Task Cannot_select_ToMany_relationship_with_blocked_capability() + { + // Arrange + Calendar calendar = _fakers.Calendar.Generate(); + + string route = $"/calendars/{calendar.Id}?fields[calendars]=appointments"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Retrieving the requested relationship is not allowed."); + error.Detail.Should().Be("Retrieving the relationship 'appointments' is not allowed."); + error.Source.ShouldNotBeNull(); + error.Source.Parameter.Should().Be("fields[calendars]"); + } + [Fact] public async Task Retrieves_all_properties_when_fieldset_contains_readonly_attribute() { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/WebAccount.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/WebAccount.cs index 1749184fa4..70133fba54 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/WebAccount.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/WebAccount.cs @@ -11,7 +11,7 @@ public sealed class WebAccount : Identifiable [Attr] public string UserName { get; set; } = null!; - [Attr(Capabilities = ~AttrCapabilities.AllowView)] + [Attr(Capabilities = AttrCapabilities.All & ~AttrCapabilities.AllowView)] public string Password { get; set; } = null!; [Attr] @@ -23,7 +23,7 @@ public sealed class WebAccount : Identifiable [Attr] public string EmailAddress { get; set; } = null!; - [HasOne] + [HasOne(Capabilities = HasOneCapabilities.All & ~HasOneCapabilities.AllowView)] public Human? Person { get; set; } [HasMany] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs index f85eb6579e..587b7d8277 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs @@ -728,41 +728,6 @@ public async Task Cannot_create_on_resource_type_mismatch_between_url_and_body() error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } - [Fact] - public async Task Cannot_create_resource_attribute_with_blocked_capability() - { - // Arrange - var requestBody = new - { - data = new - { - type = "workItems", - attributes = new - { - isImportant = true - } - } - }; - - const string route = "/workItems"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); - - // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.ShouldHaveCount(1); - - ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when creating resource."); - error.Detail.Should().Be("The attribute 'isImportant' on resource type 'workItems' cannot be assigned to."); - error.Source.ShouldNotBeNull(); - error.Source.Pointer.Should().Be("/data/attributes/isImportant"); - error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); - } - [Fact] public async Task Cannot_create_resource_with_readonly_attribute() { @@ -958,4 +923,39 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.Tags.Single().Id.Should().Be(existingTag.Id); }); } + + [Fact] + public async Task Cannot_assign_attribute_with_blocked_capability() + { + // Arrange + var requestBody = new + { + data = new + { + type = "workItems", + attributes = new + { + isImportant = true + } + } + }; + + const string route = "/workItems"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when creating resource."); + error.Detail.Should().Be("The attribute 'isImportant' on resource type 'workItems' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/data/attributes/isImportant"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs index fb96a8b0f5..0a6ed42c60 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs @@ -17,6 +17,7 @@ public CreateResourceWithToManyRelationshipTests(IntegrationTestContext(); + testContext.UseController(); testContext.UseController(); } @@ -790,4 +791,49 @@ public async Task Cannot_create_resource_with_local_ID() error.Source.Pointer.Should().Be("/data/lid"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + var requestBody = new + { + data = new + { + type = "workItemGroups", + relationships = new + { + items = new + { + data = new[] + { + new + { + type = "workItems", + id = Unknown.StringId.For() + } + } + } + } + } + }; + + const string route = "/workItemGroups"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'items' on resource type 'workItemGroups' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/data/relationships/items"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs index 07b97c252d..08bbcf1f63 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs @@ -762,4 +762,46 @@ public async Task Cannot_create_resource_with_local_ID() error.Source.Pointer.Should().Be("/data/lid"); error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + var requestBody = new + { + data = new + { + type = "workItems", + relationships = new + { + group = new + { + data = new + { + type = "workItemGroups", + id = Unknown.StringId.For() + } + } + } + } + }; + + const string route = "/workItems"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'group' on resource type 'workItems' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/data/relationships/group"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs index 5a4a3b226b..08ac8c3f54 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs @@ -17,6 +17,7 @@ public AddToToManyRelationshipTests(IntegrationTestContext(); + testContext.UseController(); } [Fact] @@ -933,4 +934,46 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.RelatedTo.Should().ContainSingle(workItem => workItem.Id == existingWorkItem.RelatedTo[0].Id); }); } + + [Fact] + public async Task Cannot_add_with_blocked_capability() + { + // Arrange + WorkItemGroup existingWorkItemGroup = _fakers.WorkItemGroup.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Groups.Add(existingWorkItemGroup); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new[] + { + new + { + type = "workItems", + id = Unknown.StringId.For() + } + } + }; + + string route = $"/workItemGroups/{existingWorkItemGroup.StringId}/relationships/items"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be added to."); + error.Detail.Should().Be("The relationship 'items' on resource type 'workItemGroups' cannot be added to."); + error.Source.Should().BeNull(); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs index 684575b860..eef972dd1f 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs @@ -23,6 +23,7 @@ public RemoveFromToManyRelationshipTests(IntegrationTestContext(); + testContext.UseController(); testContext.ConfigureServicesAfterStartup(services => { @@ -1055,6 +1056,48 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }); } + [Fact] + public async Task Cannot_remove_with_blocked_capability() + { + // Arrange + WorkItemGroup existingWorkItemGroup = _fakers.WorkItemGroup.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Groups.Add(existingWorkItemGroup); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new[] + { + new + { + type = "workItems", + id = Unknown.StringId.For() + } + } + }; + + string route = $"/workItemGroups/{existingWorkItemGroup.StringId}/relationships/items"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteDeleteAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be removed from."); + error.Detail.Should().Be("The relationship 'items' on resource type 'workItemGroups' cannot be removed from."); + error.Source.Should().BeNull(); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] private sealed class RemoveExtraFromWorkItemDefinition : JsonApiResourceDefinition { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs index fb368fd524..02a7e68292 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs @@ -18,6 +18,7 @@ public ReplaceToManyRelationshipTests(IntegrationTestContext(); + testContext.UseController(); } [Fact] @@ -1013,4 +1014,46 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.RelatedTo[0].Id.Should().Be(existingWorkItem.Id); }); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + WorkItemGroup existingWorkItemGroup = _fakers.WorkItemGroup.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Groups.Add(existingWorkItemGroup); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new[] + { + new + { + type = "workItems", + id = Unknown.StringId.For() + } + } + }; + + string route = $"/workItemGroups/{existingWorkItemGroup.StringId}/relationships/items"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'items' on resource type 'workItemGroups' cannot be assigned to."); + error.Source.Should().BeNull(); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs index 1ba844be78..a492cc4826 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs @@ -773,4 +773,43 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.Parent.Id.Should().Be(existingWorkItem.Id); }); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + WorkItem existingWorkItem = _fakers.WorkItem.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.WorkItems.Add(existingWorkItem); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "workItemGroups", + id = Unknown.StringId.For() + } + }; + + string route = $"/workItems/{existingWorkItem.StringId}/relationships/group"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'group' on resource type 'workItems' cannot be assigned to."); + error.Source.Should().BeNull(); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs index 0ab3ab93b2..be673f660e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs @@ -19,6 +19,7 @@ public ReplaceToManyRelationshipTests(IntegrationTestContext(); + testContext.UseController(); testContext.UseController(); testContext.ConfigureServicesAfterStartup(services => @@ -1134,4 +1135,58 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.RelatedTo[0].Id.Should().Be(existingWorkItem.Id); }); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + WorkItemGroup existingWorkItemGroup = _fakers.WorkItemGroup.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Groups.Add(existingWorkItemGroup); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "workItemGroups", + id = existingWorkItemGroup.StringId, + relationships = new + { + items = new + { + data = new[] + { + new + { + type = "workItems", + id = Unknown.StringId.For() + } + } + } + } + } + }; + + string route = $"/workItemGroups/{existingWorkItemGroup.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'items' on resource type 'workItemGroups' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/data/relationships/items"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs index b67090e22f..dc5fb35996 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs @@ -1085,50 +1085,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext => error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); } - [Fact] - public async Task Cannot_update_resource_attribute_with_blocked_capability() - { - // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - dbContext.WorkItems.Add(existingWorkItem); - await dbContext.SaveChangesAsync(); - }); - - var requestBody = new - { - data = new - { - type = "workItems", - id = existingWorkItem.StringId, - attributes = new - { - isImportant = true - } - } - }; - - string route = $"/workItems/{existingWorkItem.StringId}"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); - - // Assert - httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.ShouldHaveCount(1); - - ErrorObject error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when updating resource."); - error.Detail.Should().Be("The attribute 'isImportant' on resource type 'workItems' cannot be assigned to."); - error.Source.ShouldNotBeNull(); - error.Source.Pointer.Should().Be("/data/attributes/isImportant"); - error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); - } - [Fact] public async Task Cannot_update_resource_with_readonly_attribute() { @@ -1542,4 +1498,48 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.RelatedTo.Single().Id.Should().Be(existingWorkItem.Id); }); } + + [Fact] + public async Task Cannot_assign_attribute_with_blocked_capability() + { + // Arrange + WorkItem existingWorkItem = _fakers.WorkItem.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.WorkItems.Add(existingWorkItem); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "workItems", + id = existingWorkItem.StringId, + attributes = new + { + isImportant = true + } + } + }; + + string route = $"/workItems/{existingWorkItem.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when updating resource."); + error.Detail.Should().Be("The attribute 'isImportant' on resource type 'workItems' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/data/attributes/isImportant"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs index be3c9b89b2..81faaa8297 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs @@ -941,4 +941,55 @@ await _testContext.RunOnDatabaseAsync(async dbContext => workItemInDatabase.Parent.Id.Should().Be(existingWorkItem.Id); }); } + + [Fact] + public async Task Cannot_assign_relationship_with_blocked_capability() + { + // Arrange + WorkItem existingWorkItem = _fakers.WorkItem.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.WorkItems.Add(existingWorkItem); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "workItems", + id = existingWorkItem.StringId, + relationships = new + { + group = new + { + data = new + { + type = "workItemGroups", + id = Unknown.StringId.For() + } + } + } + } + }; + + string route = $"/workItems/{existingWorkItem.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); + + responseDocument.Errors.ShouldHaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Failed to deserialize request body: Relationship cannot be assigned."); + error.Detail.Should().Be("The relationship 'group' on resource type 'workItems' cannot be assigned to."); + error.Source.ShouldNotBeNull(); + error.Source.Pointer.Should().Be("/data/relationships/group"); + error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItem.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItem.cs index e7a0a01b5c..c722592e83 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItem.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItem.cs @@ -19,7 +19,7 @@ public sealed class WorkItem : Identifiable public WorkItemPriority Priority { get; set; } [NotMapped] - [Attr(Capabilities = ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] + [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] public bool IsImportant { get => Priority == WorkItemPriority.High; @@ -47,6 +47,6 @@ public bool IsImportant [HasMany] public IList RelatedTo { get; set; } = new List(); - [HasOne] + [HasOne(Capabilities = HasOneCapabilities.All & ~HasOneCapabilities.AllowSet)] public WorkItemGroup? Group { get; set; } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItemGroup.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItemGroup.cs index 86c11391ed..7f132c8994 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItemGroup.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/WorkItemGroup.cs @@ -22,6 +22,6 @@ public sealed class WorkItemGroup : Identifiable [HasOne] public RgbColor? Color { get; set; } - [HasMany] + [HasMany(Capabilities = HasManyCapabilities.All & ~(HasManyCapabilities.AllowSet | HasManyCapabilities.AllowAdd | HasManyCapabilities.AllowRemove))] public IList Items { get; set; } = new List(); } From 45b1136ce35c9f8a1a0b2508d3f8e4a5a96da30c Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 1 Oct 2022 16:47:52 +0200 Subject: [PATCH 37/50] Allow empty include query string parameter value --- .../Queries/Internal/Parsing/IncludeParser.cs | 14 ++++++++--- .../IncludeQueryStringParameterReader.cs | 2 +- .../QueryStrings/Includes/IncludeTests.cs | 25 +++++++++++++++++++ .../QueryStrings/QueryStringTests.cs | 1 - .../IncludeParseTests.cs | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 38ee1ffa83..3c8be88e46 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -30,12 +30,18 @@ public IncludeExpression Parse(string source, ResourceType resourceTypeInScope, protected IncludeExpression ParseInclude(ResourceType resourceTypeInScope, int? maximumDepth) { var treeRoot = IncludeTreeNode.CreateRoot(resourceTypeInScope); - - ParseRelationshipChain(treeRoot); + bool isAtStart = true; while (TokenStack.Any()) { - EatSingleCharacterToken(TokenKind.Comma); + if (!isAtStart) + { + EatSingleCharacterToken(TokenKind.Comma); + } + else + { + isAtStart = false; + } ParseRelationshipChain(treeRoot); } @@ -244,7 +250,7 @@ public IncludeExpression ToExpression() if (element.Relationship is HiddenRootRelationshipAttribute) { - return new IncludeExpression(element.Children); + return element.Children.Any() ? new IncludeExpression(element.Children) : IncludeExpression.Empty; } return new IncludeExpression(ImmutableHashSet.Create(element)); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs index 299e8b22b2..a4db6ebd4a 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs @@ -18,7 +18,7 @@ public class IncludeQueryStringParameterReader : QueryStringParameterReader, IIn private IncludeExpression? _includeExpression; - public bool AllowEmptyValue => false; + public bool AllowEmptyValue => true; public IncludeQueryStringParameterReader(IJsonApiRequest request, IResourceGraph resourceGraph, IJsonApiOptions options) : base(request, resourceGraph) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs index a3fbfe54c7..6dece6f0d1 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs @@ -828,6 +828,31 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Included[0].Attributes.ShouldContainKey("userName").With(value => value.Should().Be(account.UserName)); } + [Fact] + public async Task Can_select_empty_includes() + { + // Arrange + WebAccount account = _fakers.WebAccount.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Accounts.Add(account); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/webAccounts/{account.StringId}?include="; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + + responseDocument.Included.Should().BeEmpty(); + } + [Fact] public async Task Cannot_include_unknown_relationship() { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs index 70a40d1e4f..0aa955a219 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringTests.cs @@ -64,7 +64,6 @@ public async Task Can_use_unknown_query_string_parameter() } [Theory] - [InlineData("include")] [InlineData("filter")] [InlineData("sort")] [InlineData("page[size]")] diff --git a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/IncludeParseTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/IncludeParseTests.cs index 7c7c28f4e3..b576b58b71 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/IncludeParseTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/IncludeParseTests.cs @@ -50,7 +50,6 @@ public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, b } [Theory] - [InlineData("includes", "", "Relationship name expected.")] [InlineData("includes", " ", "Unexpected whitespace.")] [InlineData("includes", ",", "Relationship name expected.")] [InlineData("includes", "posts,", "Relationship name expected.")] @@ -85,6 +84,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin } [Theory] + [InlineData("includes", "", "")] [InlineData("includes", "owner", "owner")] [InlineData("includes", "posts", "posts")] [InlineData("includes", "owner.posts", "owner.posts")] From f2c6029989f3e59616f5edce8f54a291a653ab1e Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 1 Oct 2022 18:30:21 +0200 Subject: [PATCH 38/50] Fixed: full linkage does not allow primary resources to occur in included --- .../Response/ResourceObjectTreeNode.cs | 12 +++++- .../QueryStrings/Includes/IncludeTests.cs | 22 ++++------ .../Reading/IClientSettingsProvider.cs | 2 +- .../ResourceDefinitions/Reading/Moon.cs | 3 ++ .../Reading/MoonDefinition.cs | 8 ++-- .../Reading/ResourceDefinitionReadTests.cs | 42 ++++++++++--------- .../Reading/TestClientSettingsProvider.cs | 8 ++-- 7 files changed, 54 insertions(+), 43 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs index 01743168be..9527f766e1 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs @@ -181,7 +181,17 @@ public IList GetResponseIncluded() VisitRelationshipChildrenInSubtree(child, visited); } - return visited.Select(node => node.ResourceObject).ToArray(); + List includes = visited.Select(node => node.ResourceObject).ToList(); + + foreach (ResourceObject primaryResourceObjects in GetDirectChildren().Select(node => node.ResourceObject)) + { + if (includes.Contains(primaryResourceObjects, ResourceObjectComparer.Instance)) + { + includes.Remove(primaryResourceObjects); + } + } + + return includes; } private IList GetDirectChildren() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs index 6dece6f0d1..88360bcfa2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs @@ -398,29 +398,25 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Data.SingleValue.Id.Should().Be(comment.StringId); responseDocument.Data.SingleValue.Attributes.ShouldContainKey("text").With(value => value.Should().Be(comment.Text)); - responseDocument.Included.ShouldHaveCount(5); + responseDocument.Included.ShouldHaveCount(4); responseDocument.Included[0].Type.Should().Be("blogPosts"); responseDocument.Included[0].Id.Should().Be(comment.Parent.StringId); responseDocument.Included[0].Attributes.ShouldContainKey("caption").With(value => value.Should().Be(comment.Parent.Caption)); responseDocument.Included[1].Type.Should().Be("comments"); - responseDocument.Included[1].Id.Should().Be(comment.StringId); - responseDocument.Included[1].Attributes.ShouldContainKey("text").With(value => value.Should().Be(comment.Text)); - - responseDocument.Included[2].Type.Should().Be("comments"); - responseDocument.Included[2].Id.Should().Be(comment.Parent.Comments.ElementAt(0).StringId); - responseDocument.Included[2].Attributes.ShouldContainKey("text").With(value => value.Should().Be(comment.Parent.Comments.ElementAt(0).Text)); + responseDocument.Included[1].Id.Should().Be(comment.Parent.Comments.ElementAt(0).StringId); + responseDocument.Included[1].Attributes.ShouldContainKey("text").With(value => value.Should().Be(comment.Parent.Comments.ElementAt(0).Text)); string userName = comment.Parent.Comments.ElementAt(0).Author!.UserName; - responseDocument.Included[3].Type.Should().Be("webAccounts"); - responseDocument.Included[3].Id.Should().Be(comment.Parent.Comments.ElementAt(0).Author!.StringId); - responseDocument.Included[3].Attributes.ShouldContainKey("userName").With(value => value.Should().Be(userName)); + responseDocument.Included[2].Type.Should().Be("webAccounts"); + responseDocument.Included[2].Id.Should().Be(comment.Parent.Comments.ElementAt(0).Author!.StringId); + responseDocument.Included[2].Attributes.ShouldContainKey("userName").With(value => value.Should().Be(userName)); - responseDocument.Included[4].Type.Should().Be("comments"); - responseDocument.Included[4].Id.Should().Be(comment.Parent.Comments.ElementAt(1).StringId); - responseDocument.Included[4].Attributes.ShouldContainKey("text").With(value => value.Should().Be(comment.Parent.Comments.ElementAt(1).Text)); + responseDocument.Included[3].Type.Should().Be("comments"); + responseDocument.Included[3].Id.Should().Be(comment.Parent.Comments.ElementAt(1).StringId); + responseDocument.Included[3].Attributes.ShouldContainKey("text").With(value => value.Should().Be(comment.Parent.Comments.ElementAt(1).Text)); } [Fact] diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs index 65f32a4f65..f67cd3d993 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs @@ -4,5 +4,5 @@ public interface IClientSettingsProvider { bool IsIncludePlanetMoonsBlocked { get; } bool ArePlanetsWithPrivateNameHidden { get; } - bool IsMoonOrbitingPlanetAutoIncluded { get; } + bool IsStarGivingLightToMoonAutoIncluded { get; } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Moon.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Moon.cs index 0a394c6d17..f6d16fed1b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Moon.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Moon.cs @@ -16,4 +16,7 @@ public sealed class Moon : Identifiable [HasOne] public Planet OrbitsAround { get; set; } = null!; + + [HasOne] + public Star? IsGivenLightBy { get; set; } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs index 7ef545b462..bdd75a9aff 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs @@ -28,15 +28,15 @@ public override IImmutableSet OnApplyIncludes(IImmutab { base.OnApplyIncludes(existingIncludes); - if (!_clientSettingsProvider.IsMoonOrbitingPlanetAutoIncluded || - existingIncludes.Any(include => include.Relationship.Property.Name == nameof(Moon.OrbitsAround))) + if (!_clientSettingsProvider.IsStarGivingLightToMoonAutoIncluded || + existingIncludes.Any(include => include.Relationship.Property.Name == nameof(Moon.IsGivenLightBy))) { return existingIncludes; } - RelationshipAttribute orbitsAroundRelationship = ResourceType.GetRelationshipByPropertyName(nameof(Moon.OrbitsAround)); + RelationshipAttribute isGivenLightByRelationship = ResourceType.GetRelationshipByPropertyName(nameof(Moon.IsGivenLightBy)); - return existingIncludes.Add(new IncludeElementExpression(orbitsAroundRelationship)); + return existingIncludes.Add(new IncludeElementExpression(isGivenLightByRelationship)); } public override QueryStringParameterHandlers OnRegisterQueryableHandlersForQueryStringParameters() diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs index 83dececbec..db80d4c14b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs @@ -93,10 +93,11 @@ public async Task Include_from_resource_definition_is_added() var hitCounter = _testContext.Factory.Services.GetRequiredService(); var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); - settingsProvider.AutoIncludeOrbitingPlanetForMoons(); + settingsProvider.AutoIncludeStarGivingLightToMoon(); Moon moon = _fakers.Moon.Generate(); moon.OrbitsAround = _fakers.Planet.Generate(); + moon.IsGivenLightBy = _fakers.Star.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -114,18 +115,18 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Data.SingleValue.ShouldNotBeNull(); - responseDocument.Data.SingleValue.Relationships.ShouldContainKey("orbitsAround").With(value => + responseDocument.Data.SingleValue.Relationships.ShouldContainKey("isGivenLightBy").With(value => { value.ShouldNotBeNull(); value.Data.SingleValue.ShouldNotBeNull(); - value.Data.SingleValue.Type.Should().Be("planets"); - value.Data.SingleValue.Id.Should().Be(moon.OrbitsAround.StringId); + value.Data.SingleValue.Type.Should().Be("stars"); + value.Data.SingleValue.Id.Should().Be(moon.IsGivenLightBy.StringId); }); responseDocument.Included.ShouldHaveCount(1); - responseDocument.Included[0].Type.Should().Be("planets"); - responseDocument.Included[0].Id.Should().Be(moon.OrbitsAround.StringId); - responseDocument.Included[0].Attributes.ShouldContainKey("publicName").With(value => value.Should().Be(moon.OrbitsAround.PublicName)); + responseDocument.Included[0].Type.Should().Be("stars"); + responseDocument.Included[0].Id.Should().Be(moon.IsGivenLightBy.StringId); + responseDocument.Included[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be(moon.IsGivenLightBy.Name)); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] { @@ -134,12 +135,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplySort), (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), - (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), - (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), (typeof(Moon), ResourceDefinitionExtensibilityPoints.GetMeta), - (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), - (typeof(Planet), ResourceDefinitionExtensibilityPoints.GetMeta) + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.GetMeta) }, options => options.WithStrictOrdering()); } @@ -150,11 +151,11 @@ public async Task Include_from_included_resource_definition_is_added() var hitCounter = _testContext.Factory.Services.GetRequiredService(); var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); - settingsProvider.AutoIncludeOrbitingPlanetForMoons(); + settingsProvider.AutoIncludeStarGivingLightToMoon(); Planet planet = _fakers.Planet.Generate(); planet.Moons = _fakers.Moon.Generate(1).ToHashSet(); - planet.Moons.ElementAt(0).OrbitsAround = _fakers.Planet.Generate(); + planet.Moons.ElementAt(0).IsGivenLightBy = _fakers.Star.Generate(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -178,11 +179,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Included[0].Id.Should().Be(planet.Moons.ElementAt(0).StringId); responseDocument.Included[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be(planet.Moons.ElementAt(0).Name)); - string moonName = planet.Moons.ElementAt(0).OrbitsAround.PublicName; - - responseDocument.Included[1].Type.Should().Be("planets"); - responseDocument.Included[1].Id.Should().Be(planet.Moons.ElementAt(0).OrbitsAround.StringId); - responseDocument.Included[1].Attributes.ShouldContainKey("publicName").With(value => value.Should().Be(moonName)); + responseDocument.Included[1].Type.Should().Be("stars"); + responseDocument.Included[1].Id.Should().Be(planet.Moons.ElementAt(0).IsGivenLightBy!.StringId); + responseDocument.Included[1].Attributes.ShouldContainKey("name").With(value => value.Should().Be(planet.Moons.ElementAt(0).IsGivenLightBy!.Name)); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] { @@ -196,11 +195,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplyPagination), (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), - (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), (typeof(Planet), ResourceDefinitionExtensibilityPoints.GetMeta), (typeof(Moon), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), - (typeof(Moon), ResourceDefinitionExtensibilityPoints.GetMeta) + (typeof(Moon), ResourceDefinitionExtensibilityPoints.GetMeta), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.GetMeta) }, options => options.WithStrictOrdering()); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs index 63f0033119..0efc7a415e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs @@ -4,13 +4,13 @@ internal sealed class TestClientSettingsProvider : IClientSettingsProvider { public bool IsIncludePlanetMoonsBlocked { get; private set; } public bool ArePlanetsWithPrivateNameHidden { get; private set; } - public bool IsMoonOrbitingPlanetAutoIncluded { get; private set; } + public bool IsStarGivingLightToMoonAutoIncluded { get; private set; } public void ResetToDefaults() { IsIncludePlanetMoonsBlocked = false; ArePlanetsWithPrivateNameHidden = false; - IsMoonOrbitingPlanetAutoIncluded = false; + IsStarGivingLightToMoonAutoIncluded = false; } public void BlockIncludePlanetMoons() @@ -23,8 +23,8 @@ public void HidePlanetsWithPrivateName() ArePlanetsWithPrivateNameHidden = true; } - public void AutoIncludeOrbitingPlanetForMoons() + public void AutoIncludeStarGivingLightToMoon() { - IsMoonOrbitingPlanetAutoIncluded = true; + IsStarGivingLightToMoonAutoIncluded = true; } } From 935224d963a8a2dde1292890a4ab51fcde3d33c9 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 3 Oct 2022 22:31:01 +0200 Subject: [PATCH 39/50] Remove unneeded dependencies --- src/JsonApiDotNetCore/JsonApiDotNetCore.csproj | 1 - test/TestBuildingBlocks/TestBuildingBlocks.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index e401db38fa..9b73a09253 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -43,6 +43,5 @@ - diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index ed335f630f..5600104fda 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -13,7 +13,6 @@ - From 87265184d4a91d438ef94630523b257b0273c497 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 3 Oct 2022 22:37:22 +0200 Subject: [PATCH 40/50] Fix SourceLink and IntelliSense on doc-comments in Annotations --- .../JsonApiDotNetCore.Annotations.csproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj index 5f4d7c1d76..1fe6858b96 100644 --- a/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj +++ b/src/JsonApiDotNetCore.Annotations/JsonApiDotNetCore.Annotations.csproj @@ -1,4 +1,4 @@ - + $(TargetFrameworkName);netstandard1.0 true @@ -45,4 +45,9 @@ + + + + + From b13ede9a8ce9c9985b451fa268c3a9dd1628f725 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 7 Oct 2022 04:47:52 +0200 Subject: [PATCH 41/50] Update compatibility table --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35adb3fe9f..e9b17a7179 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,10 @@ See also our [versioning policy](./VERSIONING_POLICY.md). | | | Core 3.1 | 5 | | | | 5 | 5 | | | | 6 | 5 | -| v5.x | Stable | 6 | 6 | +| v5.0.0-5.0.2 | Stable | 6 | 6 | +| v5.0.3+ | Stable | 6 | 6 | +| | | 6 | 7 | +| | | 7 | 7 | ## Contributing From cdf3c0bc8194953477be3116876be333589278e3 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 7 Oct 2022 04:49:29 +0200 Subject: [PATCH 42/50] Update compatibility table (remove v) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9b17a7179..26432ee909 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,8 @@ See also our [versioning policy](./VERSIONING_POLICY.md). | | | Core 3.1 | 5 | | | | 5 | 5 | | | | 6 | 5 | -| v5.0.0-5.0.2 | Stable | 6 | 6 | -| v5.0.3+ | Stable | 6 | 6 | +| 5.0.0-5.0.2 | Stable | 6 | 6 | +| 5.0.3+ | Stable | 6 | 6 | | | | 6 | 7 | | | | 7 | 7 | From 2113302dd3028768de9821c49be00f982aac4e65 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:30:01 +0200 Subject: [PATCH 43/50] Update docs/usage/resources/relationships.md Co-authored-by: Maurits Moeys --- docs/usage/resources/relationships.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/resources/relationships.md b/docs/usage/resources/relationships.md index 14ff2eb7f5..d1f36a89a9 100644 --- a/docs/usage/resources/relationships.md +++ b/docs/usage/resources/relationships.md @@ -180,7 +180,7 @@ This can be overridden per relationship. Indicates whether the relationship can be returned in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response. Otherwise, the relationship (and its related resources, when included) are silently omitted. -Note this setting does not affect retrieving the related resources directly. +Note that this setting does not affect retrieving the related resources directly. ```c# #nullable enable From 4b725bd26359af123f4fb1280cde3bb8c67ac1bc Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 19 Oct 2022 19:08:12 +0200 Subject: [PATCH 44/50] Fixed typo --- .../Serialization/Response/ResourceObjectTreeNode.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs index 9527f766e1..4c3a44fe38 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/ResourceObjectTreeNode.cs @@ -183,11 +183,11 @@ public IList GetResponseIncluded() List includes = visited.Select(node => node.ResourceObject).ToList(); - foreach (ResourceObject primaryResourceObjects in GetDirectChildren().Select(node => node.ResourceObject)) + foreach (ResourceObject primaryResourceObject in GetDirectChildren().Select(node => node.ResourceObject)) { - if (includes.Contains(primaryResourceObjects, ResourceObjectComparer.Instance)) + if (includes.Contains(primaryResourceObject, ResourceObjectComparer.Instance)) { - includes.Remove(primaryResourceObjects); + includes.Remove(primaryResourceObject); } } From 01e132bdbb851fc88d21acdb191c6d83e9868fe0 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 20 Oct 2022 03:29:28 +0200 Subject: [PATCH 45/50] Correct pattern that is used in null-check refactorings --- JsonApiDotNetCore.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index 1ffdf4a909..05a03c584b 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -3,7 +3,7 @@ // $EXPR$ -- source expression // $NAME$ -- source name (string literal or 'nameof' expression) // $MESSAGE$ -- string literal in the form of "$NAME$ != null" -JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); +JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$); 199 5000 99 From 993ed61788f1ad819481364fe5b572900afe935b Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 26 Oct 2022 01:59:40 +0200 Subject: [PATCH 46/50] Updates tests to write SQL statements to the Output Window, so we don't need to edit appsettings.json when debugging --- .../Archiving/TelevisionDbContext.cs | 3 ++- .../AtomicOperations/OperationsDbContext.cs | 3 ++- .../Transactions/ExtraDbContext.cs | 3 ++- .../IntegrationTests/Blobs/BlobDbContext.cs | 3 ++- .../CompositeKeys/CompositeDbContext.cs | 3 ++- .../ContentNegotiation/PolicyDbContext.cs | 3 ++- .../ActionResultDbContext.cs | 3 ++- .../CustomRoutes/CustomRouteDbContext.cs | 3 ++- .../EagerLoading/EagerLoadingDbContext.cs | 3 ++- .../ExceptionHandling/ErrorDbContext.cs | 3 ++- .../HostingInIIS/HostingDbContext.cs | 3 ++- .../HostingInIIS/HostingStartup.cs | 3 +-- .../IdObfuscation/ObfuscationDbContext.cs | 3 ++- .../ModelState/ModelStateDbContext.cs | 3 ++- .../RequestBody/WorkflowDbContext.cs | 3 ++- .../IntegrationTests/Links/LinksDbContext.cs | 3 ++- .../Logging/LoggingDbContext.cs | 3 ++- .../IntegrationTests/Meta/MetaDbContext.cs | 3 ++- .../FireForgetDbContext.cs | 3 ++- .../OutboxDbContext.cs | 3 ++- .../MultiTenancy/MultiTenancyDbContext.cs | 3 ++- .../KebabCasingConventionStartup.cs | 3 +-- .../NamingConventions/NamingDbContext.cs | 3 ++- .../PascalCasingConventionStartup.cs | 3 +-- .../NonJsonApiControllers/EmptyDbContext.cs | 3 ++- .../NonJsonApiControllers/KnownDbContext.cs | 3 ++- .../QueryStrings/Filtering/FilterDbContext.cs | 3 ++- .../QueryStrings/QueryStringDbContext.cs | 3 ++- .../ReadWrite/ReadWriteDbContext.cs | 3 ++- .../DefaultBehaviorDbContext.cs | 3 ++- .../InjectionDbContext.cs | 3 ++- .../Reading/UniverseDbContext.cs | 3 ++- .../Serialization/SerializationDbContext.cs | 3 ++- .../ResourceInheritanceDbContext.cs | 3 ++- .../RestrictionDbContext.cs | 3 ++- .../Serialization/SerializationDbContext.cs | 3 ++- .../SoftDeletion/SoftDeletionDbContext.cs | 3 ++- .../ZeroKeys/ZeroKeyDbContext.cs | 3 ++- .../AbsoluteLinksInApiNamespaceStartup.cs | 3 +-- .../AbsoluteLinksNoNamespaceStartup.cs | 3 +-- .../Startups/NoModelStateValidationStartup.cs | 3 +-- .../RelativeLinksInApiNamespaceStartup.cs | 3 +-- .../RelativeLinksNoNamespaceStartup.cs | 3 +-- .../DependencyContainerRegistrationTests.cs | 2 +- .../IntegrationTestContext.cs | 2 +- test/TestBuildingBlocks/TestableDbContext.cs | 20 +++++++++++++++++++ test/TestBuildingBlocks/TestableStartup.cs | 3 +-- .../ServiceCollectionExtensionsTests.cs | 3 ++- 48 files changed, 103 insertions(+), 56 deletions(-) create mode 100644 test/TestBuildingBlocks/TestableDbContext.cs diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs index 471f471028..f378dd5846 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Archiving/TelevisionDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Archiving; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class TelevisionDbContext : DbContext +public sealed class TelevisionDbContext : TestableDbContext { public DbSet Networks => Set(); public DbSet Stations => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs index 45be92ce8b..26dd815521 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class OperationsDbContext : DbContext +public sealed class OperationsDbContext : TestableDbContext { public DbSet Playlists => Set(); public DbSet MusicTracks => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs index 85fe191e86..8638855a46 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/ExtraDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Transactions; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ExtraDbContext : DbContext +public sealed class ExtraDbContext : TestableDbContext { public ExtraDbContext(DbContextOptions options) : base(options) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs index cadbb08b56..88a891c319 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Blobs; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class BlobDbContext : DbContext +public sealed class BlobDbContext : TestableDbContext { public DbSet ImageContainers => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs index e9e12439e6..b745208cae 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.CompositeKeys; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class CompositeDbContext : DbContext +public sealed class CompositeDbContext : TestableDbContext { public DbSet Cars => Set(); public DbSet Engines => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs index 61063269ce..2526faae2e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ContentNegotiation/PolicyDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.ContentNegotiation; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class PolicyDbContext : DbContext +public sealed class PolicyDbContext : TestableDbContext { public DbSet Policies => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs index 63b748ffab..d0a6050a0b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ControllerActionResults/ActionResultDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.ControllerActionResults; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ActionResultDbContext : DbContext +public sealed class ActionResultDbContext : TestableDbContext { public DbSet Toothbrushes => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs index 784e07bb4b..9e2debb9a0 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.CustomRoutes; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class CustomRouteDbContext : DbContext +public sealed class CustomRouteDbContext : TestableDbContext { public DbSet Towns => Set(); public DbSet Civilians => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs index 01f81cd319..030a1a447b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.EagerLoading; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class EagerLoadingDbContext : DbContext +public sealed class EagerLoadingDbContext : TestableDbContext { public DbSet States => Set(); public DbSet Streets => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs index 141dfc4f71..bf176e681e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ExceptionHandling/ErrorDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.ExceptionHandling; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ErrorDbContext : DbContext +public sealed class ErrorDbContext : TestableDbContext { public DbSet ConsumerArticles => Set(); public DbSet ThrowingArticles => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs index e92dae1318..a99db32f47 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.HostingInIIS; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class HostingDbContext : DbContext +public sealed class HostingDbContext : TestableDbContext { public DbSet ArtGalleries => Set(); public DbSet Paintings => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingStartup.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingStartup.cs index a22dfe5954..1653cd5e96 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingStartup.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/HostingInIIS/HostingStartup.cs @@ -1,14 +1,13 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using Microsoft.AspNetCore.Builder; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.HostingInIIS; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class HostingStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs index 94921ea800..efc83bbdf8 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/IdObfuscation/ObfuscationDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.IdObfuscation; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ObfuscationDbContext : DbContext +public sealed class ObfuscationDbContext : TestableDbContext { public DbSet BankAccounts => Set(); public DbSet DebitCards => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs index 2f1b56cf6f..73f3241f28 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.InputValidation.ModelState; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ModelStateDbContext : DbContext +public sealed class ModelStateDbContext : TestableDbContext { public DbSet Volumes => Set(); public DbSet Directories => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs index decc09bdc6..438093f855 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/RequestBody/WorkflowDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.InputValidation.RequestBody; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class WorkflowDbContext : DbContext +public sealed class WorkflowDbContext : TestableDbContext { public DbSet Workflows => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs index 390f8ec5e2..ffd2333fbe 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.Links; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class LinksDbContext : DbContext +public sealed class LinksDbContext : TestableDbContext { public DbSet PhotoAlbums => Set(); public DbSet Photos => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs index 39c497616c..761806d3c9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Logging/LoggingDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Logging; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class LoggingDbContext : DbContext +public sealed class LoggingDbContext : TestableDbContext { public DbSet AuditEntries => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs index 25f6f10810..84937ff3d5 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Meta/MetaDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Meta; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class MetaDbContext : DbContext +public sealed class MetaDbContext : TestableDbContext { public DbSet ProductFamilies => Set(); public DbSet SupportTickets => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs index 2dd337421a..83cddc52f0 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/FireAndForgetDelivery/FireForgetDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.FireAndForgetDelivery; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class FireForgetDbContext : DbContext +public sealed class FireForgetDbContext : TestableDbContext { public DbSet Users => Set(); public DbSet Groups => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs index f2a88e1c8d..c02b8fcc34 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Microservices/TransactionalOutboxPattern/OutboxDbContext.cs @@ -1,11 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCoreTests.IntegrationTests.Microservices.Messages; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Microservices.TransactionalOutboxPattern; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class OutboxDbContext : DbContext +public sealed class OutboxDbContext : TestableDbContext { public DbSet Users => Set(); public DbSet Groups => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs index ce54a154d8..8e1fcd8350 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.MultiTenancy; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class MultiTenancyDbContext : DbContext +public sealed class MultiTenancyDbContext : TestableDbContext { private readonly ITenantProvider _tenantProvider; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/KebabCasingConventionStartup.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/KebabCasingConventionStartup.cs index cc0dfcd11d..da3d3b10f9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/KebabCasingConventionStartup.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/KebabCasingConventionStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.NamingConventions; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class KebabCasingConventionStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs index d231a5f822..059abf5d51 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/NamingDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.NamingConventions; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NamingDbContext : DbContext +public sealed class NamingDbContext : TestableDbContext { public DbSet SwimmingPools => Set(); public DbSet WaterSlides => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/PascalCasingConventionStartup.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/PascalCasingConventionStartup.cs index c1c8cb8fa1..dad29067cf 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/PascalCasingConventionStartup.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NamingConventions/PascalCasingConventionStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.NamingConventions; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class PascalCasingConventionStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs index f4a24e1d00..0ac00ea2ae 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/EmptyDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.NonJsonApiControllers; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class EmptyDbContext : DbContext +public sealed class EmptyDbContext : TestableDbContext { public EmptyDbContext(DbContextOptions options) : base(options) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs index 39014e85b3..b18d71938c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/NonJsonApiControllers/KnownDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.NonJsonApiControllers; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class KnownDbContext : DbContext +public sealed class KnownDbContext : TestableDbContext { public DbSet KnownResources => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs index 1c6c7eff32..1b939ee9a1 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.Filtering; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class FilterDbContext : DbContext +public sealed class FilterDbContext : TestableDbContext { public DbSet FilterableResources => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs index 1e240f8e8d..131bfe19fe 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class QueryStringDbContext : DbContext +public sealed class QueryStringDbContext : TestableDbContext { public DbSet Blogs => Set(); public DbSet Posts => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs index 276c3f0a91..d25acf8a06 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs @@ -1,5 +1,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true @@ -7,7 +8,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ReadWrite; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ReadWriteDbContext : DbContext +public sealed class ReadWriteDbContext : TestableDbContext { public DbSet WorkItems => Set(); public DbSet WorkTags => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs index 55ef653c95..6ed4deaeff 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.RequiredRelationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class DefaultBehaviorDbContext : DbContext +public sealed class DefaultBehaviorDbContext : TestableDbContext { public DbSet Customers => Set(); public DbSet Orders => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs index faa496aef9..ea98dd6c08 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs @@ -2,11 +2,12 @@ using JsonApiDotNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceConstructorInjection; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class InjectionDbContext : DbContext +public sealed class InjectionDbContext : TestableDbContext { public ISystemClock SystemClock { get; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs index 7964bfdd09..3dc619dcba 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UniverseDbContext : DbContext +public sealed class UniverseDbContext : TestableDbContext { public DbSet Stars => Set(); public DbSet Planets => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs index c8695f2ab4..7f62a63b73 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Serialization; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class SerializationDbContext : DbContext +public sealed class SerializationDbContext : TestableDbContext { public DbSet Students => Set(); public DbSet Scholarships => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs index 23fa61b7ad..f5e0fc1bbe 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs @@ -1,11 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance.Models; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceInheritance; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public abstract class ResourceInheritanceDbContext : DbContext +public abstract class ResourceInheritanceDbContext : TestableDbContext { public DbSet Vehicles => Set(); public DbSet Bikes => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs index 8db6489aba..433d50ecd3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/RestrictedControllers/RestrictionDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.RestrictedControllers; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class RestrictionDbContext : DbContext +public sealed class RestrictionDbContext : TestableDbContext { public DbSet Tables => Set
(); public DbSet Chairs => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs index 83c39f1791..885c3b950a 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Serialization/SerializationDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.IntegrationTests.Serialization; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class SerializationDbContext : DbContext +public sealed class SerializationDbContext : TestableDbContext { public DbSet Meetings => Set(); public DbSet Attendees => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs index e9a03df2f6..2a19ba74b4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.SoftDeletion; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class SoftDeletionDbContext : DbContext +public sealed class SoftDeletionDbContext : TestableDbContext { public DbSet Companies => Set(); public DbSet Departments => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs index bfe4fe7a3b..301a7b6d7b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs @@ -1,12 +1,13 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always namespace JsonApiDotNetCoreTests.IntegrationTests.ZeroKeys; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ZeroKeyDbContext : DbContext +public sealed class ZeroKeyDbContext : TestableDbContext { public DbSet Games => Set(); public DbSet Players => Set(); diff --git a/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksInApiNamespaceStartup.cs b/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksInApiNamespaceStartup.cs index 00af98c4e1..5e165653b3 100644 --- a/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksInApiNamespaceStartup.cs +++ b/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksInApiNamespaceStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.Startups; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class AbsoluteLinksInApiNamespaceStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksNoNamespaceStartup.cs b/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksNoNamespaceStartup.cs index 392c4ad8b2..c8234ed695 100644 --- a/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksNoNamespaceStartup.cs +++ b/test/JsonApiDotNetCoreTests/Startups/AbsoluteLinksNoNamespaceStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.Startups; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class AbsoluteLinksNoNamespaceStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/Startups/NoModelStateValidationStartup.cs b/test/JsonApiDotNetCoreTests/Startups/NoModelStateValidationStartup.cs index e92b0301c7..ac6f9c82f1 100644 --- a/test/JsonApiDotNetCoreTests/Startups/NoModelStateValidationStartup.cs +++ b/test/JsonApiDotNetCoreTests/Startups/NoModelStateValidationStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.Startups; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class NoModelStateValidationStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/Startups/RelativeLinksInApiNamespaceStartup.cs b/test/JsonApiDotNetCoreTests/Startups/RelativeLinksInApiNamespaceStartup.cs index ff0d5b59b5..5fdfd20048 100644 --- a/test/JsonApiDotNetCoreTests/Startups/RelativeLinksInApiNamespaceStartup.cs +++ b/test/JsonApiDotNetCoreTests/Startups/RelativeLinksInApiNamespaceStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.Startups; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class RelativeLinksInApiNamespaceStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/Startups/RelativeLinksNoNamespaceStartup.cs b/test/JsonApiDotNetCoreTests/Startups/RelativeLinksNoNamespaceStartup.cs index 658b874f08..99ae80d207 100644 --- a/test/JsonApiDotNetCoreTests/Startups/RelativeLinksNoNamespaceStartup.cs +++ b/test/JsonApiDotNetCoreTests/Startups/RelativeLinksNoNamespaceStartup.cs @@ -1,13 +1,12 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; namespace JsonApiDotNetCoreTests.Startups; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class RelativeLinksNoNamespaceStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs index d7d6484d2a..d0bb39a187 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs @@ -124,7 +124,7 @@ public CircularServiceB(CircularServiceA serviceA) } [UsedImplicitly(ImplicitUseTargetFlags.Members)] - private sealed class DependencyContainerRegistrationDbContext : DbContext + private sealed class DependencyContainerRegistrationDbContext : TestableDbContext { public DbSet Resources => Set(); diff --git a/test/TestBuildingBlocks/IntegrationTestContext.cs b/test/TestBuildingBlocks/IntegrationTestContext.cs index 8ccbc14079..bccf7d8bf3 100644 --- a/test/TestBuildingBlocks/IntegrationTestContext.cs +++ b/test/TestBuildingBlocks/IntegrationTestContext.cs @@ -26,7 +26,7 @@ namespace TestBuildingBlocks; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public class IntegrationTestContext : IntegrationTest, IDisposable where TStartup : class - where TDbContext : DbContext + where TDbContext : TestableDbContext { private readonly Lazy> _lazyFactory; private readonly TestControllerProvider _testControllerProvider = new(); diff --git a/test/TestBuildingBlocks/TestableDbContext.cs b/test/TestBuildingBlocks/TestableDbContext.cs new file mode 100644 index 0000000000..d40db11c03 --- /dev/null +++ b/test/TestBuildingBlocks/TestableDbContext.cs @@ -0,0 +1,20 @@ +using System.Diagnostics; +using JsonApiDotNetCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace TestBuildingBlocks; + +public abstract class TestableDbContext : DbContext +{ + protected TestableDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder builder) + { + // Writes SQL statements to the Output Window when debugging. + builder.LogTo(message => Debug.WriteLine(message), DbLoggerCategory.Database.Name.AsArray(), LogLevel.Information); + } +} diff --git a/test/TestBuildingBlocks/TestableStartup.cs b/test/TestBuildingBlocks/TestableStartup.cs index 687b9aa406..e5dc7075e8 100644 --- a/test/TestBuildingBlocks/TestableStartup.cs +++ b/test/TestBuildingBlocks/TestableStartup.cs @@ -1,12 +1,11 @@ using JsonApiDotNetCore.Configuration; using Microsoft.AspNetCore.Builder; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace TestBuildingBlocks; public class TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { public virtual void ConfigureServices(IServiceCollection services) { diff --git a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs index 344668c800..a90c3851ee 100644 --- a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -12,6 +12,7 @@ using JsonApiDotNetCore.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using TestBuildingBlocks; using Xunit; namespace UnitTests.Extensions; @@ -589,7 +590,7 @@ public void OnSerialize(ResourceOfGuid resource) } [UsedImplicitly(ImplicitUseTargetFlags.Members)] - private sealed class TestDbContext : DbContext + private sealed class TestDbContext : TestableDbContext { public DbSet ResourcesOfInt32 => Set(); public DbSet ResourcesOfGuid => Set(); From 2e4f15e07cbed92436b37fca20937ef103443c32 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 26 Oct 2022 23:14:28 +0200 Subject: [PATCH 47/50] Update guidance on one-to-one relationship mapping in EF Core to avoid common pitfalls --- docs/usage/resources/relationships.md | 119 +++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 11 deletions(-) diff --git a/docs/usage/resources/relationships.md b/docs/usage/resources/relationships.md index d1f36a89a9..1787bbd8ac 100644 --- a/docs/usage/resources/relationships.md +++ b/docs/usage/resources/relationships.md @@ -3,7 +3,7 @@ A relationship is a named link between two resource types, including a direction. They are similar to [navigation properties in Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/modeling/relationships). -Relationships come in three flavors: to-one, to-many and many-to-many. +Relationships come in two flavors: to-one and to-many. The left side of a relationship is where the relationship is declared, the right side is the resource type it points to. ## HasOne @@ -22,10 +22,14 @@ public class TodoItem : Identifiable The left side of this relationship is of type `TodoItem` (public name: "todoItems") and the right side is of type `Person` (public name: "persons"). -### Required one-to-one relationships in Entity Framework Core +### One-to-one relationships in Entity Framework Core -By default, Entity Framework Core generates an identifying foreign key for a required 1-to-1 relationship. -This means no foreign key column is generated, instead the primary keys point to each other directly. +By default, Entity Framework Core tries to generate an *identifying foreign key* for a one-to-one relationship whenever possible. +In that case, no foreign key column is generated. Instead the primary keys point to each other directly. + +**That mechanism does not make sense for JSON:API, because patching a relationship would result in also +changing the identity of a resource. Naming the foreign key explicitly fixes the problem, which enforces +to create a foreign key column.** The next example defines that each car requires an engine, while an engine is optionally linked to a car. @@ -51,18 +55,19 @@ public sealed class AppDbContext : DbContext builder.Entity() .HasOne(car => car.Engine) .WithOne(engine => engine.Car) - .HasForeignKey() - .IsRequired(); + .HasForeignKey(); } } ``` Which results in Entity Framework Core generating the next database objects: + ```sql CREATE TABLE "Engine" ( "Id" integer GENERATED BY DEFAULT AS IDENTITY, CONSTRAINT "PK_Engine" PRIMARY KEY ("Id") ); + CREATE TABLE "Cars" ( "Id" integer NOT NULL, CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"), @@ -71,9 +76,7 @@ CREATE TABLE "Cars" ( ); ``` -That mechanism does not make sense for JSON:API, because patching a relationship would result in also -changing the identity of a resource. Naming the foreign key explicitly fixes the problem by forcing to -create a foreign key column. +To fix this, name the foreign key explicitly: ```c# protected override void OnModelCreating(ModelBuilder builder) @@ -81,17 +84,18 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasOne(car => car.Engine) .WithOne(engine => engine.Car) - .HasForeignKey("EngineId") // Explicit foreign key name added - .IsRequired(); + .HasForeignKey("EngineId"); // <-- Explicit foreign key name added } ``` Which generates the correct database objects: + ```sql CREATE TABLE "Engine" ( "Id" integer GENERATED BY DEFAULT AS IDENTITY, CONSTRAINT "PK_Engine" PRIMARY KEY ("Id") ); + CREATE TABLE "Cars" ( "Id" integer GENERATED BY DEFAULT AS IDENTITY, "EngineId" integer NOT NULL, @@ -99,6 +103,99 @@ CREATE TABLE "Cars" ( CONSTRAINT "FK_Cars_Engine_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engine" ("Id") ON DELETE CASCADE ); + +CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId"); +``` + +#### Optional one-to-one relationships in Entity Framework Core + +For optional one-to-one relationships, Entity Framework Core uses `DeleteBehavior.ClientSetNull` by default, instead of `DeleteBehavior.SetNull`. +This means that Entity Framework Core tries to handle the cascading effects (by sending multiple SQL statements), instead of leaving it up to the database. +Of course that's only going to work when all the related resources are loaded in the change tracker upfront, which is expensive because it requires fetching more data than necessary. + +The reason for this odd default is poor support in SQL Server, as explained [here](https://stackoverflow.com/questions/54326165/ef-core-why-clientsetnull-is-default-ondelete-behavior-for-optional-relations) and [here](https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete#database-cascade-limitations). + +**Our [testing](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1205) shows that these limitations don't exist when using PostgreSQL. +Therefore the general advice is to map the delete behavior of optional one-to-one relationships explicitly with `.OnDelete(DeleteBehavior.SetNull)`. This is simpler and more efficient.** + +The next example defines that each car optionally has an engine, while an engine is optionally linked to a car. + +```c# +#nullable enable + +public sealed class Car : Identifiable +{ + [HasOne] + public Engine? Engine { get; set; } +} + +public sealed class Engine : Identifiable +{ + [HasOne] + public Car? Car { get; set; } +} + +public sealed class AppDbContext : DbContext +{ + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(car => car.Engine) + .WithOne(engine => engine.Car) + .HasForeignKey("EngineId"); + } +} +``` + +Which results in Entity Framework Core generating the next database objects: + +```sql +CREATE TABLE "Engines" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + CONSTRAINT "PK_Engines" PRIMARY KEY ("Id") +); + +CREATE TABLE "Cars" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + "EngineId" integer NULL, + CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Cars_Engines_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engines" ("Id") +); + +CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId"); +``` + +To fix this, set the delete behavior explicitly: + +``` +public sealed class AppDbContext : DbContext +{ + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(car => car.Engine) + .WithOne(engine => engine.Car) + .HasForeignKey("EngineId") + .OnDelete(DeleteBehavior.SetNull); // <-- Explicit delete behavior set + } +} +``` + +Which generates the correct database objects: + +```sql +CREATE TABLE "Engines" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + CONSTRAINT "PK_Engines" PRIMARY KEY ("Id") +); + +CREATE TABLE "Cars" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + "EngineId" integer NULL, + CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Cars_Engines_EngineId" FOREIGN KEY ("EngineId") REFERENCES "Engines" ("Id") ON DELETE SET NULL +); + CREATE UNIQUE INDEX "IX_Cars_EngineId" ON "Cars" ("EngineId"); ``` From bbec95bc29760c878463bc9e19ac89871451041a Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 26 Oct 2022 23:14:52 +0200 Subject: [PATCH 48/50] Fix naming inconsistency --- .../InjectionDbContext.cs | 2 +- .../ResourceInjectionTests.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs index faa496aef9..1eade6b66b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs @@ -10,7 +10,7 @@ public sealed class InjectionDbContext : DbContext { public ISystemClock SystemClock { get; } - public DbSet PostOffice => Set(); + public DbSet PostOffices => Set(); public DbSet GiftCertificates => Set(); public InjectionDbContext(DbContextOptions options, ISystemClock systemClock) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/ResourceInjectionTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/ResourceInjectionTests.cs index 68e5c1a77a..19b38cf071 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/ResourceInjectionTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/ResourceInjectionTests.cs @@ -72,7 +72,7 @@ public async Task Can_filter_resources_by_ID() await _testContext.RunOnDatabaseAsync(async dbContext => { await dbContext.ClearTableAsync(); - dbContext.PostOffice.AddRange(postOffices); + dbContext.PostOffices.AddRange(postOffices); await dbContext.SaveChangesAsync(); }); @@ -133,7 +133,7 @@ public async Task Can_create_resource_with_ToOne_relationship_and_include() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.PostOffice.Add(existingOffice); + dbContext.PostOffices.Add(existingOffice); await dbContext.SaveChangesAsync(); }); @@ -216,7 +216,7 @@ public async Task Can_update_resource_with_ToMany_relationship() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.PostOffice.Add(existingOffice); + dbContext.PostOffices.Add(existingOffice); await dbContext.SaveChangesAsync(); }); @@ -259,7 +259,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - PostOffice officeInDatabase = await dbContext.PostOffice.Include(postOffice => postOffice.GiftCertificates).FirstWithIdAsync(existingOffice.Id); + PostOffice officeInDatabase = await dbContext.PostOffices.Include(postOffice => postOffice.GiftCertificates).FirstWithIdAsync(existingOffice.Id); officeInDatabase.Address.Should().Be(newAddress); @@ -276,7 +276,7 @@ public async Task Can_delete_resource() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.PostOffice.Add(existingOffice); + dbContext.PostOffices.Add(existingOffice); await dbContext.SaveChangesAsync(); }); @@ -292,7 +292,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - PostOffice? officeInDatabase = await dbContext.PostOffice.FirstWithIdOrDefaultAsync(existingOffice.Id); + PostOffice? officeInDatabase = await dbContext.PostOffices.FirstWithIdOrDefaultAsync(existingOffice.Id); officeInDatabase.Should().BeNull(); }); @@ -359,7 +359,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - PostOffice officeInDatabase = await dbContext.PostOffice.Include(postOffice => postOffice.GiftCertificates).FirstWithIdAsync(existingOffice.Id); + PostOffice officeInDatabase = await dbContext.PostOffices.Include(postOffice => postOffice.GiftCertificates).FirstWithIdAsync(existingOffice.Id); officeInDatabase.GiftCertificates.ShouldHaveCount(2); }); From 75fad29cfa38f29fadfc1b70a2abb4e4fd326ca7 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 27 Oct 2022 12:37:40 +0200 Subject: [PATCH 49/50] Updated example to match with current implementation --- docs/internals/queries.md | 168 ++++++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 54 deletions(-) diff --git a/docs/internals/queries.md b/docs/internals/queries.md index 46005f489c..5c2b238a18 100644 --- a/docs/internals/queries.md +++ b/docs/internals/queries.md @@ -22,7 +22,7 @@ Processing a request involves the following steps: - `JsonApiResourceService` contains no more usage of `IQueryable`. - `EntityFrameworkCoreRepository` delegates to `QueryableBuilder` to transform the `QueryLayer` tree into `IQueryable` expression trees. `QueryBuilder` depends on `QueryClauseBuilder` implementations that visit the tree nodes, transforming them to `System.Linq.Expression` equivalents. - The `IQueryable` expression trees are executed by Entity Framework Core, which produces SQL statements out of them. + The `IQueryable` expression trees are passed to Entity Framework Core, which produces SQL statements out of them. - `JsonApiWriter` transforms resource objects into json response. # Example @@ -30,17 +30,17 @@ To get a sense of what this all looks like, let's look at an example query strin ``` /api/v1/blogs? - include=owner,articles.revisions.author& - filter=has(articles)& - sort=count(articles)& + include=owner,posts.comments.author& + filter=has(posts)& + sort=count(posts)& page[number]=3& fields[blogs]=title& - filter[articles]=and(not(equals(author.firstName,null)),has(revisions))& - sort[articles]=author.lastName& - fields[articles]=url& - filter[articles.revisions]=and(greaterThan(publishTime,'2001-01-01'),startsWith(author.firstName,'J'))& - sort[articles.revisions]=-publishTime,author.lastName& - fields[revisions]=publishTime + filter[posts]=and(not(equals(author.userName,null)),has(comments))& + sort[posts]=author.displayName& + fields[blogPosts]=url& + filter[posts.comments]=and(greaterThan(createdAt,'2001-01-01Z'),startsWith(author.userName,'J'))& + sort[posts.comments]=-createdAt,author.displayName& + fields[comments]=createdAt ``` After parsing, the set of scoped expressions is transformed into the following tree by `QueryLayerComposer`: @@ -48,40 +48,50 @@ After parsing, the set of scoped expressions is transformed into the following t ``` QueryLayer { - Include: owner,articles.revisions - Filter: has(articles) - Sort: count(articles) + Include: owner,posts.comments.author + Filter: has(posts) + Sort: count(posts) Pagination: Page number: 3, size: 5 - Projection + Selection { - title - id - owner: QueryLayer + FieldSelectors { - Sort: id - Pagination: Page number: 1, size: 5 - } - articles: QueryLayer
- { - Filter: and(not(equals(author.firstName,null)),has(revisions)) - Sort: author.lastName - Pagination: Page number: 1, size: 5 - Projection + title + id + posts: QueryLayer { - url - id - revisions: QueryLayer + Filter: and(not(equals(author.userName,null)),has(comments)) + Sort: author.displayName + Pagination: Page number: 1, size: 5 + Selection { - Filter: and(greaterThan(publishTime,'2001-01-01'),startsWith(author.firstName,'J')) - Sort: -publishTime,author.lastName - Pagination: Page number: 1, size: 5 - Projection + FieldSelectors { - publishTime + url id + comments: QueryLayer + { + Filter: and(greaterThan(createdAt,'2001-01-01'),startsWith(author.userName,'J')) + Sort: -createdAt,author.displayName + Pagination: Page number: 1, size: 5 + Selection + { + FieldSelectors + { + createdAt + id + author: QueryLayer + { + } + } + } + } } } } + owner: QueryLayer + { + } } } } @@ -90,36 +100,86 @@ QueryLayer Next, the repository translates this into a LINQ query that the following C# code would represent: ```c# -var query = dbContext.Blogs +IQueryable query = dbContext.Blogs + .Include("Posts.Comments.Author") .Include("Owner") - .Include("Articles.Revisions") - .Where(blog => blog.Articles.Any()) - .OrderBy(blog => blog.Articles.Count) + .Where(blog => blog.Posts.Any()) + .OrderBy(blog => blog.Posts.Count) .Skip(10) .Take(5) .Select(blog => new Blog { Title = blog.Title, Id = blog.Id, - Owner = blog.Owner, - Articles = new List
(blog.Articles - .Where(article => article.Author.FirstName != null && article.Revisions.Any()) - .OrderBy(article => article.Author.LastName) + Posts = blog.Posts + .Where(blogPost => blogPost.Author.UserName != null && blogPost.Comments.Any()) + .OrderBy(blogPost => blogPost.Author.DisplayName) .Take(5) - .Select(article => new Article + .Select(blogPost => new BlogPost { - Url = article.Url, - Id = article.Id, - Revisions = new HashSet(article.Revisions - .Where(revision => revision.PublishTime > DateTime.Parse("2001-01-01") && revision.Author.FirstName.StartsWith("J")) - .OrderByDescending(revision => revision.PublishTime) - .ThenBy(revision => revision.Author.LastName) + Url = blogPost.Url, + Id = blogPost.Id, + Comments = blogPost.Comments + .Where(comment => comment.CreatedAt > DateTime.Parse("2001-01-01Z") && + comment.Author.UserName.StartsWith("J")) + .OrderByDescending(comment => comment.CreatedAt) + .ThenBy(comment => comment.Author.DisplayName) .Take(5) - .Select(revision => new Revision + .Select(comment => new Comment { - PublishTime = revision.PublishTime, - Id = revision.Id - })) - })) + CreatedAt = comment.CreatedAt, + Id = comment.Id, + Author = comment.Author + }).ToHashSet() + }).ToList(), + Owner = blog.Owner }); ``` + +The LINQ query gets translated by Entity Framework Core into the following SQL: + +```sql +SELECT t."Title", t."Id", a."Id", t2."Url", t2."Id", t2."Id0", t2."CreatedAt", t2."Id1", t2."Id00", t2."DateOfBirth", t2."DisplayName", t2."EmailAddress", t2."Password", t2."PersonId", t2."PreferencesId", t2."UserName", a."DateOfBirth", a."DisplayName", a."EmailAddress", a."Password", a."PersonId", a."PreferencesId", a."UserName" +FROM ( + SELECT b."Id", b."OwnerId", b."Title", ( + SELECT COUNT(*)::INT + FROM "Posts" AS p0 + WHERE b."Id" = p0."ParentId") AS c + FROM "Blogs" AS b + WHERE EXISTS ( + SELECT 1 + FROM "Posts" AS p + WHERE b."Id" = p."ParentId") + ORDER BY ( + SELECT COUNT(*)::INT + FROM "Posts" AS p0 + WHERE b."Id" = p0."ParentId") + LIMIT @__Create_Item1_1 OFFSET @__Create_Item1_0 +) AS t +LEFT JOIN "Accounts" AS a ON t."OwnerId" = a."Id" +LEFT JOIN LATERAL ( + SELECT t0."Url", t0."Id", t0."Id0", t1."CreatedAt", t1."Id" AS "Id1", t1."Id0" AS "Id00", t1."DateOfBirth", t1."DisplayName", t1."EmailAddress", t1."Password", t1."PersonId", t1."PreferencesId", t1."UserName", t0."DisplayName" AS "DisplayName0", t1."ParentId" + FROM ( + SELECT p1."Url", p1."Id", a0."Id" AS "Id0", a0."DisplayName" + FROM "Posts" AS p1 + LEFT JOIN "Accounts" AS a0 ON p1."AuthorId" = a0."Id" + WHERE (t."Id" = p1."ParentId") AND (((a0."UserName" IS NOT NULL)) AND EXISTS ( + SELECT 1 + FROM "Comments" AS c + WHERE p1."Id" = c."ParentId")) + ORDER BY a0."DisplayName" + LIMIT @__Create_Item1_1 + ) AS t0 + LEFT JOIN ( + SELECT t3."CreatedAt", t3."Id", t3."Id0", t3."DateOfBirth", t3."DisplayName", t3."EmailAddress", t3."Password", t3."PersonId", t3."PreferencesId", t3."UserName", t3."ParentId" + FROM ( + SELECT c0."CreatedAt", c0."Id", a1."Id" AS "Id0", a1."DateOfBirth", a1."DisplayName", a1."EmailAddress", a1."Password", a1."PersonId", a1."PreferencesId", a1."UserName", c0."ParentId", ROW_NUMBER() OVER(PARTITION BY c0."ParentId" ORDER BY c0."CreatedAt" DESC, a1."DisplayName") AS row + FROM "Comments" AS c0 + LEFT JOIN "Accounts" AS a1 ON c0."AuthorId" = a1."Id" + WHERE (c0."CreatedAt" > @__Create_Item1_2) AND ((@__Create_Item1_3 = '') OR (((a1."UserName" IS NOT NULL)) AND ((a1."UserName" LIKE @__Create_Item1_3 || '%' ESCAPE '') AND (left(a1."UserName", length(@__Create_Item1_3))::text = @__Create_Item1_3::text)))) + ) AS t3 + WHERE t3.row <= @__Create_Item1_1 + ) AS t1 ON t0."Id" = t1."ParentId" +) AS t2 ON TRUE +ORDER BY t.c, t."Id", a."Id", t2."DisplayName0", t2."Id", t2."Id0", t2."ParentId", t2."CreatedAt" DESC, t2."DisplayName", t2."Id1" +``` From 13a47d290bdbd851d6510198bb2d28270a7f5e87 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Tue, 8 Nov 2022 21:59:52 +0100 Subject: [PATCH 50/50] Update version to 5.1.0 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3a3d018a65..99319a64e6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ 6.0.* 4.3.* 2.14.1 - 5.0.4 + 5.1.0 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy