diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3bd80e6084..f8589a73a7 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,13 +3,13 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2021.3.4", + "version": "2022.2.4", "commands": [ "jb" ] }, "regitlint": { - "version": "6.1.1", + "version": "6.2.1", "commands": [ "regitlint" ] @@ -21,7 +21,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.1.3", + "version": "5.1.11", "commands": [ "reportgenerator" ] diff --git a/.editorconfig b/.editorconfig index 70d678243d..7e3b5e1cf2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,10 +6,9 @@ indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true -end_of_line = lf insert_final_newline = true -[*.{csproj,json}] +[*.{config,csproj,css,js,json,props,ruleset,xslt}] indent_size = 2 [test/OpenApiClientTests/obj/*.{cs}] @@ -17,10 +16,14 @@ indent_size = 2 dotnet_diagnostic.CS8625.severity = suggestion [*.{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 @@ -34,6 +37,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 @@ -41,6 +45,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 @@ -58,38 +63,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 @@ -100,25 +105,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 = _ diff --git a/.gitignore b/.gitignore index 2bd200a72d..85bd0f1080 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,50 @@ 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 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 @@ + + + + diff --git a/Build.ps1 b/Build.ps1 index 69ea0f2fe4..82dcab0864 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" @@ -52,7 +51,7 @@ function RunCleanupCode { 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 + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff CheckLastExitCode } } @@ -114,8 +113,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 ($IsWindows) { + RunInspectCode + RunCleanupCode +} dotnet test -c Release --no-build --collect:"XPlat Code Coverage" CheckLastExitCode diff --git a/Directory.Build.props b/Directory.Build.props index b10fb859f3..620177f16b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ 13.16.* 6.0.* 13.0.* - 5.0.4 + 5.1.1 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable @@ -20,7 +20,7 @@ - + @@ -37,8 +37,8 @@ - 3.1.2 + 3.2.0 4.18.2 - 17.3.1 + 17.4.0 diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln index 95b664c16f..bc06e7d92e 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 @@ -52,6 +53,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 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.OpenApi", "src\JsonApiDotNetCore.OpenApi\JsonApiDotNetCore.OpenApi.csproj", "{71287D6F-6C3B-44B4-9FCA-E78FE3F02289}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiTests", "test\OpenApiTests\OpenApiTests.csproj", "{B693DE14-BB28-496F-AB39-B4E674ABCA80}" @@ -276,6 +279,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 {71287D6F-6C3B-44B4-9FCA-E78FE3F02289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {71287D6F-6C3B-44B4-9FCA-E78FE3F02289}.Debug|Any CPU.Build.0 = Debug|Any CPU {71287D6F-6C3B-44B4-9FCA-E78FE3F02289}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -358,6 +373,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} {71287D6F-6C3B-44B4-9FCA-E78FE3F02289} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} {B693DE14-BB28-496F-AB39-B4E674ABCA80} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {5ADAA902-5A75-4ECB-B4B4-03291D63CE9C} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index 5e593dd32d..8cd5bba842 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 @@ -57,6 +57,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$, $NAME$); WARNING WARNING WARNING + HINT WARNING DO_NOT_SHOW HINT @@ -602,12 +603,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 @@ -617,7 +618,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 @@ -633,6 +634,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/README.md b/README.md index e2291f080e..26432ee909 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) @@ -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 | +| 5.0.0-5.0.2 | Stable | 6 | 6 | +| 5.0.3+ | Stable | 6 | 6 | +| | | 6 | 7 | +| | | 7 | 7 | ## Contributing 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 diff --git a/appveyor.yml b/appveyor.yml index 445f615ef9..433d391b16 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,10 @@ image: - - Ubuntu + - Ubuntu2004 - Visual Studio 2022 version: '{build}' -stack: postgresql 13.4 +stack: postgresql 15 environment: PGUSER: postgres @@ -35,7 +35,10 @@ for: only: - image: Visual Studio 2022 services: - - postgresql13 + - postgresql15 + install: + # Temporary workaround for https://help.appveyor.com/discussions/questions/60488-postgresql-version + - net start postgresql-x64-15 # REF: https://github.com/docascode/docfx-seed/blob/master/appveyor.yml before_build: - pwsh: | @@ -102,7 +105,7 @@ build_script: Write-Output "PostgreSQL version:" if ($IsWindows) { - . "${env:ProgramFiles}\PostgreSQL\13\bin\psql" --version + . "${env:ProgramFiles}\PostgreSQL\15\bin\psql" --version } else { psql --version diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index bab8b82af1..5740ab5a90 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -28,17 +28,17 @@ if ($revision) { 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 + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --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 + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --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 + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN VerifySuccessExitCode } 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" +``` 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": [ { 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/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/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..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"); ``` @@ -160,7 +257,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 that 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/run-docker-postgres.ps1 b/run-docker-postgres.ps1 index 89a325e3b5..153b93a846 100644 --- a/run-docker-postgres.ps1 +++ b/run-docker-postgres.ps1 @@ -9,4 +9,4 @@ docker run --rm --name jsonapi-dotnet-core-testing ` -e POSTGRES_USER=postgres ` -e POSTGRES_PASSWORD=postgres ` -p 5432:5432 ` - postgres:13.4 + postgres:15 diff --git a/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs b/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs index c70fc8655f..ba73b8bf3a 100644 --- a/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs +++ b/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs @@ -36,7 +36,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) private string GetConnectionString() { string? tenantName = GetTenantName(); - string connectionString = _configuration[$"Data:{tenantName ?? "Default"}Connection"]; + string? connectionString = _configuration[$"Data:{tenantName ?? "Default"}Connection"]; if (connectionString == null) { 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/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs index 6df109e5ba..34a40755cb 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs @@ -11,12 +11,12 @@ namespace NoEntityFrameworkExample.Services; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class WorkItemService : IResourceService { - private readonly string _connectionString; + private readonly string? _connectionString; public WorkItemService(IConfiguration configuration) { string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres"; - _connectionString = configuration["Data:DefaultConnection"].Replace("###", postgresPassword); + _connectionString = configuration["Data:DefaultConnection"]?.Replace("###", postgresPassword); } public async Task> GetAsync(CancellationToken cancellationToken) 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/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..1fe6858b96 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,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs index d3d6133f6e..7a6cbd960f 100644 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrAttribute.cs @@ -14,17 +14,16 @@ 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. /// /// - /// - /// public class Author : Identifiable + /// /// { /// [Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort)] - /// public string Name { get; set; } + /// public string Name { get; set; } = null!; /// } - /// + /// ]]> /// public AttrCapabilities Capabilities { 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.cs deleted file mode 100644 index c6f849fdff..0000000000 --- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace JsonApiDotNetCore.Resources.Annotations; - -/// -/// Indicates capabilities that can be performed on an . -/// -[Flags] -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. - /// - 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, - - /// - /// Whether or not PATCH requests can update the attribute value. Attempts to update when disabled will return an HTTP 422 response. - /// - AllowChange = 4, - - /// - /// 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. - /// - AllowFilter = 8, - - /// - /// 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. - /// - AllowSort = 16, - - All = AllowView | AllowCreate | AllowChange | AllowFilter | AllowSort -} diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs new file mode 100644 index 0000000000..0951010b3b --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/AttrCapabilities.shared.cs @@ -0,0 +1,43 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Resources.Annotations; + +/// +/// Indicates what can be performed on an . +/// +[PublicAPI] +[Flags] +public enum AttrCapabilities +{ + None = 0, + + /// + /// 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 = 1 << 1, + + /// + /// Whether or not PATCH requests can update the attribute value. Attempts to update when disabled will return an HTTP 422 response. + /// + AllowChange = 1 << 2, + + /// + /// 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 = 1 << 3, + + /// + /// 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 = 1 << 4, + + All = AllowView | AllowCreate | AllowChange | AllowFilter | AllowSort +} 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.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 new file mode 100644 index 0000000000..cf83f0ce17 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasManyAttribute.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 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 new file mode 100644 index 0000000000..42be2f3c5f --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/HasOneAttribute.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 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/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.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs index 11320a7abc..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. @@ -57,7 +61,7 @@ public ResourceType RightType get => _rightType!; internal set { - ArgumentGuard.NotNull(value, nameof(value)); + ArgumentGuard.NotNull(value); _rightType = value; } } @@ -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 new file mode 100644 index 0000000000..d7af592564 --- /dev/null +++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs @@ -0,0 +1,17 @@ +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; + + /// + [Obsolete("Use AllowInclude in Capabilities instead.")] + 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.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceFieldAttribute.cs index 599b17a42a..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 { @@ -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/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/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.OpenApi.Client/ArgumentGuard.cs b/src/JsonApiDotNetCore.OpenApi.Client/ArgumentGuard.cs index da71b2c8e5..71a217a9b3 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/ArgumentGuard.cs @@ -1,19 +1,18 @@ +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.OpenApi.Client; 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); - } + ArgumentNullException.ThrowIfNull(value, parameterName); } } diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs index 7a1ba5883d..219ac04353 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiClient.cs @@ -17,7 +17,7 @@ public abstract class JsonApiClient : IJsonApiClient protected void SetSerializerSettingsForJsonApi(JsonSerializerSettings settings) { - ArgumentGuard.NotNull(settings, nameof(settings)); + ArgumentGuard.NotNull(settings); settings.Converters.Add(_jsonApiJsonConverter); } @@ -27,7 +27,7 @@ public IDisposable RegisterAttributesForRequestDocument>[] alwaysIncludedAttributeSelectors) where TRequestDocument : class { - ArgumentGuard.NotNull(requestDocument, nameof(requestDocument)); + ArgumentGuard.NotNull(requestDocument); var attributeNames = new HashSet(); @@ -105,7 +105,7 @@ public void RemoveAttributeRegistration(object requestDocument) public override bool CanConvert(Type objectType) { - ArgumentGuard.NotNull(objectType, nameof(objectType)); + ArgumentGuard.NotNull(objectType); return !_isSerializing && _requestDocumentInstancesPerRequestDocumentType.ContainsKey(objectType); } @@ -117,8 +117,8 @@ public override object ReadJson(JsonReader reader, Type objectType, object? exis public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { - ArgumentGuard.NotNull(writer, nameof(writer)); - ArgumentGuard.NotNull(serializer, nameof(serializer)); + ArgumentGuard.NotNull(writer); + ArgumentGuard.NotNull(serializer); if (value != null) { @@ -148,8 +148,8 @@ private sealed class AttributeNamesContainer public AttributeNamesContainer(ISet attributeNames, Type containerType) { - ArgumentGuard.NotNull(attributeNames, nameof(attributeNames)); - ArgumentGuard.NotNull(containerType, nameof(containerType)); + ArgumentGuard.NotNull(attributeNames); + ArgumentGuard.NotNull(containerType); _attributeNames = attributeNames; _containerType = containerType; @@ -173,8 +173,8 @@ private sealed class AttributesRegistrationScope : IDisposable public AttributesRegistrationScope(JsonApiJsonConverter jsonApiJsonConverter, object requestDocument) { - ArgumentGuard.NotNull(jsonApiJsonConverter, nameof(jsonApiJsonConverter)); - ArgumentGuard.NotNull(requestDocument, nameof(requestDocument)); + ArgumentGuard.NotNull(jsonApiJsonConverter); + ArgumentGuard.NotNull(requestDocument); _jsonApiJsonConverter = jsonApiJsonConverter; _requestDocument = requestDocument; @@ -192,7 +192,7 @@ private sealed class JsonApiDocumentContractResolver : DefaultContractResolver public JsonApiDocumentContractResolver(AttributeNamesContainer attributeNamesContainer) { - ArgumentGuard.NotNull(attributeNamesContainer, nameof(attributeNamesContainer)); + ArgumentGuard.NotNull(attributeNamesContainer); _attributeNamesContainer = attributeNamesContainer; } diff --git a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiDotNetCore.OpenApi.Client.csproj b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiDotNetCore.OpenApi.Client.csproj index 22bbc3665a..7246bf0781 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/JsonApiDotNetCore.OpenApi.Client.csproj +++ b/src/JsonApiDotNetCore.OpenApi.Client/JsonApiDotNetCore.OpenApi.Client.csproj @@ -28,10 +28,8 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/src/JsonApiDotNetCore.OpenApi/ActionDescriptorExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ActionDescriptorExtensions.cs index 4ace6ada42..9f83e18031 100644 --- a/src/JsonApiDotNetCore.OpenApi/ActionDescriptorExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ActionDescriptorExtensions.cs @@ -10,7 +10,7 @@ internal static class ActionDescriptorExtensions { public static MethodInfo GetActionMethod(this ActionDescriptor descriptor) { - ArgumentGuard.NotNull(descriptor, nameof(descriptor)); + ArgumentGuard.NotNull(descriptor); return ((ControllerActionDescriptor)descriptor).MethodInfo; } @@ -18,7 +18,7 @@ public static MethodInfo GetActionMethod(this ActionDescriptor descriptor) public static TFilterMetaData? GetFilterMetadata(this ActionDescriptor descriptor) where TFilterMetaData : IFilterMetadata { - ArgumentGuard.NotNull(descriptor, nameof(descriptor)); + ArgumentGuard.NotNull(descriptor); IFilterMetadata? filterMetadata = descriptor.FilterDescriptors.Select(filterDescriptor => filterDescriptor.Filter) .FirstOrDefault(filter => filter is TFilterMetaData); @@ -28,7 +28,7 @@ public static MethodInfo GetActionMethod(this ActionDescriptor descriptor) public static ControllerParameterDescriptor? GetBodyParameterDescriptor(this ActionDescriptor descriptor) { - ArgumentGuard.NotNull(descriptor, nameof(descriptor)); + ArgumentGuard.NotNull(descriptor); return (ControllerParameterDescriptor?)descriptor.Parameters.FirstOrDefault(parameterDescriptor => // ReSharper disable once ConstantConditionalAccessQualifier diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs index dca0b5aef7..98ae9938b4 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiActionDescriptorCollectionProvider.cs @@ -24,8 +24,8 @@ internal sealed class JsonApiActionDescriptorCollectionProvider : IActionDescrip public JsonApiActionDescriptorCollectionProvider(IControllerResourceMapping controllerResourceMapping, IActionDescriptorCollectionProvider defaultProvider) { - ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); - ArgumentGuard.NotNull(defaultProvider, nameof(defaultProvider)); + ArgumentGuard.NotNull(controllerResourceMapping); + ArgumentGuard.NotNull(defaultProvider); _defaultProvider = defaultProvider; _jsonApiEndpointMetadataProvider = new JsonApiEndpointMetadataProvider(controllerResourceMapping); diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiDotNetCore.OpenApi.csproj b/src/JsonApiDotNetCore.OpenApi/JsonApiDotNetCore.OpenApi.csproj index 95e9b33f7b..d2ad102392 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiDotNetCore.OpenApi.csproj +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiDotNetCore.OpenApi.csproj @@ -32,10 +32,8 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/EndpointResolver.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/EndpointResolver.cs index bbe2e35ba0..38b3b6677e 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/EndpointResolver.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/EndpointResolver.cs @@ -9,7 +9,7 @@ internal sealed class EndpointResolver { public JsonApiEndpoint? Get(MethodInfo controllerAction) { - ArgumentGuard.NotNull(controllerAction, nameof(controllerAction)); + ArgumentGuard.NotNull(controllerAction); // This is a temporary work-around to prevent the JsonApiDotNetCoreExample project from crashing upon startup. if (!IsJsonApiController(controllerAction) || IsOperationsController(controllerAction)) diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs index bc23d3e0ec..d0fe2f1234 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs @@ -18,14 +18,14 @@ internal sealed class JsonApiEndpointMetadataProvider public JsonApiEndpointMetadataProvider(IControllerResourceMapping controllerResourceMapping) { - ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); + ArgumentGuard.NotNull(controllerResourceMapping); _controllerResourceMapping = controllerResourceMapping; } public JsonApiEndpointMetadataContainer Get(MethodInfo controllerAction) { - ArgumentGuard.NotNull(controllerAction, nameof(controllerAction)); + ArgumentGuard.NotNull(controllerAction); JsonApiEndpoint? endpoint = _endpointResolver.Get(controllerAction); diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/NonPrimaryEndpointMetadata.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/NonPrimaryEndpointMetadata.cs index 650108b9f9..72cb2c0c12 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/NonPrimaryEndpointMetadata.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/NonPrimaryEndpointMetadata.cs @@ -6,7 +6,7 @@ internal abstract class NonPrimaryEndpointMetadata protected NonPrimaryEndpointMetadata(IDictionary documentTypesByRelationshipName) { - ArgumentGuard.NotNull(documentTypesByRelationshipName, nameof(documentTypesByRelationshipName)); + ArgumentGuard.NotNull(documentTypesByRelationshipName); DocumentTypesByRelationshipName = documentTypesByRelationshipName; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryRequestMetadata.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryRequestMetadata.cs index 8497de020a..f5784b0177 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryRequestMetadata.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryRequestMetadata.cs @@ -6,7 +6,7 @@ internal sealed class PrimaryRequestMetadata : IJsonApiRequestMetadata public PrimaryRequestMetadata(Type documentType) { - ArgumentGuard.NotNull(documentType, nameof(documentType)); + ArgumentGuard.NotNull(documentType); DocumentType = documentType; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryResponseMetadata.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryResponseMetadata.cs index dc02f69f7b..914191a0c1 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryResponseMetadata.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiMetadata/PrimaryResponseMetadata.cs @@ -6,7 +6,7 @@ internal sealed class PrimaryResponseMetadata : IJsonApiResponseMetadata public PrimaryResponseMetadata(Type documentType) { - ArgumentGuard.NotNull(documentType, nameof(documentType)); + ArgumentGuard.NotNull(documentType); DocumentType = documentType; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs index 6ec4dbce2a..95d7e2f6e5 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NonPrimaryDocumentTypeFactory.cs @@ -23,21 +23,21 @@ private NonPrimaryDocumentTypeFactory() public Type GetForSecondaryResponse(RelationshipAttribute relationship) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); return Get(relationship, SecondaryResponseDocumentOpenTypes); } public Type GetForRelationshipRequest(RelationshipAttribute relationship) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); return Get(relationship, RelationshipRequestDocumentOpenTypes); } public Type GetForRelationshipResponse(RelationshipAttribute relationship) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); return Get(relationship, RelationshipResponseDocumentOpenTypes); } @@ -65,9 +65,9 @@ private sealed class DocumentOpenTypes public DocumentOpenTypes(Type manyDataOpenType, Type nullableSingleDataOpenType, Type singleDataOpenType) { - ArgumentGuard.NotNull(manyDataOpenType, nameof(manyDataOpenType)); - ArgumentGuard.NotNull(nullableSingleDataOpenType, nameof(nullableSingleDataOpenType)); - ArgumentGuard.NotNull(singleDataOpenType, nameof(singleDataOpenType)); + ArgumentGuard.NotNull(manyDataOpenType); + ArgumentGuard.NotNull(nullableSingleDataOpenType); + ArgumentGuard.NotNull(singleDataOpenType); ManyDataOpenType = manyDataOpenType; NullableSingleDataOpenType = nullableSingleDataOpenType; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs index 28bb85d83e..7593c9f9f5 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/RelationshipTypeFactory.cs @@ -13,14 +13,14 @@ private RelationshipTypeFactory() public Type GetForRequest(RelationshipAttribute relationship) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); return NonPrimaryDocumentTypeFactory.Instance.GetForRelationshipRequest(relationship); } public Type GetForResponse(RelationshipAttribute relationship) { - ArgumentGuard.NotNull(relationship, nameof(relationship)); + ArgumentGuard.NotNull(relationship); // @formatter:nested_ternary_style expanded diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs index 9724effbd5..197d1d27b8 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs @@ -39,7 +39,7 @@ internal sealed class JsonApiOperationIdSelector public JsonApiOperationIdSelector(IControllerResourceMapping controllerResourceMapping, JsonNamingPolicy? namingPolicy) { - ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); + ArgumentGuard.NotNull(controllerResourceMapping); _controllerResourceMapping = controllerResourceMapping; _namingPolicy = namingPolicy; @@ -47,7 +47,7 @@ public JsonApiOperationIdSelector(IControllerResourceMapping controllerResourceM public string GetOperationId(ApiDescription endpoint) { - ArgumentGuard.NotNull(endpoint, nameof(endpoint)); + ArgumentGuard.NotNull(endpoint); ResourceType? primaryResourceType = _controllerResourceMapping.GetResourceTypeForController(endpoint.ActionDescriptor.GetActionMethod().ReflectedType); diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs index 26dcac4c48..297f23a613 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs @@ -33,8 +33,8 @@ public Task ReadAsync(InputFormatterContext context) /// public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) { - ArgumentGuard.NotNullNorEmpty(contentType, nameof(contentType)); - ArgumentGuard.NotNull(objectType, nameof(objectType)); + ArgumentGuard.NotNullNorEmpty(contentType); + ArgumentGuard.NotNull(objectType); if (contentType == HeaderConstants.MediaType && objectType.IsGenericType && JsonApiRequestObjectOpenType.Contains(objectType.GetGenericTypeDefinition())) diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs index 2eb3f13824..c62bf662ff 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs @@ -43,7 +43,7 @@ internal sealed class JsonApiSchemaIdSelector public JsonApiSchemaIdSelector(JsonNamingPolicy? namingPolicy, IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(resourceGraph); _namingPolicy = namingPolicy; _resourceGraph = resourceGraph; @@ -51,7 +51,7 @@ public JsonApiSchemaIdSelector(JsonNamingPolicy? namingPolicy, IResourceGraph re public string GetSchemaId(Type type) { - ArgumentGuard.NotNull(type, nameof(type)); + ArgumentGuard.NotNull(type); ResourceType? resourceType = _resourceGraph.FindResourceType(type); diff --git a/src/JsonApiDotNetCore.OpenApi/ObjectExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ObjectExtensions.cs index 2047b11f96..8c216c93df 100644 --- a/src/JsonApiDotNetCore.OpenApi/ObjectExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ObjectExtensions.cs @@ -10,7 +10,7 @@ internal static class ObjectExtensions public static object MemberwiseClone(this object source) { - ArgumentGuard.NotNull(source, nameof(source)); + ArgumentGuard.NotNull(source); return MemberwiseCloneMethod.Value.Invoke(source, null)!; } diff --git a/src/JsonApiDotNetCore.OpenApi/OpenApiEndpointConvention.cs b/src/JsonApiDotNetCore.OpenApi/OpenApiEndpointConvention.cs index 6f175b71d5..c83266c1b0 100644 --- a/src/JsonApiDotNetCore.OpenApi/OpenApiEndpointConvention.cs +++ b/src/JsonApiDotNetCore.OpenApi/OpenApiEndpointConvention.cs @@ -18,14 +18,14 @@ internal sealed class OpenApiEndpointConvention : IActionModelConvention public OpenApiEndpointConvention(IControllerResourceMapping controllerResourceMapping) { - ArgumentGuard.NotNull(controllerResourceMapping, nameof(controllerResourceMapping)); + ArgumentGuard.NotNull(controllerResourceMapping); _controllerResourceMapping = controllerResourceMapping; } public void Apply(ActionModel action) { - ArgumentGuard.NotNull(action, nameof(action)); + ArgumentGuard.NotNull(action); JsonApiEndpoint? endpoint = _endpointResolver.Get(action.ActionMethod); diff --git a/src/JsonApiDotNetCore.OpenApi/ParameterInfoExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ParameterInfoExtensions.cs index ea14dce0b8..2a0b94f103 100644 --- a/src/JsonApiDotNetCore.OpenApi/ParameterInfoExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ParameterInfoExtensions.cs @@ -12,8 +12,8 @@ internal static class ParameterInfoExtensions public static ParameterInfo WithName(this ParameterInfo source, string name) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNullNorEmpty(name, nameof(name)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNullNorEmpty(name); var cloned = (ParameterInfo)source.MemberwiseClone(); NameField.Value.SetValue(cloned, name); @@ -23,8 +23,8 @@ public static ParameterInfo WithName(this ParameterInfo source, string name) public static ParameterInfo WithParameterType(this ParameterInfo source, Type parameterType) { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(parameterType, nameof(parameterType)); + ArgumentGuard.NotNull(source); + ArgumentGuard.NotNull(parameterType); var cloned = (ParameterInfo)source.MemberwiseClone(); ParameterTypeField.Value.SetValue(cloned, parameterType); diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs index d4b9c40fea..b1274cbe97 100644 --- a/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ResourceFieldAttributeExtensions.cs @@ -7,8 +7,6 @@ namespace JsonApiDotNetCore.OpenApi; internal static class ResourceFieldAttributeExtensions { - private static readonly NullabilityInfoContext NullabilityInfoContext = new(); - public static bool IsNullable(this ResourceFieldAttribute source) { bool hasRequiredAttribute = source.Property.HasAttribute(); @@ -24,7 +22,8 @@ public static bool IsNullable(this ResourceFieldAttribute source) return false; } - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(source.Property); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(source.Property); // Reflects the following cases: // Independent of NRT: diff --git a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs index 55695102a2..f223c7d994 100644 --- a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs @@ -18,8 +18,8 @@ public static class ServiceCollectionExtensions /// public static void AddOpenApi(this IServiceCollection services, IMvcCoreBuilder mvcBuilder, Action? setupSwaggerGenAction = null) { - ArgumentGuard.NotNull(services, nameof(services)); - ArgumentGuard.NotNull(mvcBuilder, nameof(mvcBuilder)); + ArgumentGuard.NotNull(services); + ArgumentGuard.NotNull(mvcBuilder); AddCustomApiExplorer(services, mvcBuilder); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs index 26f5fab3f8..f3867e4dc2 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs @@ -18,13 +18,13 @@ internal sealed class CachingSwaggerGenerator : ISwaggerProvider public CachingSwaggerGenerator(SwaggerGenerator defaultSwaggerGenerator) { - ArgumentGuard.NotNull(defaultSwaggerGenerator, nameof(defaultSwaggerGenerator)); + ArgumentGuard.NotNull(defaultSwaggerGenerator); _defaultSwaggerGenerator = defaultSwaggerGenerator; } public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null) { - ArgumentGuard.NotNullNorEmpty(documentName, nameof(documentName)); + ArgumentGuard.NotNullNorEmpty(documentName); string cacheKey = $"{documentName}#{host}#{basePath}"; diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/EndpointOrderingFilter.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/EndpointOrderingFilter.cs index 62a6224c4c..4de3beffe8 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/EndpointOrderingFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/EndpointOrderingFilter.cs @@ -13,8 +13,8 @@ internal sealed class EndpointOrderingFilter : IDocumentFilter public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { - ArgumentGuard.NotNull(swaggerDoc, nameof(swaggerDoc)); - ArgumentGuard.NotNull(context, nameof(context)); + ArgumentGuard.NotNull(swaggerDoc); + ArgumentGuard.NotNull(context); List> orderedEndpoints = swaggerDoc.Paths.OrderBy(GetPrimaryResourcePublicName) .ThenBy(GetRelationshipName).ThenBy(IsSecondaryEndpoint).ToList(); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiDataContractResolver.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiDataContractResolver.cs index 2fc7257478..4b6ae76cef 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiDataContractResolver.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiDataContractResolver.cs @@ -16,8 +16,8 @@ internal sealed class JsonApiDataContractResolver : ISerializerDataContractResol public JsonApiDataContractResolver(IResourceGraph resourceGraph, IJsonApiOptions options) { - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(options); _resourceGraph = resourceGraph; _dataContractResolver = new JsonSerializerDataContractResolver(options.SerializerOptions); @@ -25,7 +25,7 @@ public JsonApiDataContractResolver(IResourceGraph resourceGraph, IJsonApiOptions public DataContract GetDataContractForType(Type type) { - ArgumentGuard.NotNull(type, nameof(type)); + ArgumentGuard.NotNull(type); if (type == typeof(IIdentifiable)) { diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs index 3cfa015cf2..2df640379a 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs @@ -41,9 +41,9 @@ internal sealed class JsonApiSchemaGenerator : ISchemaGenerator public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options) { - ArgumentGuard.NotNull(defaultSchemaGenerator, nameof(defaultSchemaGenerator)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(defaultSchemaGenerator); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(options); _defaultSchemaGenerator = defaultSchemaGenerator; _nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); @@ -53,8 +53,8 @@ public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceG public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo? memberInfo = null, ParameterInfo? parameterInfo = null, ApiParameterRouteInfo? routeInfo = null) { - ArgumentGuard.NotNull(modelType, nameof(modelType)); - ArgumentGuard.NotNull(schemaRepository, nameof(schemaRepository)); + ArgumentGuard.NotNull(modelType); + ArgumentGuard.NotNull(schemaRepository); _schemaRepositoryAccessor.Current = schemaRepository; diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs index d9bedc343c..4e09769e87 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/NullableReferenceSchemaGenerator.cs @@ -17,7 +17,7 @@ internal sealed class NullableReferenceSchemaGenerator public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor, JsonNamingPolicy? namingPolicy) { - ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor)); + ArgumentGuard.NotNull(schemaRepositoryAccessor); _schemaRepositoryAccessor = schemaRepositoryAccessor; @@ -26,7 +26,7 @@ public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaReposito public OpenApiSchema GenerateSchema(OpenApiSchema referenceSchema) { - ArgumentGuard.NotNull(referenceSchema, nameof(referenceSchema)); + ArgumentGuard.NotNull(referenceSchema); return new OpenApiSchema { diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs index 6dbf35dd1e..804fae41fc 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs @@ -12,8 +12,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; internal sealed class ResourceFieldObjectSchemaBuilder { - private static readonly NullabilityInfoContext NullabilityInfoContext = new(); - private static readonly Type[] RelationshipSchemaInResponseOpenTypes = { typeof(ToOneRelationshipInResponse<>), @@ -39,11 +37,11 @@ internal sealed class ResourceFieldObjectSchemaBuilder public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor, SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, IJsonApiOptions options) { - ArgumentGuard.NotNull(resourceTypeInfo, nameof(resourceTypeInfo)); - ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor)); - ArgumentGuard.NotNull(defaultSchemaGenerator, nameof(defaultSchemaGenerator)); - ArgumentGuard.NotNull(resourceTypeSchemaGenerator, nameof(resourceTypeSchemaGenerator)); - ArgumentGuard.NotNull(options, nameof(options)); + ArgumentGuard.NotNull(resourceTypeInfo); + ArgumentGuard.NotNull(schemaRepositoryAccessor); + ArgumentGuard.NotNull(defaultSchemaGenerator); + ArgumentGuard.NotNull(resourceTypeSchemaGenerator); + ArgumentGuard.NotNull(options); _resourceTypeInfo = resourceTypeInfo; _schemaRepositoryAccessor = schemaRepositoryAccessor; @@ -121,7 +119,8 @@ private bool IsFieldRequired(ResourceFieldAttribute field) bool hasRequiredAttribute = field.Property.HasAttribute(); - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(field.Property); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(field.Property); return field.Property.PropertyType.IsValueType switch { diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs index a4fda8d7d0..f7db9e86ef 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs @@ -17,10 +17,10 @@ internal sealed class ResourceObjectSchemaGenerator public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, ISchemaRepositoryAccessor schemaRepositoryAccessor) { - ArgumentGuard.NotNull(defaultSchemaGenerator, nameof(defaultSchemaGenerator)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor)); + ArgumentGuard.NotNull(defaultSchemaGenerator); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(schemaRepositoryAccessor); _defaultSchemaGenerator = defaultSchemaGenerator; _resourceGraph = resourceGraph; @@ -36,7 +36,7 @@ public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IRe public OpenApiSchema GenerateSchema(Type resourceObjectType) { - ArgumentGuard.NotNull(resourceObjectType, nameof(resourceObjectType)); + ArgumentGuard.NotNull(resourceObjectType); (OpenApiSchema fullSchemaForResourceObject, OpenApiSchema referenceSchemaForResourceObject) = EnsureSchemasExist(resourceObjectType); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs index 43acb09a03..ea96d89a79 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs @@ -15,8 +15,8 @@ private ResourceTypeInfo(Type resourceObjectOpenType, ResourceType resourceType) public static ResourceTypeInfo Create(Type resourceObjectType, IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(resourceObjectType, nameof(resourceObjectType)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(resourceObjectType); + ArgumentGuard.NotNull(resourceGraph); Type resourceObjectOpenType = resourceObjectType.GetGenericTypeDefinition(); Type resourceClrType = resourceObjectType.GenericTypeArguments[0]; diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs index 9113f3b359..592a8ed860 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs @@ -16,8 +16,8 @@ internal sealed class ResourceTypeSchemaGenerator public ResourceTypeSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor, IResourceGraph resourceGraph, JsonNamingPolicy? namingPolicy) { - ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor)); - ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(schemaRepositoryAccessor); + ArgumentGuard.NotNull(resourceGraph); _schemaRepositoryAccessor = schemaRepositoryAccessor; _resourceGraph = resourceGraph; @@ -26,7 +26,7 @@ public ResourceTypeSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAcc public OpenApiSchema Get(Type resourceClrType) { - ArgumentGuard.NotNull(resourceClrType, nameof(resourceClrType)); + ArgumentGuard.NotNull(resourceClrType); if (_resourceClrTypeSchemaCache.TryGetValue(resourceClrType, out OpenApiSchema? referenceSchema)) { diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs index 6c2334fbf3..cc5edb6367 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs @@ -19,7 +19,7 @@ public SchemaRepository Current } set { - ArgumentGuard.NotNull(value, nameof(Current)); + ArgumentGuard.NotNull(value); _schemaRepository = value; } 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/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..6a17fed7b9 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++) { @@ -79,15 +79,4 @@ public static IEnumerable WhereNotNull(this IEnumerable source) return source.Where(element => element is not null)!; #pragma warning restore AV1250 // Evaluate LINQ query before returning it } - - public static void AddRange(this ICollection source, IEnumerable itemsToAdd) - { - ArgumentGuard.NotNull(source, nameof(source)); - ArgumentGuard.NotNull(itemsToAdd, nameof(itemsToAdd)); - - foreach (T item in itemsToAdd) - { - source.Add(item); - } - } } 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/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 597d22294d..bc7d17d89f 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -14,15 +14,27 @@ public interface IJsonApiOptions /// The URL prefix to use for exposed endpoints. /// /// - /// options.Namespace = "api/v1"; + /// /// 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. /// @@ -42,10 +54,10 @@ public interface IJsonApiOptions /// Use relative links for all resources. False by default. /// /// - /// + /// - /// + /// ]]> + /// + /// ]]> /// bool UseRelativeLinks { get; } 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/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/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 e07318c7f7..2b6f19acd3 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)) { @@ -307,6 +307,7 @@ private IReadOnlyCollection GetRelationships(Type resourc { relationship.Property = property; SetPublicName(relationship, property); + SetRelationshipCapabilities(relationship); IncludeField(relationshipsByName, relationship); } @@ -317,10 +318,56 @@ private IReadOnlyCollection GetRelationships(Type resourc private void SetPublicName(ResourceFieldAttribute field, PropertyInfo property) { - // ReSharper disable once ConstantNullCoalescingCondition + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract 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/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..981c50c4e4 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(); @@ -147,14 +147,14 @@ public IReadOnlyCollection GetDerivedTypesForUnboundType(Assembly assembly /// The inherited type. /// /// - /// + /// + /// ]]> /// 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/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/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..0f596326f7 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) { @@ -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/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..1250e36312 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); @@ -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); } @@ -104,9 +110,9 @@ private ICollection LookupRelationshipName(string relationshipN if (relationships.Any()) { - relationshipsFound.AddRange(relationships); + relationshipsFound.UnionWith(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 +145,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; @@ -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)); @@ -266,7 +272,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..4661a5bdda 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(); @@ -254,7 +254,7 @@ private static IImmutableSet ApplyIncludeElementUpdate IDictionary> updatesInChildren) { ImmutableHashSet.Builder newElementsBuilder = ImmutableHashSet.CreateBuilder(); - newElementsBuilder.AddRange(includeElements); + newElementsBuilder.UnionWith(includeElements); foreach ((IncludeElementExpression existingElement, IImmutableSet updatedChildren) in updatesInChildren) { @@ -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..ab1edf9f9e 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)) { @@ -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/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..dace5b8ca4 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); @@ -40,17 +40,19 @@ 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."); } } /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Filter); } @@ -58,7 +60,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; @@ -75,18 +77,21 @@ public virtual void Read(string parameterName, StringValues parameterValue) } } - private IEnumerable ExtractParameterValue(string parameterValue) + private IEnumerable ExtractParameterValue(string? parameterValue) { - if (_options.EnableLegacyFilterNotation) + if (parameterValue != null) { - foreach (string condition in LegacyConverter.ExtractConditions(parameterValue)) + if (_options.EnableLegacyFilterNotation) { - yield return condition; + foreach (string condition in LegacyConverter.ExtractConditions(parameterValue)) + { + yield return condition; + } + } + else + { + yield return parameterValue; } - } - else - { - yield return parameterValue; } } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs index 6c8bfa2934..7db9a9a7d7 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs @@ -18,12 +18,12 @@ 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) { - 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); } @@ -48,7 +48,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) { try { - _includeExpression = GetInclude(parameterValue); + _includeExpression = GetInclude(parameterValue.ToString()); } catch (QueryParseException exception) { 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..3e4293c5e0 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); } @@ -53,7 +53,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) { try { - PaginationQueryStringValueExpression constraint = GetPageConstraint(parameterValue); + PaginationQueryStringValueExpression constraint = GetPageConstraint(parameterValue.ToString()); if (constraint.Elements.Any(element => element.Scope == null)) { 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..5e5842c960 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; @@ -62,7 +62,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) try { ResourceFieldChainExpression? scope = GetScope(parameterName); - SortExpression sort = GetSort(parameterValue, scope); + SortExpression sort = GetSort(parameterValue.ToString(), scope); var expressionInScope = new ExpressionInScope(scope, sort); _constraints.Add(expressionInScope); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index d8f1e858ea..09c3c0ede8 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -36,17 +36,19 @@ 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."); } } /// public virtual bool IsEnabled(DisableQueryStringAttribute disableQueryStringAttribute) { - ArgumentGuard.NotNull(disableQueryStringAttribute, nameof(disableQueryStringAttribute)); + ArgumentGuard.NotNull(disableQueryStringAttribute); return !IsAtomicOperationsRequest && !disableQueryStringAttribute.ContainsParameter(JsonApiQueryStringParameters.Fields); } @@ -54,7 +56,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); } @@ -67,7 +69,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) try { ResourceType targetResourceType = GetSparseFieldType(parameterName); - SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue, targetResourceType); + SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue.ToString(), targetResourceType); _sparseFieldTableBuilder[targetResourceType] = sparseFieldSet; } 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/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..7cdd114301 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"); @@ -471,7 +471,7 @@ private IEnumerable GetRightValueToStoreForAddToToMany(TResource leftResource, H if (rightResourceIdsStored.Any()) { - rightResourceIdsStored.AddRange(rightResourceIdsToAdd); + rightResourceIdsStored.UnionWith(rightResourceIdsToAdd); return rightResourceIdsStored; } @@ -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/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/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..88ea29ecdc 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); } @@ -58,6 +58,6 @@ private void AddSecondaryResources(RelationshipAttribute relationship, HashSet rightResources = CollectionConverter.ExtractResources(rightValue); - secondaryResources.AddRange(rightResources); + secondaryResources.UnionWith(rightResources); } } 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/Resources/TargetedFields.cs b/src/JsonApiDotNetCore/Resources/TargetedFields.cs index fe4701c61e..4d40fc240d 100644 --- a/src/JsonApiDotNetCore/Resources/TargetedFields.cs +++ b/src/JsonApiDotNetCore/Resources/TargetedFields.cs @@ -18,8 +18,8 @@ public void CopyFrom(ITargetedFields other) { Clear(); - Attributes.AddRange(other.Attributes); - Relationships.AddRange(other.Relationships); + Attributes.UnionWith(other.Attributes); + Relationships.UnionWith(other.Relationships); } public void 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 - /// 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) { 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 diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs index 061bd0f920..071b8d309e 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicOperationObjectAdapter.cs @@ -17,10 +17,10 @@ public sealed class AtomicOperationObjectAdapter : IAtomicOperationObjectAdapter public AtomicOperationObjectAdapter(IJsonApiOptions options, IAtomicReferenceAdapter atomicReferenceAdapter, IResourceDataInOperationsRequestAdapter resourceDataInOperationsRequestAdapter, IRelationshipDataAdapter relationshipDataAdapter) { - ArgumentGuard.NotNull(options, nameof(options)); - ArgumentGuard.NotNull(atomicReferenceAdapter, nameof(atomicReferenceAdapter)); - ArgumentGuard.NotNull(resourceDataInOperationsRequestAdapter, nameof(resourceDataInOperationsRequestAdapter)); - ArgumentGuard.NotNull(relationshipDataAdapter, nameof(relationshipDataAdapter)); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(atomicReferenceAdapter); + ArgumentGuard.NotNull(resourceDataInOperationsRequestAdapter); + ArgumentGuard.NotNull(relationshipDataAdapter); _options = options; _atomicReferenceAdapter = atomicReferenceAdapter; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs index e1aec5641b..f7a5ad82fa 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/AtomicReferenceAdapter.cs @@ -18,9 +18,9 @@ public AtomicReferenceAdapter(IResourceGraph resourceGraph, IResourceFactory res /// 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); @@ -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/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..38a8ce0a29 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; @@ -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/RelationshipDataAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs index 89cf3caa18..0e90f7df07 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"); @@ -111,7 +111,7 @@ private IEnumerable ConvertToManyRelationshipData(SingleOrManyData(IdentifiableComparer.Instance); - resourceSet.AddRange(rightResources); + resourceSet.UnionWith(rightResources); return resourceSet; } } 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..e4c0df21df 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); @@ -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 2199782a3a..1b85b35336 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); @@ -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/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..fb86eb084c 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; @@ -140,13 +140,13 @@ private void SetPaginationInTopLevelLinks(ResourceType resourceType, TopLevelLin private string? CalculatePageSizeValue(PageSize? topPageSize, ResourceType resourceType) { - string pageSizeParameterValue = HttpContext.Request.Query[PageSizeParameterName]; + string? pageSizeParameterValue = HttpContext.Request.Query[PageSizeParameterName]; PageSize? newTopPageSize = Equals(topPageSize, _options.DefaultPageSize) ? null : topPageSize; return ChangeTopPageSize(pageSizeParameterValue, newTopPageSize, resourceType); } - private string? ChangeTopPageSize(string pageSizeParameterValue, PageSize? topPageSize, ResourceType resourceType) + private string? ChangeTopPageSize(string? pageSizeParameterValue, PageSize? topPageSize, ResourceType resourceType) { IImmutableList elements = ParsePageSizeExpression(pageSizeParameterValue, resourceType); int elementInTopScopeIndex = elements.FindIndex(expression => expression.Scope == null); @@ -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..4c3a44fe38 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) { @@ -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 primaryResourceObject in GetDirectChildren().Select(node => node.ResourceObject)) + { + if (includes.Contains(primaryResourceObject, ResourceObjectComparer.Instance)) + { + includes.Remove(primaryResourceObject); + } + } + + return includes; } private IList GetDirectChildren() diff --git a/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs b/src/JsonApiDotNetCore/Serialization/Response/ResponseModelAdapter.cs index 223166e59e..b1398b7cfb 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; @@ -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/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/AnnotationTests/AnnotationTests.csproj b/test/AnnotationTests/AnnotationTests.csproj new file mode 100644 index 0000000000..7b221a9a42 --- /dev/null +++ b/test/AnnotationTests/AnnotationTests.csproj @@ -0,0 +1,14 @@ + + + $(TargetFrameworkName);netstandard1.0 + latest + + + + + + + + + + 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..9002773680 --- /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", Capabilities = HasOneCapabilities.AllowView | HasOneCapabilities.AllowInclude, Links = LinkTypes.All)] + public TreeNode? Parent { get; set; } + + [HasMany(PublicName = "orders", Capabilities = HasManyCapabilities.AllowView | HasManyCapabilities.AllowFilter, Links = LinkTypes.All)] + public ISet Children { get; set; } = new HashSet(); +} diff --git a/test/DiscoveryTests/DiscoveryTests.csproj b/test/DiscoveryTests/DiscoveryTests.csproj index 2f1048de3f..abbec3ed98 100644 --- a/test/DiscoveryTests/DiscoveryTests.csproj +++ b/test/DiscoveryTests/DiscoveryTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/DiscoveryTests/xunit.runner.json b/test/DiscoveryTests/xunit.runner.json deleted file mode 100644 index 9db029ba52..0000000000 --- a/test/DiscoveryTests/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false -} 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/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/OperationsDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs index 45be92ce8b..6fd7817ba7 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(); @@ -30,5 +31,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(musicTrack => musicTrack.OccursIn) .WithMany(playlist => playlist.Tracks); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs index fdf91e9744..65ab4a4344 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 FilterOnRecentlyReleased(IQueryable s { IQueryable tracks = source; - if (bool.Parse(parameterValue)) + if (bool.Parse(parameterValue.ToString())) { tracks = tracks.Where(musicTrack => musicTrack.ReleasedAt < _systemClock.UtcNow && musicTrack.ReleasedAt > _systemClock.UtcNow.AddMonths(-3)); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index 97f0e08ff5..46ef0c4784 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Transactions; -public sealed class AtomicTransactionConsistencyTests : IClassFixture, OperationsDbContext>> +public sealed class AtomicTransactionConsistencyTests + : IClassFixture, OperationsDbContext>>, IAsyncLifetime { private readonly IntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new(); @@ -27,7 +28,7 @@ public AtomicTransactionConsistencyTests(IntegrationTestContext(); string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres"; - string dbConnectionString = $"Host=localhost;Port=5432;Database=JsonApiTest-{Guid.NewGuid():N};User ID=postgres;Password={postgresPassword}"; + string dbConnectionString = $"Host=localhost;Port=5432;Database=JsonApiTest-Extra-{Guid.NewGuid():N};User ID=postgres;Password={postgresPassword}"; services.AddDbContext(options => options.UseNpgsql(dbConnectionString)); }); @@ -158,4 +159,22 @@ public async Task Cannot_use_distributed_transaction() error.Source.ShouldNotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } + + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + Task IAsyncLifetime.DisposeAsync() + { + return DeleteExtraDatabaseAsync(); + } + + private async Task DeleteExtraDatabaseAsync() + { + await using AsyncServiceScope scope = _testContext.Factory.Services.CreateAsyncScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + await dbContext.Database.EnsureDeletedAsync(); + } } 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/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/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..d4850ad428 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(); @@ -38,5 +39,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(car => car.PreviousDealerships) .WithMany(dealership => dealership.SoldCars); + + base.OnModelCreating(builder); } } 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/CustomRoutes/CustomRouteTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs index 572f2baeed..e89641470d 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs @@ -67,12 +67,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_resources_at_custom_action_method() { // Arrange - List town = _fakers.Town.Generate(7); + List towns = _fakers.Town.Generate(7); await _testContext.RunOnDatabaseAsync(async dbContext => { await dbContext.ClearTableAsync(); - dbContext.Towns.AddRange(town); + dbContext.Towns.AddRange(towns); await dbContext.SaveChangesAsync(); }); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingDefinition.cs index a1ebb2c87f..18dca5a502 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/BuildingDefinition.cs @@ -14,7 +14,7 @@ public sealed class BuildingDefinition : JsonApiResourceDefinition States => Set(); public DbSet Streets => Set(); @@ -33,5 +34,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(building => building.SecondaryDoor) .WithOne() .HasForeignKey("SecondaryDoorId"); + + base.OnModelCreating(builder); } } 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/HitCountingResourceDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/HitCountingResourceDefinition.cs index 8132728c90..3a51f0961f 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/HitCountingResourceDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/HitCountingResourceDefinition.cs @@ -22,7 +22,7 @@ public abstract class HitCountingResourceDefinition : 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/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..1bfdb1a28e 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(); @@ -36,5 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasOne(systemDirectory => systemDirectory.AlsoSelf) .WithOne(); + + base.OnModelCreating(builder); } } 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..149b29b785 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(); @@ -23,5 +24,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(photo => photo.Location) .WithOne(location => location.Photo) .HasForeignKey("LocationId"); + + base.OnModelCreating(builder); } } 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..69a6459303 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; @@ -30,5 +31,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasQueryFilter(webProduct => webProduct.Shop.TenantId == _tenantProvider.TenantId); + + base.OnModelCreating(builder); } } 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/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}'"); } } 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/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/FilterDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs index 1c6c7eff32..35e7b4e51e 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(); @@ -20,5 +21,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .Property(resource => resource.SomeDateTimeInLocalZone) .HasColumnType("timestamp without time zone"); + + base.OnModelCreating(builder); } } 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..88360bcfa2 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; @@ -397,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] @@ -827,6 +824,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() { @@ -872,7 +894,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 +916,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 +937,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/QueryStringDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs index 1e240f8e8d..473a7428ba 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(); @@ -36,5 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(man => man.Wife) .WithOne(woman => woman.Husband) .HasForeignKey(); + + base.OnModelCreating(builder); } } 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/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/ReadWriteDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs index 276c3f0a91..4f3d1080cf 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(); @@ -48,5 +49,7 @@ protected override void OnModelCreating(ModelBuilder builder) left => left .HasOne(joinEntity => joinEntity.ToItem) .WithMany()); + + base.OnModelCreating(builder); } } 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..ed9e4e7936 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 { @@ -1099,7 +1142,7 @@ private void RemoveFromSubscribers(WorkItem workItem, ISet rightR { if (!workItem.Subscribers.IsNullOrEmpty()) { - PreloadedSubscribers.AddRange(workItem.Subscribers); + PreloadedSubscribers.UnionWith(workItem.Subscribers); } foreach (long subscriberId in ExtraSubscribersIdsToRemove) @@ -1115,7 +1158,7 @@ private void RemoveFromTags(WorkItem workItem, ISet rightResource { if (!workItem.Tags.IsNullOrEmpty()) { - PreloadedTags.AddRange(workItem.Tags); + PreloadedTags.UnionWith(workItem.Tags); } foreach (int tagId in ExtraTagIdsToRemove) 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..b96fcd6fff 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs @@ -66,7 +66,7 @@ public async Task Can_clear_OneToOne_relationship() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.Groups.AddRange(existingGroup); + dbContext.Groups.Add(existingGroup); await dbContext.SaveChangesAsync(); }); @@ -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..f96f4a9efa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs @@ -209,7 +209,7 @@ public async Task Can_clear_OneToOne_relationship() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.RgbColors.AddRange(existingColor); + dbContext.RgbColors.Add(existingColor); await dbContext.SaveChangesAsync(); }); @@ -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(); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs index 55ef653c95..5b0b839c63 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(); @@ -32,5 +33,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(order => order.Shipment) .WithOne(shipment => shipment.Order) .HasForeignKey("OrderId"); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs index 6516100641..bb3bbfd85b 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceConstructorInjection/InjectionDbContext.cs @@ -2,21 +2,22 @@ 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; } - public DbSet PostOffice => Set(); + public DbSet PostOffices => Set(); public DbSet GiftCertificates => Set(); 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/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); }); 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..2b904434ad 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() @@ -51,7 +51,7 @@ public override QueryStringParameterHandlers OnRegisterQueryableHandlersFo private static IQueryable FilterByRadius(IQueryable source, StringValues parameterValue) { - bool isFilterOnLargerThan = bool.Parse(parameterValue); + bool isFilterOnLargerThan = bool.Parse(parameterValue.ToString()); return isFilterOnLargerThan ? source.Where(moon => moon.SolarRadius > 1m) : source.Where(moon => moon.SolarRadius <= 1m); } } 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; } } 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..4bfb7aa709 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(); @@ -21,5 +22,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(scholarship => scholarship.Participants) .WithOne(student => student.Scholarship!); + + base.OnModelCreating(builder); } } 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/ResourceInheritance/ResourceInheritanceReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs index 3a7b60e93d..ebca28dac9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs @@ -2429,7 +2429,7 @@ public async Task Can_sort_on_derived_attribute_from_resource_definition_using_e await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); dbContext.Wheels.AddRange(chromeWheel1, chromeWheel2, chromeWheel3, carbonWheel1, carbonWheel2); await dbContext.SaveChangesAsync(); }); @@ -2487,7 +2487,7 @@ public async Task Can_sort_on_derived_attribute_from_resource_definition_using_l await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); dbContext.Wheels.AddRange(chromeWheel1, chromeWheel2, chromeWheel3, carbonWheel1, carbonWheel2); await dbContext.SaveChangesAsync(); }); 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/TablePerType/TablePerTypeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs index a1549e6332..7c50ee5573 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs @@ -32,5 +32,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity().ToTable("GenericProperties"); builder.Entity().ToTable("StringProperties"); builder.Entity().ToTable("NumberProperties"); + + base.OnModelCreating(builder); } } 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/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..3e98950be0 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(); @@ -23,5 +24,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasQueryFilter(department => department.SoftDeletedAt == null); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs index bfe4fe7a3b..3e93768683 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(); @@ -26,5 +27,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasOne(player => player.ActiveGame) .WithMany(game => game.ActivePlayers); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj index ddb7550e5e..22d50630ca 100644 --- a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj +++ b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - : 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 785c2ebd5c..8642461d50 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs @@ -74,7 +74,7 @@ private static IHostBuilder CreateValidatingHostBuilder() IHostBuilder hostBuilder = Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureServices(services => - services.AddDbContext(options => options.UseInMemoryDatabase("db"))); + services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()))); webBuilder.UseStartup>(); @@ -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,12 +119,12 @@ private sealed class CircularServiceB // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local public CircularServiceB(CircularServiceA serviceA) { - ArgumentGuard.NotNull(serviceA, nameof(serviceA)); + ArgumentGuard.NotNull(serviceA); } } [UsedImplicitly(ImplicitUseTargetFlags.Members)] - private sealed class DependencyContainerRegistrationDbContext : DbContext + private sealed class DependencyContainerRegistrationDbContext : TestableDbContext { public DbSet Resources => Set(); 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")] diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs index d9459f7ec1..39279181dd 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs @@ -40,6 +40,7 @@ public void Resources_in_deeply_nested_circular_chain_are_written_in_relationshi // Assert string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + // ReSharper disable StringLiteralTypo text.Should().BeJson(@"{ ""data"": { ""type"": ""articles"", @@ -145,6 +146,7 @@ public void Resources_in_deeply_nested_circular_chain_are_written_in_relationshi } ] }"); + // ReSharper restore StringLiteralTypo } [Fact] @@ -177,6 +179,7 @@ public void Resources_in_deeply_nested_circular_chains_are_written_in_relationsh // Assert string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + // ReSharper disable StringLiteralTypo text.Should().BeJson(@"{ ""data"": [ { @@ -299,6 +302,7 @@ public void Resources_in_deeply_nested_circular_chains_are_written_in_relationsh } ] }"); + // ReSharper restore StringLiteralTypo } [Fact] @@ -335,6 +339,7 @@ public void Resources_in_overlapping_deeply_nested_circular_chains_are_written_i // Assert string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + // ReSharper disable StringLiteralTypo text.Should().BeJson(@"{ ""data"": { ""type"": ""articles"", @@ -514,6 +519,7 @@ public void Resources_in_overlapping_deeply_nested_circular_chains_are_written_i } ] }"); + // ReSharper restore StringLiteralTypo } [Fact] diff --git a/test/JsonApiDotNetCoreTests/xunit.runner.json b/test/JsonApiDotNetCoreTests/xunit.runner.json deleted file mode 100644 index 8f5f10571b..0000000000 --- a/test/JsonApiDotNetCoreTests/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 -} diff --git a/test/MultiDbContextTests/MultiDbContextTests.csproj b/test/MultiDbContextTests/MultiDbContextTests.csproj index a5d0715a2a..b08c25805f 100644 --- a/test/MultiDbContextTests/MultiDbContextTests.csproj +++ b/test/MultiDbContextTests/MultiDbContextTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/MultiDbContextTests/xunit.runner.json b/test/MultiDbContextTests/xunit.runner.json deleted file mode 100644 index 8f5f10571b..0000000000 --- a/test/MultiDbContextTests/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 -} diff --git a/test/OpenApiClientTests/ApiResponse.cs b/test/OpenApiClientTests/ApiResponse.cs index 0c353ea5c0..7ea398c73a 100644 --- a/test/OpenApiClientTests/ApiResponse.cs +++ b/test/OpenApiClientTests/ApiResponse.cs @@ -12,7 +12,7 @@ internal static class ApiResponse { // Workaround for https://github.com/RicoSuter/NSwag/issues/2499 - ArgumentGuard.NotNull(operation, nameof(operation)); + ArgumentGuard.NotNull(operation); try { diff --git a/test/OpenApiClientTests/FakeHttpClientWrapper.cs b/test/OpenApiClientTests/FakeHttpClientWrapper.cs index 7240daf029..222650ac2d 100644 --- a/test/OpenApiClientTests/FakeHttpClientWrapper.cs +++ b/test/OpenApiClientTests/FakeHttpClientWrapper.cs @@ -75,7 +75,7 @@ public FakeHttpMessageHandler(HttpResponseMessage response) public void SetResponse(HttpResponseMessage response) { - ArgumentGuard.NotNull(response, nameof(response)); + ArgumentGuard.NotNull(response); _response = response; } diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index d45489ae38..badeeaeae7 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs index c3338a300b..b29c635f8e 100644 --- a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs +++ b/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs @@ -6,14 +6,13 @@ namespace OpenApiClientTests; internal static class PropertyInfoAssertionsExtensions { - private static readonly NullabilityInfoContext NullabilityInfoContext = new(); - [CustomAssertion] public static void BeNullable(this PropertyInfoAssertions source, string because = "", params object[] becauseArgs) { PropertyInfo propertyInfo = source.Subject; - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(propertyInfo); nullabilityInfo.ReadState.Should().NotBe(NullabilityState.NotNull, because, becauseArgs); } @@ -23,7 +22,8 @@ public static void BeNonNullable(this PropertyInfoAssertions source, string beca { PropertyInfo propertyInfo = source.Subject; - NullabilityInfo nullabilityInfo = NullabilityInfoContext.Create(propertyInfo); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(propertyInfo); nullabilityInfo.ReadState.Should().Be(NullabilityState.NotNull, because, becauseArgs); } diff --git a/test/OpenApiClientTests/xunit.runner.json b/test/OpenApiClientTests/xunit.runner.json deleted file mode 100644 index 8f5f10571b..0000000000 --- a/test/OpenApiClientTests/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 -} diff --git a/test/OpenApiTests/LegacyOpenApiIntegration/LegacyIntegrationDbContext.cs b/test/OpenApiTests/LegacyOpenApiIntegration/LegacyIntegrationDbContext.cs index b6a1ce7f34..d3f9672416 100644 --- a/test/OpenApiTests/LegacyOpenApiIntegration/LegacyIntegrationDbContext.cs +++ b/test/OpenApiTests/LegacyOpenApiIntegration/LegacyIntegrationDbContext.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 OpenApiTests.LegacyOpenApiIntegration; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class LegacyIntegrationDbContext : DbContext +public sealed class LegacyIntegrationDbContext : TestableDbContext { public DbSet Airplanes => Set(); public DbSet Flights => Set(); @@ -31,5 +32,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasOne(flight => flight.BackupPurser) .WithMany(); + + base.OnModelCreating(builder); } } diff --git a/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationStartup.cs b/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationStartup.cs index 6dbbba3521..2cf6da28ba 100644 --- a/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationStartup.cs +++ b/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationStartup.cs @@ -2,13 +2,13 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources.Annotations; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.LegacyOpenApiIntegration; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class LegacyOpenApiIntegrationStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseNamingConventionStartup.cs b/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseNamingConventionStartup.cs index 242291c4f6..eeca328a8b 100644 --- a/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseNamingConventionStartup.cs +++ b/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseNamingConventionStartup.cs @@ -2,13 +2,13 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.NamingConventions.CamelCase; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class CamelCaseNamingConventionStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseNamingConventionStartup.cs b/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseNamingConventionStartup.cs index ebd532aa65..e7534bf657 100644 --- a/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseNamingConventionStartup.cs +++ b/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseNamingConventionStartup.cs @@ -1,14 +1,14 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using OpenApiTests.LegacyOpenApiIntegration; +using TestBuildingBlocks; namespace OpenApiTests.NamingConventions.KebabCase; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class KebabCaseNamingConventionStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/NamingConventions/NamingConventionsDbContext.cs b/test/OpenApiTests/NamingConventions/NamingConventionsDbContext.cs index eb2d0b52d2..0e645e8db0 100644 --- a/test/OpenApiTests/NamingConventions/NamingConventionsDbContext.cs +++ b/test/OpenApiTests/NamingConventions/NamingConventionsDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.NamingConventions; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NamingConventionsDbContext : DbContext +public sealed class NamingConventionsDbContext : TestableDbContext { public DbSet Supermarkets => Set(); diff --git a/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseNamingConventionStartup.cs b/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseNamingConventionStartup.cs index 7c934b94e4..e51e9029a2 100644 --- a/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseNamingConventionStartup.cs +++ b/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseNamingConventionStartup.cs @@ -1,13 +1,13 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.NamingConventions.PascalCase; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class PascalCaseNamingConventionStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/OpenApiStartup.cs b/test/OpenApiTests/OpenApiStartup.cs index dd2a7d50ab..46978e7f67 100644 --- a/test/OpenApiTests/OpenApiStartup.cs +++ b/test/OpenApiTests/OpenApiStartup.cs @@ -1,14 +1,13 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi; using Microsoft.AspNetCore.Builder; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using TestBuildingBlocks; namespace OpenApiTests; public abstract class OpenApiStartup : TestableStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { public override void ConfigureServices(IServiceCollection services) { diff --git a/test/OpenApiTests/OpenApiTestContext.cs b/test/OpenApiTests/OpenApiTestContext.cs index 9d4a95c33d..78bbd0f08d 100644 --- a/test/OpenApiTests/OpenApiTestContext.cs +++ b/test/OpenApiTests/OpenApiTestContext.cs @@ -1,7 +1,6 @@ using System.Reflection; using System.Text.Json; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; using TestBuildingBlocks; using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; @@ -10,7 +9,7 @@ namespace OpenApiTests; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public class OpenApiTestContext : IntegrationTestContext where TStartup : class - where TDbContext : DbContext + where TDbContext : TestableDbContext { private readonly Lazy> _lazySwaggerDocument; diff --git a/test/OpenApiTests/OpenApiTests.csproj b/test/OpenApiTests/OpenApiTests.csproj index 50b96c1d36..d6cb1d3002 100644 --- a/test/OpenApiTests/OpenApiTests.csproj +++ b/test/OpenApiTests/OpenApiTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs index 9895d7d61b..b2906ba460 100644 --- a/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs +++ b/test/OpenApiTests/SchemaProperties/ModelStateValidationDisabledStartup.cs @@ -1,13 +1,13 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class ModelStateValidationDisabledStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs index 1208a369ef..16bdc07e15 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesDisabled/NullableReferenceTypesDisabledDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesDisabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesDisabledDbContext : DbContext +public sealed class NullableReferenceTypesDisabledDbContext : TestableDbContext { public DbSet Chicken => Set(); diff --git a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs index 01c61aa589..b7011e7d27 100644 --- a/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs +++ b/test/OpenApiTests/SchemaProperties/NullableReferenceTypesEnabled/NullableReferenceTypesEnabledDbContext.cs @@ -1,10 +1,11 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties.NullableReferenceTypesEnabled; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class NullableReferenceTypesEnabledDbContext : DbContext +public sealed class NullableReferenceTypesEnabledDbContext : TestableDbContext { public DbSet Cow => Set(); diff --git a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs index d4a1bf1d19..03f6531631 100644 --- a/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs +++ b/test/OpenApiTests/SchemaProperties/SchemaPropertiesStartup.cs @@ -1,13 +1,13 @@ using System.Text.Json.Serialization; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; +using TestBuildingBlocks; namespace OpenApiTests.SchemaProperties; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class SchemaPropertiesStartup : OpenApiStartup - where TDbContext : DbContext + where TDbContext : TestableDbContext { protected override void SetJsonApiOptions(JsonApiOptions options) { diff --git a/test/OpenApiTests/xunit.runner.json b/test/OpenApiTests/xunit.runner.json deleted file mode 100644 index 8f5f10571b..0000000000 --- a/test/OpenApiTests/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 -} diff --git a/test/TestBuildingBlocks/DbContextExtensions.cs b/test/TestBuildingBlocks/DbContextExtensions.cs index b570cbf655..5bb3f81a14 100644 --- a/test/TestBuildingBlocks/DbContextExtensions.cs +++ b/test/TestBuildingBlocks/DbContextExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; -using Npgsql; namespace TestBuildingBlocks; @@ -36,17 +35,7 @@ private static async Task ClearTablesAsync(this DbContext dbContext, params Type } string tableName = entityType.GetTableName()!; - - // PERF: We first try to clear the table, which is fast and usually succeeds, unless foreign key constraints are violated. - // In that case, we recursively delete all related data, which is slow. - try - { - await dbContext.Database.ExecuteSqlRawAsync($"delete from \"{tableName}\""); - } - catch (PostgresException) - { - await dbContext.Database.ExecuteSqlRawAsync($"truncate table \"{tableName}\" cascade"); - } + await dbContext.Database.ExecuteSqlRawAsync($"delete from \"{tableName}\""); } } } diff --git a/test/TestBuildingBlocks/IntegrationTest.cs b/test/TestBuildingBlocks/IntegrationTest.cs index 99ced55b01..92f49879fc 100644 --- a/test/TestBuildingBlocks/IntegrationTest.cs +++ b/test/TestBuildingBlocks/IntegrationTest.cs @@ -2,14 +2,18 @@ using System.Text; using System.Text.Json; using JsonApiDotNetCore.Middleware; +using Xunit; namespace TestBuildingBlocks; /// -/// A base class for tests that conveniently enables to execute HTTP requests against JSON:API endpoints. +/// A base class for tests that conveniently enables to execute HTTP requests against JSON:API endpoints. It throttles tests that are running in parallel +/// to avoid exceeding the maximum active database connections. /// -public abstract class IntegrationTest +public abstract class IntegrationTest : IAsyncLifetime { + private static readonly SemaphoreSlim ThrottleSemaphore = new(64); + protected abstract JsonSerializerOptions SerializerOptions { get; } public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecuteHeadAsync(string requestUrl, @@ -79,8 +83,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(); @@ -106,4 +109,15 @@ public abstract class IntegrationTest throw new FormatException($"Failed to deserialize response body to JSON:\n{responseText}", exception); } } + + public async Task InitializeAsync() + { + await ThrottleSemaphore.WaitAsync(); + } + + public virtual Task DisposeAsync() + { + _ = ThrottleSemaphore.Release(); + return Task.CompletedTask; + } } diff --git a/test/TestBuildingBlocks/IntegrationTestContext.cs b/test/TestBuildingBlocks/IntegrationTestContext.cs index 8ccbc14079..7856ba67f9 100644 --- a/test/TestBuildingBlocks/IntegrationTestContext.cs +++ b/test/TestBuildingBlocks/IntegrationTestContext.cs @@ -24,9 +24,9 @@ namespace TestBuildingBlocks; /// The Entity Framework Core database context, which can be defined in the test project or API project. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class IntegrationTestContext : IntegrationTest, IDisposable +public class IntegrationTestContext : IntegrationTest where TStartup : class - where TDbContext : DbContext + where TDbContext : TestableDbContext { private readonly Lazy> _lazyFactory; private readonly TestControllerProvider _testControllerProvider = new(); @@ -103,16 +103,6 @@ private WebApplicationFactory CreateFactory() return factoryWithConfiguredContentRoot; } - public void Dispose() - { - if (_lazyFactory.IsValueCreated) - { - RunOnDatabaseAsync(async dbContext => await dbContext.Database.EnsureDeletedAsync()).Wait(); - - _lazyFactory.Value.Dispose(); - } - } - public void ConfigureLogging(Action loggingConfiguration) { _loggingConfiguration = loggingConfiguration; @@ -136,6 +126,22 @@ public async Task RunOnDatabaseAsync(Func asyncAction) await asyncAction(dbContext); } + public override async Task DisposeAsync() + { + try + { + if (_lazyFactory.IsValueCreated) + { + await RunOnDatabaseAsync(async dbContext => await dbContext.Database.EnsureDeletedAsync()); + await _lazyFactory.Value.DisposeAsync(); + } + } + finally + { + await base.DisposeAsync(); + } + } + private sealed class IntegrationTestWebApplicationFactory : WebApplicationFactory { private Action? _loggingConfiguration; 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/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index ed335f630f..ce8c54ef3b 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -10,10 +10,9 @@ - + - diff --git a/test/TestBuildingBlocks/TestableDbContext.cs b/test/TestBuildingBlocks/TestableDbContext.cs new file mode 100644 index 0000000000..18ef090baa --- /dev/null +++ b/test/TestBuildingBlocks/TestableDbContext.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using JsonApiDotNetCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +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); + } + + protected override void OnModelCreating(ModelBuilder builder) + { + foreach (IMutableForeignKey foreignKey in builder.Model.GetEntityTypes().SelectMany(entityType => entityType.GetForeignKeys())) + { + if (foreignKey.DeleteBehavior == DeleteBehavior.ClientSetNull) + { + foreignKey.DeleteBehavior = DeleteBehavior.SetNull; + } + } + } +} 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..0cb24d8025 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; @@ -24,7 +25,7 @@ public void RegisterResource_DeviatingDbContextPropertyName_RegistersCorrectly() // Arrange var services = new ServiceCollection(); services.AddLogging(); - services.AddDbContext(options => options.UseInMemoryDatabase("UnitTestDb")); + services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString())); // Act services.AddJsonApi(); @@ -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(); 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; } diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index eb3383fbdf..3166fe27e1 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/UnitTests/xunit.runner.json b/test/UnitTests/xunit.runner.json deleted file mode 100644 index 9db029ba52..0000000000 --- a/test/UnitTests/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false -}