diff --git a/.gitattributes b/.gitattributes index b508c0d946..1f6c60ff78 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ # When running OpenAPI tests, these committed files are downloaded and written to disk (so we'll know when something changes). -# On Windows, these text files are auto-converted to crlf on fetch, while the written downloaded files use lf line endings. +# On Windows, these text files are auto-converted to crlf on git fetch, while the written downloaded files use lf line endings. # Therefore, running the tests on Windows creates local changes. Staging them auto-converts back to crlf, which undoes the changes. -# To avoid this annoyance, the next line opts out of the auto-conversion and forces to lf. +# To avoid this annoyance, the next lines opt out of the auto-conversion and force to lf. swagger.g.json text eol=lf +**/GeneratedSwagger/*.json text eol=lf diff --git a/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings index a5b6860b2d..077dd5e21f 100644 --- a/JsonApiDotNetCore.sln.DotSettings +++ b/JsonApiDotNetCore.sln.DotSettings @@ -15,6 +15,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$); 50 False True + 71287D6F-6C3B-44B4-9FCA-E78FE3F02289/f:SchemaGenerator.cs swagger.g.json swagger.json SOLUTION diff --git a/docs/usage/openapi-client.md b/docs/usage/openapi-client.md index 55d5bcb178..3f0b93f52e 100644 --- a/docs/usage/openapi-client.md +++ b/docs/usage/openapi-client.md @@ -3,8 +3,9 @@ You can generate a JSON:API client in various programming languages from the [OpenAPI specification](https://swagger.io/specification/) file that JsonApiDotNetCore APIs provide. For C# .NET clients generated using [NSwag](https://github.com/RicoSuter/NSwag), we provide an additional package -that introduces support for partial PATCH/POST requests. The concern here is that a property on a generated C# class -being `null` could mean "set the value to `null` in the request" or "this is `null` because I never touched it". +that provides workarounds for NSwag bugs and introduces support for partial PATCH/POST requests. +The concern here is that a property on a generated C# class being `null` could either mean: "set the value to `null` +in the request" or: "this is `null` because I never touched it". ## Getting started @@ -26,29 +27,19 @@ The next steps describe how to generate a JSON:API client library and use our pa 3. Although not strictly required, we recommend to run package update now, which fixes some issues. -4. Add code that calls one of your JSON:API endpoints. + > [!WARNING] + > NSwag v14 is currently *incompatible* with JsonApiDotNetCore (tracked [here](https://github.com/RicoSuter/NSwag/issues/4662)). Stick with v13.x for the moment. - ```c# - using var httpClient = new HttpClient(); - var apiClient = new ExampleApiClient("http://localhost:14140", httpClient); - - PersonCollectionResponseDocument getResponse = await apiClient.GetPersonCollectionAsync(new Dictionary - { - ["filter"] = "has(assignedTodoItems)", - ["sort"] = "-lastName", - ["page[size]"] = "5" - }); +4. Add our client package to your project: - foreach (PersonDataInResponse person in getResponse.Data) - { - Console.WriteLine($"Found person {person.Id}: {person.Attributes.DisplayName}"); - } + ``` + dotnet add package JsonApiDotNetCore.OpenApi.Client ``` -5. Add our client package to your project: +5. Add the next line inside the **OpenApiReference** section in your project file: - ``` - dotnet add package JsonApiDotNetCore.OpenApi.Client + ```xml + /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions ``` 6. Add the following glue code to connect our package with your generated code. @@ -73,8 +64,28 @@ The next steps describe how to generate a JSON:API client library and use our pa > [!TIP] > The project at src/Examples/JsonApiDotNetCoreExampleClient contains an enhanced version that logs the HTTP requests and responses. + > Additionally, the example shows how to write the swagger.json file to disk when building the server, which is imported from the client project. This keeps the server and client automatically in sync, which is handy when both are in the same solution. + +7. Add code that calls one of your JSON:API endpoints. + + ```c# + using var httpClient = new HttpClient(); + var apiClient = new ExampleApiClient(httpClient); + + var getResponse = await apiClient.GetPersonCollectionAsync(new Dictionary + { + ["filter"] = "has(assignedTodoItems)", + ["sort"] = "-lastName", + ["page[size]"] = "5" + }); + + foreach (var person in getResponse.Data) + { + Console.WriteLine($"Found person {person.Id}: {person.Attributes.DisplayName}"); + } + ``` -7. Extend your demo code to send a partial PATCH request with the help of our package: +8. Extend your demo code to send a partial PATCH request with the help of our package: ```c# var patchRequest = new PersonPatchRequestDocument @@ -94,7 +105,7 @@ The next steps describe how to generate a JSON:API client library and use our pa person => person.FirstName)) { // Workaround for https://github.com/RicoSuter/NSwag/issues/2499. - await TranslateAsync(async () => await apiClient.PatchPersonAsync(patchRequest.Data.Id, null, patchRequest)); + await ApiResponse.TranslateAsync(() => apiClient.PatchPersonAsync(patchRequest.Data.Id, null, patchRequest)); // The sent request looks like this: // { @@ -145,13 +156,13 @@ From here, continue from step 3 in the list of steps for Visual Studio. The `OpenApiReference` element in the project file accepts an `Options` element to pass additional settings to the client generator, which are listed [here](https://github.com/RicoSuter/NSwag/blob/master/src/NSwag.Commands/Commands/CodeGeneration/OpenApiToCSharpClientCommand.cs). -For example, the next section puts the generated code in a namespace, removes the `baseUrl` parameter and generates an interface (which is handy for dependency injection): +For example, the next section puts the generated code in a namespace and generates an interface (which is handy for dependency injection): ```xml ExampleProject.GeneratedCode SalesApiClient NSwagCSharp - /UseBaseUrl:false /GenerateClientInterfaces:true + /GenerateClientInterfaces:true ``` diff --git a/package-versions.props b/package-versions.props index a836b2cc01..0129d2aee2 100644 --- a/package-versions.props +++ b/package-versions.props @@ -7,6 +7,7 @@ 6.5.0 + 8.0.* 0.13.* 1.0.* 35.2.* diff --git a/src/Examples/JsonApiDotNetCoreExampleClient/OpenAPIs/swagger.json b/src/Examples/JsonApiDotNetCoreExample/GeneratedSwagger/JsonApiDotNetCoreExample.json similarity index 92% rename from src/Examples/JsonApiDotNetCoreExampleClient/OpenAPIs/swagger.json rename to src/Examples/JsonApiDotNetCoreExample/GeneratedSwagger/JsonApiDotNetCoreExample.json index e1a8fa39b0..d5c1ef9db1 100644 --- a/src/Examples/JsonApiDotNetCoreExampleClient/OpenAPIs/swagger.json +++ b/src/Examples/JsonApiDotNetCoreExample/GeneratedSwagger/JsonApiDotNetCoreExample.json @@ -4,6 +4,11 @@ "title": "JsonApiDotNetCoreExample", "version": "1.0" }, + "servers": [ + { + "url": "https://localhost:44340" + } + ], "paths": { "/api/people": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -683,7 +688,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -733,7 +738,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -770,14 +775,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -820,14 +825,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1000,7 +1005,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1038,7 +1043,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1068,7 +1073,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1142,7 +1147,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1192,7 +1197,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1234,7 +1239,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1334,7 +1339,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1384,7 +1389,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1421,14 +1426,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1471,14 +1476,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1651,7 +1656,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1689,7 +1694,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1719,7 +1724,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1793,7 +1798,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1843,7 +1848,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1885,7 +1890,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1985,7 +1990,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2035,7 +2040,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2072,14 +2077,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2122,14 +2127,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2219,7 +2224,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2269,7 +2274,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2306,14 +2311,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2356,14 +2361,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2453,7 +2458,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2503,7 +2508,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2540,14 +2545,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2590,14 +2595,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2755,7 +2760,34 @@ }, "components": { "schemas": { - "linksInRelationshipObject": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "people": "#/components/schemas/personDataInResponse", + "tags": "#/components/schemas/tagDataInResponse", + "todoItems": "#/components/schemas/todoItemDataInResponse" + } + }, + "x-abstract": true + }, + "linksInRelationship": { "required": [ "related", "self" @@ -2801,6 +2833,19 @@ }, "additionalProperties": false }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceDocument": { "required": [ "self" @@ -2871,19 +2916,6 @@ }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullablePersonIdentifierResponseDocument": { "required": [ "data", @@ -2938,6 +2970,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2974,7 +3012,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -3062,6 +3100,12 @@ "$ref": "#/components/schemas/personDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3080,11 +3124,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/personResourceType" - } - ] + "$ref": "#/components/schemas/personResourceType" }, "id": { "minLength": 1, @@ -3114,11 +3154,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/personResourceType" - } - ] + "$ref": "#/components/schemas/personResourceType" }, "attributes": { "allOf": [ @@ -3138,53 +3174,48 @@ "additionalProperties": false }, "personDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/personResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/personAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/personRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/personAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/personRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "personIdentifier": { @@ -3195,11 +3226,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/personResourceType" - } - ] + "$ref": "#/components/schemas/personResourceType" }, "id": { "minLength": 1, @@ -3292,6 +3319,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3366,7 +3399,8 @@ "enum": [ "people" ], - "type": "string" + "type": "string", + "additionalProperties": false }, "personSecondaryResponseDocument": { "required": [ @@ -3389,6 +3423,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3452,6 +3492,12 @@ "$ref": "#/components/schemas/tagDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3470,11 +3516,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/tagResourceType" - } - ] + "$ref": "#/components/schemas/tagResourceType" }, "id": { "minLength": 1, @@ -3504,11 +3546,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/tagResourceType" - } - ] + "$ref": "#/components/schemas/tagResourceType" }, "attributes": { "allOf": [ @@ -3528,53 +3566,48 @@ "additionalProperties": false }, "tagDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/tagResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/tagAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/tagRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/tagAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/tagRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "tagIdentifier": { @@ -3585,11 +3618,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/tagResourceType" - } - ] + "$ref": "#/components/schemas/tagResourceType" }, "id": { "minLength": 1, @@ -3681,6 +3710,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3734,7 +3769,8 @@ "enum": [ "tags" ], - "type": "string" + "type": "string", + "additionalProperties": false }, "toManyTagInRequest": { "required": [ @@ -3760,7 +3796,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -3804,7 +3840,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -3849,7 +3885,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -3966,6 +4002,12 @@ "$ref": "#/components/schemas/todoItemDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3984,11 +4026,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/todoItemResourceType" - } - ] + "$ref": "#/components/schemas/todoItemResourceType" }, "id": { "minLength": 1, @@ -4018,11 +4056,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/todoItemResourceType" - } - ] + "$ref": "#/components/schemas/todoItemResourceType" }, "attributes": { "allOf": [ @@ -4042,53 +4076,48 @@ "additionalProperties": false }, "todoItemDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/todoItemResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/todoItemAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/todoItemRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/todoItemAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/todoItemRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "todoItemIdentifier": { @@ -4099,11 +4128,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/todoItemResourceType" - } - ] + "$ref": "#/components/schemas/todoItemResourceType" }, "id": { "minLength": 1, @@ -4195,6 +4220,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4301,7 +4332,8 @@ "enum": [ "todoItems" ], - "type": "string" + "type": "string", + "additionalProperties": false } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj index 9de430b0f1..5335a96b42 100644 --- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj +++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj @@ -1,6 +1,8 @@ net8.0;net6.0 + true + GeneratedSwagger @@ -16,5 +18,6 @@ + diff --git a/src/Examples/JsonApiDotNetCoreExample/Program.cs b/src/Examples/JsonApiDotNetCoreExample/Program.cs index da02db1ba4..332235d491 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Program.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Program.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Diagnostics; using JsonApiDotNetCore.OpenApi; +using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; @@ -16,7 +17,10 @@ WebApplication app = CreateWebApplication(args); -await CreateDatabaseAsync(app.Services); +if (!IsGeneratingOpenApiDocumentAtBuildTime()) +{ + await CreateDatabaseAsync(app.Services); +} app.Run(); @@ -83,7 +87,7 @@ static void ConfigureServices(WebApplicationBuilder builder) using (CodeTimingSessionManager.Current.Measure("AddOpenApi()")) { - builder.Services.AddOpenApi(mvcCoreBuilder); + builder.Services.AddOpenApi(mvcCoreBuilder, options => options.DocumentFilter()); } } @@ -112,6 +116,11 @@ static void ConfigurePipeline(WebApplication app) app.MapControllers(); } +static bool IsGeneratingOpenApiDocumentAtBuildTime() +{ + return Environment.GetCommandLineArgs().Any(argument => argument.Contains("GetDocument.Insider")); +} + static async Task CreateDatabaseAsync(IServiceProvider serviceProvider) { await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope(); diff --git a/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs b/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs new file mode 100644 index 0000000000..894c0d0966 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/SetOpenApiServerAtBuildTimeFilter.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCoreExample; + +/// +/// This is normally not needed. It ensures the server URL is added to the OpenAPI file during build. +/// +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +internal sealed class SetOpenApiServerAtBuildTimeFilter(IHttpContextAccessor httpContextAccessor) : IDocumentFilter +{ + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + if (_httpContextAccessor.HttpContext == null) + { + swaggerDoc.Servers.Add(new OpenApiServer + { + Url = "https://localhost:44340" + }); + } + } +} diff --git a/src/Examples/JsonApiDotNetCoreExampleClient/ColoredConsoleLogDelegatingHandler.cs b/src/Examples/JsonApiDotNetCoreExampleClient/ColoredConsoleLogDelegatingHandler.cs new file mode 100644 index 0000000000..88679e112f --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExampleClient/ColoredConsoleLogDelegatingHandler.cs @@ -0,0 +1,62 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCoreExampleClient; + +/// +/// Writes incoming and outgoing HTTP messages to the console. +/// +[UsedImplicitly] +internal sealed class ColoredConsoleLogDelegatingHandler : DelegatingHandler +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + await LogRequestAsync(request, cancellationToken); + + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + + await LogResponseAsync(response, cancellationToken); + + return response; + } + + private static async Task LogRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + using var _ = new ConsoleColorScope(ConsoleColor.Green); + + Console.WriteLine($"--> {request}"); + string? requestBody = request.Content != null ? await request.Content.ReadAsStringAsync(cancellationToken) : null; + + if (!string.IsNullOrEmpty(requestBody)) + { + Console.WriteLine(); + Console.WriteLine(requestBody); + } + } + + private static async Task LogResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken) + { + using var _ = new ConsoleColorScope(ConsoleColor.Cyan); + + Console.WriteLine($"<-- {response}"); + string responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + if (!string.IsNullOrEmpty(responseBody)) + { + Console.WriteLine(); + Console.WriteLine(responseBody); + } + } + + private sealed class ConsoleColorScope : IDisposable + { + public ConsoleColorScope(ConsoleColor foregroundColor) + { + Console.ForegroundColor = foregroundColor; + } + + public void Dispose() + { + Console.ResetColor(); + } + } +} diff --git a/src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs b/src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs index 8a5b5d3c46..e55e79d97e 100644 --- a/src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs +++ b/src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs @@ -2,8 +2,6 @@ using JsonApiDotNetCore.OpenApi.Client; using Newtonsoft.Json; -// ReSharper disable UnusedParameterInPartialMethod - namespace JsonApiDotNetCoreExampleClient; [UsedImplicitly(ImplicitUseTargetFlags.Itself)] @@ -13,50 +11,8 @@ partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings) { SetSerializerSettingsForJsonApi(settings); - // Optional: Makes the JSON easier to read when logged. +#if DEBUG settings.Formatting = Formatting.Indented; - } - - // Optional: Log outgoing request to the console. - partial void PrepareRequest(HttpClient client, HttpRequestMessage request, string url) - { - using var _ = new UsingConsoleColor(ConsoleColor.Green); - - Console.WriteLine($"--> {request}"); - string? requestBody = request.Content?.ReadAsStringAsync().GetAwaiter().GetResult(); - - if (!string.IsNullOrEmpty(requestBody)) - { - Console.WriteLine(); - Console.WriteLine(requestBody); - } - } - - // Optional: Log incoming response to the console. - partial void ProcessResponse(HttpClient client, HttpResponseMessage response) - { - using var _ = new UsingConsoleColor(ConsoleColor.Cyan); - - Console.WriteLine($"<-- {response}"); - string responseBody = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - - if (!string.IsNullOrEmpty(responseBody)) - { - Console.WriteLine(); - Console.WriteLine(responseBody); - } - } - - private sealed class UsingConsoleColor : IDisposable - { - public UsingConsoleColor(ConsoleColor foregroundColor) - { - Console.ForegroundColor = foregroundColor; - } - - public void Dispose() - { - Console.ResetColor(); - } +#endif } } diff --git a/src/Examples/JsonApiDotNetCoreExampleClient/JsonApiDotNetCoreExampleClient.csproj b/src/Examples/JsonApiDotNetCoreExampleClient/JsonApiDotNetCoreExampleClient.csproj index 1882a957a7..7d03f808b6 100644 --- a/src/Examples/JsonApiDotNetCoreExampleClient/JsonApiDotNetCoreExampleClient.csproj +++ b/src/Examples/JsonApiDotNetCoreExampleClient/JsonApiDotNetCoreExampleClient.csproj @@ -24,8 +24,8 @@ - - http://localhost:14140/swagger/v1/swagger.json + + /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions diff --git a/src/Examples/JsonApiDotNetCoreExampleClient/Program.cs b/src/Examples/JsonApiDotNetCoreExampleClient/Program.cs index b35f5723f2..a174a94747 100644 --- a/src/Examples/JsonApiDotNetCoreExampleClient/Program.cs +++ b/src/Examples/JsonApiDotNetCoreExampleClient/Program.cs @@ -1,18 +1,28 @@ +using JsonApiDotNetCore.OpenApi.Client; using JsonApiDotNetCoreExampleClient; +#if DEBUG +using var httpClient = new HttpClient(new ColoredConsoleLogDelegatingHandler +{ + InnerHandler = new HttpClientHandler() +}); +#else using var httpClient = new HttpClient(); -var apiClient = new ExampleApiClient("http://localhost:14140", httpClient); +#endif + +var apiClient = new ExampleApiClient(httpClient); PersonCollectionResponseDocument getResponse = await apiClient.GetPersonCollectionAsync(new Dictionary { ["filter"] = "has(assignedTodoItems)", ["sort"] = "-lastName", - ["page[size]"] = "5" + ["page[size]"] = "5", + ["include"] = "assignedTodoItems.tags" }); foreach (PersonDataInResponse person in getResponse.Data) { - Console.WriteLine($"Found person {person.Id}: {person.Attributes.DisplayName}"); + PrintPerson(person, getResponse.Included); } var patchRequest = new PersonPatchRequestDocument @@ -31,23 +41,37 @@ using (apiClient.WithPartialAttributeSerialization(patchRequest, person => person.FirstName)) { // Workaround for https://github.com/RicoSuter/NSwag/issues/2499. - await TranslateAsync(() => apiClient.PatchPersonAsync(patchRequest.Data.Id, null, patchRequest)); + await ApiResponse.TranslateAsync(() => apiClient.PatchPersonAsync(patchRequest.Data.Id, null, patchRequest)); } Console.WriteLine("Press any key to close."); Console.ReadKey(); -// ReSharper disable once UnusedLocalFunctionReturnValue -static async Task TranslateAsync(Func> operation) - where TResponse : class +static void PrintPerson(PersonDataInResponse person, ICollection includes) +{ + ToManyTodoItemInResponse assignedTodoItems = person.Relationships.AssignedTodoItems; + + Console.WriteLine($"Found person {person.Id}: {person.Attributes.DisplayName} with {assignedTodoItems.Data.Count} assigned todo-items:"); + + PrintRelatedTodoItems(assignedTodoItems.Data, includes); +} + +static void PrintRelatedTodoItems(IEnumerable todoItemIdentifiers, ICollection includes) { - try + foreach (TodoItemIdentifier todoItemIdentifier in todoItemIdentifiers) { - return await operation(); + TodoItemDataInResponse includedTodoItem = includes.OfType().Single(include => include.Id == todoItemIdentifier.Id); + Console.WriteLine($" TodoItem {includedTodoItem.Id}: {includedTodoItem.Attributes.Description}"); + + PrintRelatedTags(includedTodoItem.Relationships.Tags.Data, includes); } - catch (ApiException exception) when (exception.StatusCode == 204) +} + +static void PrintRelatedTags(IEnumerable tagIdentifiers, ICollection includes) +{ + foreach (TagIdentifier tagIdentifier in tagIdentifiers) { - // Workaround for https://github.com/RicoSuter/NSwag/issues/2499 - return null; + TagDataInResponse includedTag = includes.OfType().Single(include => include.Id == tagIdentifier.Id); + Console.WriteLine($" Tag {includedTag.Id}: {includedTag.Attributes.Name}"); } } diff --git a/src/JsonApiDotNetCore.OpenApi.Client/ApiResponse.cs b/src/JsonApiDotNetCore.OpenApi.Client/ApiResponse.cs index a94e5062dc..0c99f88209 100644 --- a/src/JsonApiDotNetCore.OpenApi.Client/ApiResponse.cs +++ b/src/JsonApiDotNetCore.OpenApi.Client/ApiResponse.cs @@ -23,4 +23,18 @@ public static class ApiResponse return null; } } + + public static async Task TranslateAsync(Func operation) + { + ArgumentGuard.NotNull(operation); + + try + { + await operation(); + } + catch (ApiException exception) when (exception.StatusCode == 204) + { + // Workaround for https://github.com/RicoSuter/NSwag/issues/2499 + } + } } diff --git a/src/JsonApiDotNetCore.OpenApi/ConfigureMvcOptions.cs b/src/JsonApiDotNetCore.OpenApi/ConfigureMvcOptions.cs new file mode 100644 index 0000000000..3bcc23587e --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/ConfigureMvcOptions.cs @@ -0,0 +1,42 @@ +using JsonApiDotNetCore.Middleware; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace JsonApiDotNetCore.OpenApi; + +internal sealed class ConfigureMvcOptions : IConfigureOptions +{ + private readonly IControllerResourceMapping _controllerResourceMapping; + private readonly IJsonApiRoutingConvention _jsonApiRoutingConvention; + + public ConfigureMvcOptions(IControllerResourceMapping controllerResourceMapping, IJsonApiRoutingConvention jsonApiRoutingConvention) + { + ArgumentGuard.NotNull(controllerResourceMapping); + ArgumentGuard.NotNull(jsonApiRoutingConvention); + + _controllerResourceMapping = controllerResourceMapping; + _jsonApiRoutingConvention = jsonApiRoutingConvention; + } + + public void Configure(MvcOptions options) + { + AddSwashbuckleCliCompatibility(options); + AddOpenApiEndpointConvention(options); + } + + private void AddSwashbuckleCliCompatibility(MvcOptions options) + { + if (!options.Conventions.Any(convention => convention is IJsonApiRoutingConvention)) + { + // See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1957 for why this is needed. + options.Conventions.Insert(0, _jsonApiRoutingConvention); + } + } + + private void AddOpenApiEndpointConvention(MvcOptions options) + { + var convention = new OpenApiEndpointConvention(_controllerResourceMapping); + options.Conventions.Add(convention); + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/ConfigureSwaggerGenOptions.cs b/src/JsonApiDotNetCore.OpenApi/ConfigureSwaggerGenOptions.cs new file mode 100644 index 0000000000..3a11157978 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/ConfigureSwaggerGenOptions.cs @@ -0,0 +1,93 @@ +using System.Reflection; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; +using JsonApiDotNetCore.OpenApi.SwaggerComponents; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCore.OpenApi; + +internal sealed class ConfigureSwaggerGenOptions : IConfigureOptions +{ + private readonly IControllerResourceMapping _controllerResourceMapping; + private readonly JsonApiOperationIdSelector _operationIdSelector; + private readonly JsonApiSchemaIdSelector _schemaIdSelector; + private readonly IResourceGraph _resourceGraph; + + public ConfigureSwaggerGenOptions(IControllerResourceMapping controllerResourceMapping, JsonApiOperationIdSelector operationIdSelector, + JsonApiSchemaIdSelector schemaIdSelector, IResourceGraph resourceGraph) + { + ArgumentGuard.NotNull(controllerResourceMapping); + ArgumentGuard.NotNull(operationIdSelector); + ArgumentGuard.NotNull(schemaIdSelector); + ArgumentGuard.NotNull(resourceGraph); + + _controllerResourceMapping = controllerResourceMapping; + _operationIdSelector = operationIdSelector; + _schemaIdSelector = schemaIdSelector; + _resourceGraph = resourceGraph; + } + + public void Configure(SwaggerGenOptions options) + { + options.SupportNonNullableReferenceTypes(); + options.UseAllOfToExtendReferenceSchemas(); + + options.UseAllOfForInheritance(); + options.SelectDiscriminatorNameUsing(_ => "type"); + options.SelectDiscriminatorValueUsing(clrType => _resourceGraph.GetResourceType(clrType).PublicName); + options.SelectSubTypesUsing(GetConstructedTypesForResourceData); + + SetOperationInfo(options, _controllerResourceMapping); + SetSchemaIdSelector(options); + + options.DocumentFilter(); + options.DocumentFilter(); + options.OperationFilter(); + } + + private IEnumerable GetConstructedTypesForResourceData(Type baseType) + { + if (baseType != typeof(ResourceData)) + { + return []; + } + + List derivedTypes = []; + + foreach (ResourceType resourceType in _resourceGraph.GetResourceTypes()) + { + Type constructedType = typeof(ResourceDataInResponse<>).MakeGenericType(resourceType.ClrType); + derivedTypes.Add(constructedType); + } + + return derivedTypes; + } + + private void SetOperationInfo(SwaggerGenOptions swaggerGenOptions, IControllerResourceMapping controllerResourceMapping) + { + swaggerGenOptions.TagActionsBy(description => GetOperationTags(description, controllerResourceMapping)); + swaggerGenOptions.CustomOperationIds(_operationIdSelector.GetOperationId); + } + + private static IList GetOperationTags(ApiDescription description, IControllerResourceMapping controllerResourceMapping) + { + MethodInfo actionMethod = description.ActionDescriptor.GetActionMethod(); + ResourceType? resourceType = controllerResourceMapping.GetResourceTypeForController(actionMethod.ReflectedType); + + if (resourceType == null) + { + throw new NotSupportedException("Only JsonApiDotNetCore endpoints are supported."); + } + + return [resourceType.PublicName]; + } + + private void SetSchemaIdSelector(SwaggerGenOptions swaggerGenOptions) + { + swaggerGenOptions.CustomSchemaIds(_schemaIdSelector.GetSchemaId); + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/IncludeDependencyScanner.cs b/src/JsonApiDotNetCore.OpenApi/IncludeDependencyScanner.cs new file mode 100644 index 0000000000..4507268793 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/IncludeDependencyScanner.cs @@ -0,0 +1,32 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCore.OpenApi; + +internal sealed class IncludeDependencyScanner +{ + /// + /// Returns all related resource types that are reachable from the specified resource type. May include itself. + /// + public IReadOnlySet GetReachableRelatedTypes(ResourceType resourceType) + { + ArgumentGuard.NotNull(resourceType); + + var resourceTypesFound = new HashSet(); + AddTypesFromRelationships(resourceType.Relationships, resourceTypesFound); + return resourceTypesFound; + } + + private static void AddTypesFromRelationships(IEnumerable relationships, ISet resourceTypesFound) + { + foreach (RelationshipAttribute relationship in relationships) + { + ResourceType resourceType = relationship.RightType; + + if (resourceTypesFound.Add(resourceType)) + { + AddTypesFromRelationships(resourceType.Relationships, resourceTypesFound); + } + } + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableResourceIdentifierResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableResourceIdentifierResponseDocument.cs index 1d46bd26cd..f5b1bee34a 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableResourceIdentifierResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableResourceIdentifierResponseDocument.cs @@ -10,11 +10,11 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; // Types in the current namespace are never touched by ASP.NET ModelState validation, therefore using a non-nullable reference type for a property does not // imply this property is required. Instead, we use [Required] explicitly, because this is how Swashbuckle is instructed to mark properties as required. [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class NullableResourceIdentifierResponseDocument : NullableSingleData> +internal sealed class NullableResourceIdentifierResponseDocument : NullableSingleData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableSecondaryResourceResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableSecondaryResourceResponseDocument.cs index 4c141e55c5..846cb2abbe 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableSecondaryResourceResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/NullableSecondaryResourceResponseDocument.cs @@ -8,16 +8,19 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class NullableSecondaryResourceResponseDocument : NullableSingleData> +internal sealed class NullableSecondaryResourceResponseDocument : NullableSingleData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] public LinksInResourceDocument Links { get; set; } = null!; + [JsonPropertyName("included")] + public IList Included { get; set; } = null!; + [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/PrimaryResourceResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/PrimaryResourceResponseDocument.cs index f66048a422..b4c5350a9a 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/PrimaryResourceResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/PrimaryResourceResponseDocument.cs @@ -8,16 +8,19 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class PrimaryResourceResponseDocument : SingleData> +internal sealed class PrimaryResourceResponseDocument : SingleData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] public LinksInResourceDocument Links { get; set; } = null!; + [JsonPropertyName("included")] + public IList Included { get; set; } = null!; + [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceCollectionResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceCollectionResponseDocument.cs index 109c2cde50..9eb76586a1 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceCollectionResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceCollectionResponseDocument.cs @@ -8,16 +8,19 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourceCollectionResponseDocument : ManyData> +internal sealed class ResourceCollectionResponseDocument : ManyData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] public LinksInResourceCollectionDocument Links { get; set; } = null!; + [JsonPropertyName("included")] + public IList Included { get; set; } = null!; + [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierCollectionResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierCollectionResponseDocument.cs index d0b7ca4422..ed262ca324 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierCollectionResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierCollectionResponseDocument.cs @@ -8,11 +8,11 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourceIdentifierCollectionResponseDocument : ManyData> +internal sealed class ResourceIdentifierCollectionResponseDocument : ManyData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierResponseDocument.cs index dd479129fb..be16ed69ef 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourceIdentifierResponseDocument.cs @@ -8,11 +8,11 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourceIdentifierResponseDocument : SingleData> +internal sealed class ResourceIdentifierResponseDocument : SingleData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePatchRequestDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePatchRequestDocument.cs index f76e871bc0..c06427b29c 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePatchRequestDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePatchRequestDocument.cs @@ -5,5 +5,5 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourcePatchRequestDocument : SingleData> +internal sealed class ResourcePatchRequestDocument : SingleData> where TResource : IIdentifiable; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePostRequestDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePostRequestDocument.cs index 1d1a5e8651..1c9975b3e4 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePostRequestDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/ResourcePostRequestDocument.cs @@ -5,5 +5,5 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourcePostRequestDocument : SingleData> +internal sealed class ResourcePostRequestDocument : SingleData> where TResource : IIdentifiable; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/SecondaryResourceResponseDocument.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/SecondaryResourceResponseDocument.cs index e895e2c22d..6d6db39bce 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/SecondaryResourceResponseDocument.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Documents/SecondaryResourceResponseDocument.cs @@ -8,16 +8,19 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class SecondaryResourceResponseDocument : SingleData> +internal sealed class SecondaryResourceResponseDocument : SingleData> where TResource : IIdentifiable { [JsonPropertyName("jsonapi")] - public JsonapiObject Jsonapi { get; set; } = null!; + public Jsonapi Jsonapi { get; set; } = null!; [Required] [JsonPropertyName("links")] public LinksInResourceDocument Links { get; set; } = null!; + [JsonPropertyName("included")] + public IList Included { get; set; } = null!; + [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/JsonapiObject.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Jsonapi.cs similarity index 93% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/JsonapiObject.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Jsonapi.cs index 8ad3074e4f..dcfc6ae2ca 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/JsonapiObject.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Jsonapi.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class JsonapiObject +internal sealed class Jsonapi { [JsonPropertyName("version")] public string Version { get; set; } = null!; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInRelationshipObject.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInRelationship.cs similarity index 89% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInRelationshipObject.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInRelationship.cs index 16b9735dac..f67c02f79b 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInRelationshipObject.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInRelationship.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Links; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class LinksInRelationshipObject +internal sealed class LinksInRelationship { [Required] [JsonPropertyName("self")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInResourceObject.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInResourceData.cs similarity index 87% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInResourceObject.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInResourceData.cs index 552cc703e3..95d55fc545 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInResourceObject.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Links/LinksInResourceData.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Links; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class LinksInResourceObject +internal sealed class LinksInResourceData { [Required] [JsonPropertyName("self")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ManyData.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ManyData.cs index 5f80fd0f2b..34099658d9 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ManyData.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ManyData.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] internal abstract class ManyData - where TData : ResourceIdentifierObject + where TData : class, IResourceIdentity { [Required] [JsonPropertyName("data")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NullableSingleData.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NullableSingleData.cs index 27063648cb..5bfad0cabe 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NullableSingleData.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/NullableSingleData.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] internal abstract class NullableSingleData - where TData : ResourceIdentifierObject + where TData : class, IResourceIdentity { [Required] [JsonPropertyName("data")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInRequest.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInRequest.cs index 2556f7d2cf..95b7821f48 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInRequest.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInRequest.cs @@ -5,5 +5,5 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class NullableToOneRelationshipInRequest : NullableSingleData> +internal sealed class NullableToOneRelationshipInRequest : NullableSingleData> where TResource : IIdentifiable; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInResponse.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInResponse.cs index fdc45b811a..ae0a6ed539 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInResponse.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/NullableToOneRelationshipInResponse.cs @@ -8,12 +8,12 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class NullableToOneRelationshipInResponse : NullableSingleData> +internal sealed class NullableToOneRelationshipInResponse : NullableSingleData> where TResource : IIdentifiable { [Required] [JsonPropertyName("links")] - public LinksInRelationshipObject Links { get; set; } = null!; + public LinksInRelationship Links { get; set; } = null!; [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInRequest.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInRequest.cs index 1161ca8d37..e2a3865ba8 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInRequest.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInRequest.cs @@ -5,5 +5,5 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ToManyRelationshipInRequest : ManyData> +internal sealed class ToManyRelationshipInRequest : ManyData> where TResource : IIdentifiable; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInResponse.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInResponse.cs index 14380f025e..51436b463c 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInResponse.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToManyRelationshipInResponse.cs @@ -8,12 +8,12 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ToManyRelationshipInResponse : ManyData> +internal sealed class ToManyRelationshipInResponse : ManyData> where TResource : IIdentifiable { [Required] [JsonPropertyName("links")] - public LinksInRelationshipObject Links { get; set; } = null!; + public LinksInRelationship Links { get; set; } = null!; [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInRequest.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInRequest.cs index 8d377cea65..7fdc2275d0 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInRequest.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInRequest.cs @@ -5,5 +5,5 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ToOneRelationshipInRequest : SingleData> +internal sealed class ToOneRelationshipInRequest : SingleData> where TResource : IIdentifiable; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInResponse.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInResponse.cs index a867dffdc0..94b11f630e 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInResponse.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/Relationships/ToOneRelationshipInResponse.cs @@ -8,12 +8,12 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ToOneRelationshipInResponse : SingleData> +internal sealed class ToOneRelationshipInResponse : SingleData> where TResource : IIdentifiable { [Required] [JsonPropertyName("links")] - public LinksInRelationshipObject Links { get; set; } = null!; + public LinksInRelationship Links { get; set; } = null!; [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/IResourceIdentity.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/IResourceIdentity.cs new file mode 100644 index 0000000000..46ad6aae8c --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/IResourceIdentity.cs @@ -0,0 +1,10 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +internal interface IResourceIdentity +{ + string Type { get; set; } + string Id { get; set; } +} diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceData.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceData.cs new file mode 100644 index 0000000000..21fcf96dff --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceData.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +internal abstract class ResourceData : IResourceIdentity +{ + [Required] + [JsonPropertyName("type")] + public string Type { get; set; } = null!; + + [Required] + [JsonPropertyName("id")] + public string Id { get; set; } = null!; +} diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInPatchRequest.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInPatchRequest.cs similarity index 85% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInPatchRequest.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInPatchRequest.cs index 7263ededd4..248433afc4 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInPatchRequest.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInPatchRequest.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourceObjectInPatchRequest : ResourceIdentifierObject +internal sealed class ResourceDataInPatchRequest : ResourceData where TResource : IIdentifiable { [JsonPropertyName("attributes")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInPostRequest.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInPostRequest.cs similarity index 85% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInPostRequest.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInPostRequest.cs index f886883b4e..7310620ad0 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInPostRequest.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInPostRequest.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourceObjectInPostRequest : ResourceIdentifierObject +internal sealed class ResourceDataInPostRequest : ResourceData where TResource : IIdentifiable { [JsonPropertyName("attributes")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInResponse.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInResponse.cs similarity index 83% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInResponse.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInResponse.cs index ae61d9822c..c7ed1d02fd 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceObjectInResponse.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceDataInResponse.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal sealed class ResourceObjectInResponse : ResourceIdentifierObject +internal sealed class ResourceDataInResponse : ResourceData where TResource : IIdentifiable { [JsonPropertyName("attributes")] @@ -18,7 +18,7 @@ internal sealed class ResourceObjectInResponse : ResourceIdentifierOb [Required] [JsonPropertyName("links")] - public LinksInResourceObject Links { get; set; } = null!; + public LinksInResourceData Links { get; set; } = null!; [JsonPropertyName("meta")] public IDictionary Meta { get; set; } = null!; diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceIdentifier.cs similarity index 63% rename from src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceIdentifierObject.cs rename to src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceIdentifier.cs index 9a779627ea..3b8b8e5272 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceIdentifierObject.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/ResourceObjects/ResourceIdentifier.cs @@ -1,16 +1,12 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -using JetBrains.Annotations; using JsonApiDotNetCore.Resources; namespace JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; // ReSharper disable once UnusedTypeParameter -internal sealed class ResourceIdentifierObject : ResourceIdentifierObject - where TResource : IIdentifiable; - -[UsedImplicitly(ImplicitUseTargetFlags.Members)] -internal class ResourceIdentifierObject +internal sealed class ResourceIdentifier : IResourceIdentity + where TResource : IIdentifiable { [Required] [JsonPropertyName("type")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/SingleData.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/SingleData.cs index 019fc58c2d..451b2a974f 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/SingleData.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiObjects/SingleData.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.JsonApiObjects; [UsedImplicitly(ImplicitUseTargetFlags.Members)] internal abstract class SingleData - where TData : ResourceIdentifierObject + where TData : class, IResourceIdentity { [Required] [JsonPropertyName("data")] diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs index 2da76a3931..17642015ff 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiOperationIdSelector.cs @@ -35,14 +35,15 @@ internal sealed class JsonApiOperationIdSelector }; private readonly IControllerResourceMapping _controllerResourceMapping; - private readonly JsonNamingPolicy? _namingPolicy; + private readonly IJsonApiOptions _options; - public JsonApiOperationIdSelector(IControllerResourceMapping controllerResourceMapping, JsonNamingPolicy? namingPolicy) + public JsonApiOperationIdSelector(IControllerResourceMapping controllerResourceMapping, IJsonApiOptions options) { ArgumentGuard.NotNull(controllerResourceMapping); + ArgumentGuard.NotNull(options); _controllerResourceMapping = controllerResourceMapping; - _namingPolicy = namingPolicy; + _options = options; } public string GetOperationId(ApiDescription endpoint) @@ -122,6 +123,7 @@ private string ApplyTemplate(string operationIdTemplate, ResourceType resourceTy // @formatter:wrap_before_first_method_call true restore // @formatter:wrap_chained_method_calls restore - return _namingPolicy != null ? _namingPolicy.ConvertName(pascalCaseOperationId) : pascalCaseOperationId; + JsonNamingPolicy? namingPolicy = _options.SerializerOptions.PropertyNamingPolicy; + return namingPolicy != null ? namingPolicy.ConvertName(pascalCaseOperationId) : pascalCaseOperationId; } } diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs index 309500954b..97aed20050 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiRequestFormatMetadataProvider.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.OpenApi; internal sealed class JsonApiRequestFormatMetadataProvider : IInputFormatter, IApiRequestFormatMetadataProvider { - private static readonly Type[] JsonApiRequestObjectOpenType = + private static readonly Type[] JsonApiRequestOpenTypes = [ typeof(ToManyRelationshipInRequest<>), typeof(ToOneRelationshipInRequest<>), @@ -36,8 +36,7 @@ public IReadOnlyList GetSupportedContentTypes(string contentType, Type o ArgumentGuard.NotNullNorEmpty(contentType); ArgumentGuard.NotNull(objectType); - if (contentType == HeaderConstants.MediaType && objectType.IsGenericType && - JsonApiRequestObjectOpenType.Contains(objectType.GetGenericTypeDefinition())) + if (contentType == HeaderConstants.MediaType && objectType.IsGenericType && JsonApiRequestOpenTypes.Contains(objectType.GetGenericTypeDefinition())) { return new MediaTypeCollection { diff --git a/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs b/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs index c62bf662ff..cd9816fbde 100644 --- a/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs +++ b/src/JsonApiDotNetCore.OpenApi/JsonApiSchemaIdSelector.cs @@ -9,14 +9,16 @@ namespace JsonApiDotNetCore.OpenApi; internal sealed class JsonApiSchemaIdSelector { - private static readonly IDictionary OpenTypeToSchemaTemplateMap = new Dictionary + private const string ResourceTypeSchemaIdTemplate = "[ResourceName] Resource Type"; + + private static readonly IDictionary TypeToSchemaTemplateMap = new Dictionary { [typeof(ResourcePostRequestDocument<>)] = "[ResourceName] Post Request Document", [typeof(ResourcePatchRequestDocument<>)] = "[ResourceName] Patch Request Document", - [typeof(ResourceObjectInPostRequest<>)] = "[ResourceName] Data In Post Request", + [typeof(ResourceDataInPostRequest<>)] = "[ResourceName] Data In Post Request", [typeof(AttributesInPostRequest<>)] = "[ResourceName] Attributes In Post Request", [typeof(RelationshipsInPostRequest<>)] = "[ResourceName] Relationships In Post Request", - [typeof(ResourceObjectInPatchRequest<>)] = "[ResourceName] Data In Patch Request", + [typeof(ResourceDataInPatchRequest<>)] = "[ResourceName] Data In Patch Request", [typeof(AttributesInPatchRequest<>)] = "[ResourceName] Attributes In Patch Request", [typeof(RelationshipsInPatchRequest<>)] = "[ResourceName] Relationships In Patch Request", [typeof(ToOneRelationshipInRequest<>)] = "To One [ResourceName] In Request", @@ -32,21 +34,23 @@ internal sealed class JsonApiSchemaIdSelector [typeof(ToOneRelationshipInResponse<>)] = "To One [ResourceName] In Response", [typeof(NullableToOneRelationshipInResponse<>)] = "Nullable To One [ResourceName] In Response", [typeof(ToManyRelationshipInResponse<>)] = "To Many [ResourceName] In Response", - [typeof(ResourceObjectInResponse<>)] = "[ResourceName] Data In Response", + [typeof(ResourceData)] = "Data In Response", + [typeof(ResourceDataInResponse<>)] = "[ResourceName] Data In Response", [typeof(AttributesInResponse<>)] = "[ResourceName] Attributes In Response", [typeof(RelationshipsInResponse<>)] = "[ResourceName] Relationships In Response", - [typeof(ResourceIdentifierObject<>)] = "[ResourceName] Identifier" + [typeof(ResourceIdentifier<>)] = "[ResourceName] Identifier" }; - private readonly JsonNamingPolicy? _namingPolicy; private readonly IResourceGraph _resourceGraph; + private readonly IJsonApiOptions _options; - public JsonApiSchemaIdSelector(JsonNamingPolicy? namingPolicy, IResourceGraph resourceGraph) + public JsonApiSchemaIdSelector(IResourceGraph resourceGraph, IJsonApiOptions options) { ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(options); - _namingPolicy = namingPolicy; _resourceGraph = resourceGraph; + _options = options; } public string GetSchemaId(Type type) @@ -60,23 +64,44 @@ public string GetSchemaId(Type type) return resourceType.PublicName.Singularize(); } - if (type.IsConstructedGenericType && OpenTypeToSchemaTemplateMap.ContainsKey(type.GetGenericTypeDefinition())) + if (type.IsConstructedGenericType) { Type openType = type.GetGenericTypeDefinition(); - Type resourceClrType = type.GetGenericArguments().First(); - resourceType = _resourceGraph.FindResourceType(resourceClrType); - if (resourceType == null) + if (TypeToSchemaTemplateMap.TryGetValue(openType, out string? schemaTemplate)) { - throw new UnreachableCodeException(); + Type resourceClrType = type.GetGenericArguments().First(); + resourceType = _resourceGraph.GetResourceType(resourceClrType); + + return ApplySchemaTemplate(schemaTemplate, resourceType); } + } + else + { + if (TypeToSchemaTemplateMap.TryGetValue(type, out string? schemaTemplate)) + { + return ApplySchemaTemplate(schemaTemplate, null); + } + } - string pascalCaseSchemaId = OpenTypeToSchemaTemplateMap[openType].Replace("[ResourceName]", resourceType.PublicName.Singularize()).ToPascalCase(); + // Used for a fixed set of non-generic types, such as Jsonapi, LinksInResourceCollectionDocument etc. + return ApplySchemaTemplate(type.Name, null); + } - return _namingPolicy != null ? _namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId; - } + private string ApplySchemaTemplate(string schemaTemplate, ResourceType? resourceType) + { + string pascalCaseSchemaId = resourceType != null + ? schemaTemplate.Replace("[ResourceName]", resourceType.PublicName.Singularize()).ToPascalCase() + : schemaTemplate.ToPascalCase(); + + JsonNamingPolicy? namingPolicy = _options.SerializerOptions.PropertyNamingPolicy; + return namingPolicy != null ? namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId; + } + + public string GetSchemaId(ResourceType resourceType) + { + ArgumentGuard.NotNull(resourceType); - // Used for a fixed set of types, such as JsonApiObject, LinksInResourceCollectionDocument etc. - return _namingPolicy != null ? _namingPolicy.ConvertName(type.Name) : type.Name; + return ApplySchemaTemplate(ResourceTypeSchemaIdTemplate, resourceType); } } diff --git a/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs b/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs index e40ab57618..82d59ac40d 100644 --- a/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs +++ b/src/JsonApiDotNetCore.OpenApi/ResourceFieldValidationMetadataProvider.cs @@ -9,8 +9,7 @@ namespace JsonApiDotNetCore.OpenApi; internal sealed class ResourceFieldValidationMetadataProvider { - private readonly bool _validateModelState; - private readonly NullabilityInfoContext _nullabilityContext = new(); + private readonly IJsonApiOptions _options; private readonly IModelMetadataProvider _modelMetadataProvider; public ResourceFieldValidationMetadataProvider(IJsonApiOptions options, IModelMetadataProvider modelMetadataProvider) @@ -18,7 +17,7 @@ public ResourceFieldValidationMetadataProvider(IJsonApiOptions options, IModelMe ArgumentGuard.NotNull(options); ArgumentGuard.NotNull(modelMetadataProvider); - _validateModelState = options.ValidateModelState; + _options = options; _modelMetadataProvider = modelMetadataProvider; } @@ -33,12 +32,13 @@ public bool IsNullable(ResourceFieldAttribute field) bool hasRequiredAttribute = field.Property.HasAttribute(); - if (_validateModelState && hasRequiredAttribute) + if (_options.ValidateModelState && hasRequiredAttribute) { return false; } - NullabilityInfo nullabilityInfo = _nullabilityContext.Create(field.Property); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(field.Property); return nullabilityInfo.ReadState != NullabilityState.NotNull; } @@ -48,7 +48,7 @@ public bool IsRequired(ResourceFieldAttribute field) bool hasRequiredAttribute = field.Property.HasAttribute(); - if (!_validateModelState) + if (!_options.ValidateModelState) { return hasRequiredAttribute; } @@ -58,7 +58,8 @@ public bool IsRequired(ResourceFieldAttribute field) return false; } - NullabilityInfo nullabilityInfo = _nullabilityContext.Create(field.Property); + NullabilityInfoContext nullabilityContext = new(); + NullabilityInfo nullabilityInfo = nullabilityContext.Create(field.Property); bool isRequiredValueType = field.Property.PropertyType.IsValueType && hasRequiredAttribute && nullabilityInfo.ReadState == NullabilityState.NotNull; if (isRequiredValueType) diff --git a/src/JsonApiDotNetCore.OpenApi/SchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SchemaGenerator.cs new file mode 100644 index 0000000000..7bc287fe95 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/SchemaGenerator.cs @@ -0,0 +1,518 @@ +// This file is a copy of https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs +// It was patched to fix broken inheritance using allOf. Changed code is marked with PATCH-START/PATCH-END comments. + +// PATCH-START +#nullable disable +#pragma warning disable +// PATCH-END + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.OpenApi.Models; + +// PATCH-START +namespace Swashbuckle.AspNetCore.SwaggerGen.Patched +//namespace Swashbuckle.AspNetCore.SwaggerGen +// PATCH-END +{ + public class SchemaGenerator : ISchemaGenerator + { + private readonly SchemaGeneratorOptions _generatorOptions; + private readonly ISerializerDataContractResolver _serializerDataContractResolver; + + public SchemaGenerator(SchemaGeneratorOptions generatorOptions, ISerializerDataContractResolver serializerDataContractResolver) + { + _generatorOptions = generatorOptions; + _serializerDataContractResolver = serializerDataContractResolver; + } + + public OpenApiSchema GenerateSchema( + Type modelType, + SchemaRepository schemaRepository, + MemberInfo memberInfo = null, + ParameterInfo parameterInfo = null, + ApiParameterRouteInfo routeInfo = null) + { + if (memberInfo != null) + return GenerateSchemaForMember(modelType, schemaRepository, memberInfo); + + if (parameterInfo != null) + return GenerateSchemaForParameter(modelType, schemaRepository, parameterInfo, routeInfo); + + return GenerateSchemaForType(modelType, schemaRepository); + } + + private OpenApiSchema GenerateSchemaForMember( + Type modelType, + SchemaRepository schemaRepository, + MemberInfo memberInfo, + DataProperty dataProperty = null) + { + var dataContract = GetDataContractFor(modelType); + + var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts) + ? GeneratePolymorphicSchema(dataContract, schemaRepository, knownTypesDataContracts) + : GenerateConcreteSchema(dataContract, schemaRepository); + + if (_generatorOptions.UseAllOfToExtendReferenceSchemas && schema.Reference != null) + { + schema.AllOf = new[] { new OpenApiSchema { Reference = schema.Reference } }; + schema.Reference = null; + } + + if (schema.Reference == null) + { + var customAttributes = memberInfo.GetInlineAndMetadataAttributes(); + + // Nullable, ReadOnly & WriteOnly are only relevant for Schema "properties" (i.e. where dataProperty is non-null) + if (dataProperty != null) + { + var requiredAttribute = customAttributes.OfType().FirstOrDefault(); + schema.Nullable = _generatorOptions.SupportNonNullableReferenceTypes + ? dataProperty.IsNullable && requiredAttribute==null && !memberInfo.IsNonNullableReferenceType() + : dataProperty.IsNullable && requiredAttribute==null; + + schema.ReadOnly = dataProperty.IsReadOnly; + schema.WriteOnly = dataProperty.IsWriteOnly; + schema.MinLength = modelType == typeof(string) && requiredAttribute is { AllowEmptyStrings: false } ? 1 : null; + } + + var defaultValueAttribute = customAttributes.OfType().FirstOrDefault(); + if (defaultValueAttribute != null) + { + var defaultAsJson = dataContract.JsonConverter(defaultValueAttribute.Value); + schema.Default = OpenApiAnyFactory.CreateFromJson(defaultAsJson); + } + + var obsoleteAttribute = customAttributes.OfType().FirstOrDefault(); + if (obsoleteAttribute != null) + { + schema.Deprecated = true; + } + + // NullableAttribute behaves diffrently for Dictionaries + if (schema.AdditionalPropertiesAllowed && modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + schema.AdditionalProperties.Nullable = !memberInfo.IsDictionaryValueNonNullable(); + } + + schema.ApplyValidationAttributes(customAttributes); + + ApplyFilters(schema, modelType, schemaRepository, memberInfo: memberInfo); + } + + return schema; + } + + private OpenApiSchema GenerateSchemaForParameter( + Type modelType, + SchemaRepository schemaRepository, + ParameterInfo parameterInfo, + ApiParameterRouteInfo routeInfo) + { + var dataContract = GetDataContractFor(modelType); + + var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts) + ? GeneratePolymorphicSchema(dataContract, schemaRepository, knownTypesDataContracts) + : GenerateConcreteSchema(dataContract, schemaRepository); + + if (_generatorOptions.UseAllOfToExtendReferenceSchemas && schema.Reference != null) + { + schema.AllOf = new[] { new OpenApiSchema { Reference = schema.Reference } }; + schema.Reference = null; + } + + if (schema.Reference == null) + { + var customAttributes = parameterInfo.GetCustomAttributes(); + + var defaultValue = parameterInfo.HasDefaultValue + ? parameterInfo.DefaultValue + : customAttributes.OfType().FirstOrDefault()?.Value; + + if (defaultValue != null) + { + var defaultAsJson = dataContract.JsonConverter(defaultValue); + schema.Default = OpenApiAnyFactory.CreateFromJson(defaultAsJson); + } + + schema.ApplyValidationAttributes(customAttributes); + if (routeInfo != null) + { + schema.ApplyRouteConstraints(routeInfo); + } + + ApplyFilters(schema, modelType, schemaRepository, parameterInfo: parameterInfo); + } + + return schema; + } + + private OpenApiSchema GenerateSchemaForType(Type modelType, SchemaRepository schemaRepository) + { + var dataContract = GetDataContractFor(modelType); + + var schema = _generatorOptions.UseOneOfForPolymorphism && IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts) + ? GeneratePolymorphicSchema(dataContract, schemaRepository, knownTypesDataContracts) + : GenerateConcreteSchema(dataContract, schemaRepository); + + if (schema.Reference == null) + { + ApplyFilters(schema, modelType, schemaRepository); + } + + return schema; + } + + private DataContract GetDataContractFor(Type modelType) + { + var effectiveType = Nullable.GetUnderlyingType(modelType) ?? modelType; + return _serializerDataContractResolver.GetDataContractForType(effectiveType); + } + + private bool IsBaseTypeWithKnownTypesDefined(DataContract dataContract, out IEnumerable knownTypesDataContracts) + { + knownTypesDataContracts = null; + + if (dataContract.DataType != DataType.Object) return false; + + var subTypes = _generatorOptions.SubTypesSelector(dataContract.UnderlyingType); + + if (!subTypes.Any()) return false; + + var knownTypes = !dataContract.UnderlyingType.IsAbstract + ? new[] { dataContract.UnderlyingType }.Union(subTypes) + : subTypes; + + knownTypesDataContracts = knownTypes.Select(knownType => GetDataContractFor(knownType)); + return true; + } + + private OpenApiSchema GeneratePolymorphicSchema( + DataContract dataContract, + SchemaRepository schemaRepository, + IEnumerable knownTypesDataContracts) + { + return new OpenApiSchema + { + OneOf = knownTypesDataContracts + .Select(allowedTypeDataContract => GenerateConcreteSchema(allowedTypeDataContract, schemaRepository)) + .ToList() + }; + } + + private OpenApiSchema GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository) + { + if (TryGetCustomTypeMapping(dataContract.UnderlyingType, out Func customSchemaFactory)) + { + return customSchemaFactory(); + } + + if (dataContract.UnderlyingType.IsAssignableToOneOf(typeof(IFormFile), typeof(FileResult))) + { + return new OpenApiSchema { Type = "string", Format = "binary" }; + } + + Func schemaFactory; + bool returnAsReference; + + switch (dataContract.DataType) + { + case DataType.Boolean: + case DataType.Integer: + case DataType.Number: + case DataType.String: + { + schemaFactory = () => CreatePrimitiveSchema(dataContract); + returnAsReference = dataContract.UnderlyingType.IsEnum && !_generatorOptions.UseInlineDefinitionsForEnums; + break; + } + + case DataType.Array: + { + schemaFactory = () => CreateArraySchema(dataContract, schemaRepository); + returnAsReference = dataContract.UnderlyingType == dataContract.ArrayItemType; + break; + } + + case DataType.Dictionary: + { + schemaFactory = () => CreateDictionarySchema(dataContract, schemaRepository); + returnAsReference = dataContract.UnderlyingType == dataContract.DictionaryValueType; + break; + } + + case DataType.Object: + { + schemaFactory = () => CreateObjectSchema(dataContract, schemaRepository); + returnAsReference = true; + break; + } + + default: + { + schemaFactory = () => new OpenApiSchema(); + returnAsReference = false; + break; + } + } + + return returnAsReference + ? GenerateReferencedSchema(dataContract, schemaRepository, schemaFactory) + : schemaFactory(); + } + + private bool TryGetCustomTypeMapping(Type modelType, out Func schemaFactory) + { + return _generatorOptions.CustomTypeMappings.TryGetValue(modelType, out schemaFactory) + || (modelType.IsConstructedGenericType && _generatorOptions.CustomTypeMappings.TryGetValue(modelType.GetGenericTypeDefinition(), out schemaFactory)); + } + + private OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) + { + var schema = new OpenApiSchema + { + Type = dataContract.DataType.ToString().ToLower(CultureInfo.InvariantCulture), + Format = dataContract.DataFormat + }; + + // For backcompat only - EnumValues is obsolete + if (dataContract.EnumValues != null) + { + schema.Enum = dataContract.EnumValues + .Select(value => JsonSerializer.Serialize(value)) + .Distinct() + .Select(valueAsJson => OpenApiAnyFactory.CreateFromJson(valueAsJson)) + .ToList(); + + return schema; + } + + if (dataContract.UnderlyingType.IsEnum) + { + schema.Enum = dataContract.UnderlyingType.GetEnumValues() + .Cast() + .Select(value => dataContract.JsonConverter(value)) + .Distinct() + .Select(valueAsJson => OpenApiAnyFactory.CreateFromJson(valueAsJson)) + .ToList(); + } + + return schema; + } + + private OpenApiSchema CreateArraySchema(DataContract dataContract, SchemaRepository schemaRepository) + { + var hasUniqueItems = dataContract.UnderlyingType.IsConstructedFrom(typeof(ISet<>), out _) + || dataContract.UnderlyingType.IsConstructedFrom(typeof(KeyedCollection<,>), out _); + + return new OpenApiSchema + { + Type = "array", + Items = GenerateSchema(dataContract.ArrayItemType, schemaRepository), + UniqueItems = hasUniqueItems ? (bool?)true : null + }; + } + + private OpenApiSchema CreateDictionarySchema(DataContract dataContract, SchemaRepository schemaRepository) + { + if (dataContract.DictionaryKeys != null) + { + // This is a special case where the set of key values is known (e.g. if the key type is an enum) + return new OpenApiSchema + { + Type = "object", + Properties = dataContract.DictionaryKeys.ToDictionary( + name => name, + name => GenerateSchema(dataContract.DictionaryValueType, schemaRepository)), + AdditionalPropertiesAllowed = false, + }; + } + else + { + return new OpenApiSchema + { + Type = "object", + AdditionalPropertiesAllowed = true, + AdditionalProperties = GenerateSchema(dataContract.DictionaryValueType, schemaRepository) + }; + } + } + + private OpenApiSchema CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository) + { + var schema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary(), + Required = new SortedSet(), + AdditionalPropertiesAllowed = false + }; + + // PATCH-START + OpenApiSchema root = schema; + // PATCH-END + + var applicableDataProperties = dataContract.ObjectProperties; + + if (_generatorOptions.UseAllOfForInheritance || _generatorOptions.UseOneOfForPolymorphism) + { + if (IsKnownSubType(dataContract, out var baseTypeDataContract)) + { + var baseTypeSchema = GenerateConcreteSchema(baseTypeDataContract, schemaRepository); + + // PATCH-START + root = new OpenApiSchema(); + root.AllOf.Add(baseTypeSchema); + //schema.AllOf.Add(baseTypeSchema); + // PATCH-END + + applicableDataProperties = applicableDataProperties + .Where(dataProperty => dataProperty.MemberInfo.DeclaringType == dataContract.UnderlyingType); + } + + if (IsBaseTypeWithKnownTypesDefined(dataContract, out var knownTypesDataContracts)) + { + foreach (var knownTypeDataContract in knownTypesDataContracts) + { + // Ensure schema is generated for all known types + GenerateConcreteSchema(knownTypeDataContract, schemaRepository); + } + + if (TryGetDiscriminatorFor(dataContract, schemaRepository, knownTypesDataContracts, out var discriminator)) + { + schema.Properties.Add(discriminator.PropertyName, new OpenApiSchema { Type = "string" }); + schema.Required.Add(discriminator.PropertyName); + schema.Discriminator = discriminator; + } + } + } + + foreach (var dataProperty in applicableDataProperties) + { + var customAttributes = dataProperty.MemberInfo?.GetInlineAndMetadataAttributes() ?? Enumerable.Empty(); + + if (_generatorOptions.IgnoreObsoleteProperties && customAttributes.OfType().Any()) + continue; + + schema.Properties[dataProperty.Name] = (dataProperty.MemberInfo != null) + ? GenerateSchemaForMember(dataProperty.MemberType, schemaRepository, dataProperty.MemberInfo, dataProperty) + : GenerateSchemaForType(dataProperty.MemberType, schemaRepository); + + if ((dataProperty.IsRequired || customAttributes.OfType().Any()) + && !schema.Required.Contains(dataProperty.Name)) + { + schema.Required.Add(dataProperty.Name); + } + } + + if (dataContract.ObjectExtensionDataType != null) + { + schema.AdditionalPropertiesAllowed = true; + schema.AdditionalProperties = GenerateSchema(dataContract.ObjectExtensionDataType, schemaRepository); + } + + // PATCH-START + if (root != schema) + { + root.AllOf.Add(schema); + } + + return root; + //return schema; + // PATCH-END + } + + private bool IsKnownSubType(DataContract dataContract, out DataContract baseTypeDataContract) + { + baseTypeDataContract = null; + + var baseType = dataContract.UnderlyingType.BaseType; + + if (baseType == null || baseType == typeof(object) || !_generatorOptions.SubTypesSelector(baseType).Contains(dataContract.UnderlyingType)) + return false; + + baseTypeDataContract = GetDataContractFor(baseType); + return true; + } + + private bool TryGetDiscriminatorFor( + DataContract dataContract, + SchemaRepository schemaRepository, + IEnumerable knownTypesDataContracts, + out OpenApiDiscriminator discriminator) + { + discriminator = null; + + var discriminatorName = _generatorOptions.DiscriminatorNameSelector(dataContract.UnderlyingType) + ?? dataContract.ObjectTypeNameProperty; + + if (discriminatorName == null) return false; + + discriminator = new OpenApiDiscriminator + { + PropertyName = discriminatorName + }; + + foreach (var knownTypeDataContract in knownTypesDataContracts) + { + var discriminatorValue = _generatorOptions.DiscriminatorValueSelector(knownTypeDataContract.UnderlyingType) + ?? knownTypeDataContract.ObjectTypeNameValue; + + if (discriminatorValue == null) continue; + + discriminator.Mapping.Add(discriminatorValue, GenerateConcreteSchema(knownTypeDataContract, schemaRepository).Reference.ReferenceV3); + } + + return true; + } + + private OpenApiSchema GenerateReferencedSchema( + DataContract dataContract, + SchemaRepository schemaRepository, + Func definitionFactory) + { + if (schemaRepository.TryLookupByType(dataContract.UnderlyingType, out OpenApiSchema referenceSchema)) + return referenceSchema; + + var schemaId = _generatorOptions.SchemaIdSelector(dataContract.UnderlyingType); + + schemaRepository.RegisterType(dataContract.UnderlyingType, schemaId); + + var schema = definitionFactory(); + ApplyFilters(schema, dataContract.UnderlyingType, schemaRepository); + + return schemaRepository.AddDefinition(schemaId, schema); + } + + private void ApplyFilters( + OpenApiSchema schema, + Type type, + SchemaRepository schemaRepository, + MemberInfo memberInfo = null, + ParameterInfo parameterInfo = null) + { + var filterContext = new SchemaFilterContext( + type: type, + schemaGenerator: this, + schemaRepository: schemaRepository, + memberInfo: memberInfo, + parameterInfo: parameterInfo); + + foreach (var filter in _generatorOptions.SchemaFilters) + { + filter.Apply(schema, filterContext); + } + } + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs index 7f54d776f4..3ccb82d622 100644 --- a/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs @@ -1,14 +1,13 @@ -using System.Reflection; -using System.Text.Json; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.OpenApi.SwaggerComponents; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Swashbuckle.AspNetCore.Swagger; +using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.SwaggerGen; +using SchemaGenerator = Swashbuckle.AspNetCore.SwaggerGen.Patched.SchemaGenerator; namespace JsonApiDotNetCore.OpenApi; @@ -23,14 +22,15 @@ public static void AddOpenApi(this IServiceCollection services, IMvcCoreBuilder ArgumentGuard.NotNull(mvcBuilder); AddCustomApiExplorer(services, mvcBuilder); - AddCustomSwaggerComponents(services); + AddSwaggerGenerator(services); + + services.AddTransient, ConfigureMvcOptions>(); - using ServiceProvider provider = services.BuildServiceProvider(); - using IServiceScope scope = provider.CreateScope(); - AddSwaggerGenerator(scope, services, setupSwaggerGenAction); - AddSwashbuckleCliCompatibility(scope, mvcBuilder); - AddOpenApiEndpointConvention(scope, mvcBuilder); + if (setupSwaggerGenAction != null) + { + services.Configure(setupSwaggerGenAction); + } } private static void AddCustomApiExplorer(IServiceCollection services, IMvcCoreBuilder mvcBuilder) @@ -44,10 +44,10 @@ private static void AddCustomApiExplorer(IServiceCollection services, IMvcCoreBu var apiDescriptionProviders = provider.GetRequiredService>(); var resourceFieldValidationMetadataProvider = provider.GetRequiredService(); - JsonApiActionDescriptorCollectionProvider descriptorCollectionProviderWrapper = + JsonApiActionDescriptorCollectionProvider jsonApiActionDescriptorCollectionProvider = new(controllerResourceMapping, actionDescriptorCollectionProvider, resourceFieldValidationMetadataProvider); - return new ApiDescriptionGroupCollectionProvider(descriptorCollectionProviderWrapper, apiDescriptionProviders); + return new ApiDescriptionGroupCollectionProvider(jsonApiActionDescriptorCollectionProvider, apiDescriptionProviders); }); mvcBuilder.AddApiExplorer(); @@ -55,85 +55,32 @@ private static void AddCustomApiExplorer(IServiceCollection services, IMvcCoreBu mvcBuilder.AddMvcOptions(options => options.InputFormatters.Add(new JsonApiRequestFormatMetadataProvider())); } - private static void AddSwaggerGenerator(IServiceScope scope, IServiceCollection services, Action? setupSwaggerGenAction) - { - var controllerResourceMapping = scope.ServiceProvider.GetRequiredService(); - var resourceGraph = scope.ServiceProvider.GetRequiredService(); - var jsonApiOptions = scope.ServiceProvider.GetRequiredService(); - JsonNamingPolicy? namingPolicy = jsonApiOptions.SerializerOptions.PropertyNamingPolicy; - - AddSchemaGenerator(services); - - services.AddSwaggerGen(swaggerGenOptions => - { - swaggerGenOptions.SupportNonNullableReferenceTypes(); - SetOperationInfo(swaggerGenOptions, controllerResourceMapping, namingPolicy); - SetSchemaIdSelector(swaggerGenOptions, resourceGraph, namingPolicy); - swaggerGenOptions.DocumentFilter(); - swaggerGenOptions.UseAllOfToExtendReferenceSchemas(); - swaggerGenOptions.OperationFilter(); - - setupSwaggerGenAction?.Invoke(swaggerGenOptions); - }); - } - - private static void AddSchemaGenerator(IServiceCollection services) - { - services.TryAddSingleton(); - services.TryAddSingleton(); - } - - private static void SetOperationInfo(SwaggerGenOptions swaggerGenOptions, IControllerResourceMapping controllerResourceMapping, - JsonNamingPolicy? namingPolicy) - { - swaggerGenOptions.TagActionsBy(description => GetOperationTags(description, controllerResourceMapping)); - - JsonApiOperationIdSelector jsonApiOperationIdSelector = new(controllerResourceMapping, namingPolicy); - swaggerGenOptions.CustomOperationIds(jsonApiOperationIdSelector.GetOperationId); - } - - private static IList GetOperationTags(ApiDescription description, IControllerResourceMapping controllerResourceMapping) - { - MethodInfo actionMethod = description.ActionDescriptor.GetActionMethod(); - ResourceType? resourceType = controllerResourceMapping.GetResourceTypeForController(actionMethod.ReflectedType); - - if (resourceType == null) - { - throw new NotSupportedException("Only JsonApiDotNetCore endpoints are supported."); - } - - return new[] - { - resourceType.PublicName - }; - } - - private static void SetSchemaIdSelector(SwaggerGenOptions swaggerGenOptions, IResourceGraph resourceGraph, JsonNamingPolicy? namingPolicy) - { - JsonApiSchemaIdSelector jsonApiObjectSchemaSelector = new(namingPolicy, resourceGraph); - - swaggerGenOptions.CustomSchemaIds(type => jsonApiObjectSchemaSelector.GetSchemaId(type)); - } - private static void AddCustomSwaggerComponents(IServiceCollection services) { - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); } - private static void AddSwashbuckleCliCompatibility(IServiceScope scope, IMvcCoreBuilder mvcBuilder) + private static void AddSwaggerGenerator(IServiceCollection services) { - // See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1957 for why this is needed. - var routingConvention = scope.ServiceProvider.GetRequiredService(); - mvcBuilder.AddMvcOptions(options => options.Conventions.Insert(0, routingConvention)); + AddSchemaGenerators(services); + + services.AddSwaggerGen(); + services.AddSingleton, ConfigureSwaggerGenOptions>(); } - private static void AddOpenApiEndpointConvention(IServiceScope scope, IMvcCoreBuilder mvcBuilder) + private static void AddSchemaGenerators(IServiceCollection services) { - var controllerResourceMapping = scope.ServiceProvider.GetRequiredService(); + services.TryAddSingleton(); + services.TryAddSingleton(); - mvcBuilder.AddMvcOptions(options => options.Conventions.Add(new OpenApiEndpointConvention(controllerResourceMapping))); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/AbstractResourceDataSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/AbstractResourceDataSchemaGenerator.cs new file mode 100644 index 0000000000..09a2e67442 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/AbstractResourceDataSchemaGenerator.cs @@ -0,0 +1,104 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; + +internal sealed class AbstractResourceDataSchemaGenerator +{ + private static readonly Type ResourceDataAbstractType = typeof(ResourceData); + + private readonly JsonApiSchemaIdSelector _schemaIdSelector; + private readonly IResourceGraph _resourceGraph; + + public AbstractResourceDataSchemaGenerator(JsonApiSchemaIdSelector schemaIdSelector, IResourceGraph resourceGraph) + { + ArgumentGuard.NotNull(schemaIdSelector); + ArgumentGuard.NotNull(resourceGraph); + + _schemaIdSelector = schemaIdSelector; + _resourceGraph = resourceGraph; + } + + public OpenApiSchema Get(SchemaRepository schemaRepository) + { + ArgumentGuard.NotNull(schemaRepository); + + if (schemaRepository.TryLookupByType(ResourceDataAbstractType, out OpenApiSchema? referenceSchema)) + { + return referenceSchema; + } + + var fullSchema = new OpenApiSchema + { + Required = new HashSet + { + "id", + "type" + }, + Type = "object", + Properties = new Dictionary + { + ["type"] = new() + { + MinLength = 1, + Type = "string" + }, + ["id"] = new() + { + MinLength = 1, + Type = "string" + } + }, + AdditionalPropertiesAllowed = false, + Discriminator = new OpenApiDiscriminator + { + PropertyName = "type", + Mapping = new SortedDictionary(StringComparer.Ordinal) + }, + Extensions = new Dictionary + { + ["x-abstract"] = new OpenApiBoolean(true) + } + }; + + string schemaId = _schemaIdSelector.GetSchemaId(ResourceDataAbstractType); + + referenceSchema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = schemaId, + Type = ReferenceType.Schema + } + }; + + schemaRepository.AddDefinition(schemaId, fullSchema); + schemaRepository.RegisterType(ResourceDataAbstractType, schemaId); + + return referenceSchema; + } + + public void MapDiscriminator(Type resourceDataConstructedType, OpenApiSchema referenceSchemaForResourceData, SchemaRepository schemaRepository) + { + ArgumentGuard.NotNull(resourceDataConstructedType); + ArgumentGuard.NotNull(referenceSchemaForResourceData); + ArgumentGuard.NotNull(schemaRepository); + + var resourceTypeInfo = ResourceTypeInfo.Create(resourceDataConstructedType, _resourceGraph); + + if (resourceTypeInfo.ResourceDataOpenType == typeof(ResourceDataInResponse<>)) + { + if (schemaRepository.TryLookupByType(ResourceDataAbstractType, out OpenApiSchema? referenceSchemaForAbstractResourceData)) + { + OpenApiSchema fullSchemaForAbstractResourceData = schemaRepository.Schemas[referenceSchemaForAbstractResourceData.Reference.Id]; + + fullSchemaForAbstractResourceData.Discriminator.Mapping[resourceTypeInfo.ResourceType.PublicName] = + referenceSchemaForResourceData.Reference.ReferenceV3; + } + } + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs deleted file mode 100644 index 232cc4f262..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/CachingSwaggerGenerator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Concurrent; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.Swagger; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; - -/// -/// The default implementation re-renders the OpenApiDocument every time it is requested, which is redundant in our case. -/// This implementation provides a very basic caching layer. -/// -internal sealed class CachingSwaggerGenerator : ISwaggerProvider -{ - private readonly SwaggerGenerator _defaultSwaggerGenerator; - private readonly ConcurrentDictionary _openApiDocumentCache = new(); - - public CachingSwaggerGenerator(SwaggerGenerator defaultSwaggerGenerator) - { - ArgumentGuard.NotNull(defaultSwaggerGenerator); - - _defaultSwaggerGenerator = defaultSwaggerGenerator; - } - - public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null) - { - ArgumentGuard.NotNullNorEmpty(documentName); - - string cacheKey = $"{documentName}#{host}#{basePath}"; - - return _openApiDocumentCache.GetOrAdd(cacheKey, _ => _defaultSwaggerGenerator.GetSwagger(documentName, host, basePath)); - } -} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/DocumentSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/DocumentSchemaGenerator.cs new file mode 100644 index 0000000000..dc3c9e463e --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/DocumentSchemaGenerator.cs @@ -0,0 +1,183 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.OpenApi.JsonApiObjects; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using SchemaGenerator = Swashbuckle.AspNetCore.SwaggerGen.Patched.SchemaGenerator; + +namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; + +internal sealed class DocumentSchemaGenerator +{ + private static readonly Type[] JsonApiDocumentWithNullableDataOpenTypes = + [ + typeof(NullableSecondaryResourceResponseDocument<>), + typeof(NullableResourceIdentifierResponseDocument<>), + typeof(NullableToOneRelationshipInRequest<>) + ]; + + private static readonly string[] DocumentPropertyNamesInOrder = + [ + JsonApiPropertyName.Jsonapi, + JsonApiPropertyName.Links, + JsonApiPropertyName.Data, + JsonApiPropertyName.Errors, + JsonApiPropertyName.Included, + JsonApiPropertyName.Meta + ]; + + private readonly SchemaGenerator _defaultSchemaGenerator; + private readonly AbstractResourceDataSchemaGenerator _abstractResourceDataSchemaGenerator; + private readonly ResourceDataSchemaGenerator _resourceDataSchemaGenerator; + private readonly IncludeDependencyScanner _includeDependencyScanner; + private readonly IResourceGraph _resourceGraph; + private readonly IJsonApiOptions _options; + + public DocumentSchemaGenerator(SchemaGenerator defaultSchemaGenerator, AbstractResourceDataSchemaGenerator abstractResourceDataSchemaGenerator, + ResourceDataSchemaGenerator resourceDataSchemaGenerator, IncludeDependencyScanner includeDependencyScanner, IResourceGraph resourceGraph, + IJsonApiOptions options) + { + ArgumentGuard.NotNull(defaultSchemaGenerator); + ArgumentGuard.NotNull(abstractResourceDataSchemaGenerator); + ArgumentGuard.NotNull(resourceDataSchemaGenerator); + ArgumentGuard.NotNull(includeDependencyScanner); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(options); + + _defaultSchemaGenerator = defaultSchemaGenerator; + _abstractResourceDataSchemaGenerator = abstractResourceDataSchemaGenerator; + _resourceDataSchemaGenerator = resourceDataSchemaGenerator; + _includeDependencyScanner = includeDependencyScanner; + _resourceGraph = resourceGraph; + _options = options; + } + + public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepository) + { + ArgumentGuard.NotNull(modelType); + ArgumentGuard.NotNull(schemaRepository); + + OpenApiSchema referenceSchemaForDocument = GenerateJsonApiDocumentSchema(modelType, schemaRepository); + OpenApiSchema fullSchemaForDocument = schemaRepository.Schemas[referenceSchemaForDocument.Reference.Id]; + + if (IsDataPropertyNullableInDocument(modelType)) + { + SetDataSchemaToNullable(fullSchemaForDocument); + } + + fullSchemaForDocument.SetValuesInMetaToNullable(); + + SetJsonApiVersion(fullSchemaForDocument, schemaRepository); + + return referenceSchemaForDocument; + } + + private OpenApiSchema GenerateJsonApiDocumentSchema(Type documentType, SchemaRepository schemaRepository) + { + // There's no way to intercept in the Swashbuckle recursive component schema generation when using inheritance, which we need + // to perform generic type expansions. As a workaround, we generate an empty base schema upfront. Each time the schema + // for a derived type is generated, we'll add it to the discriminator mapping. + _ = _abstractResourceDataSchemaGenerator.Get(schemaRepository); + + Type resourceDataConstructedType = documentType.BaseType!.GenericTypeArguments[0]; + + // Ensure all reachable related resource types are available in the discriminator mapping so includes work. + // Doing this matters when not all endpoints are exposed. + EnsureResourceTypesAreMappedInDiscriminator(resourceDataConstructedType, schemaRepository); + + OpenApiSchema referenceSchemaForResourceData = _resourceDataSchemaGenerator.GenerateSchema(resourceDataConstructedType, schemaRepository); + _abstractResourceDataSchemaGenerator.MapDiscriminator(resourceDataConstructedType, referenceSchemaForResourceData, schemaRepository); + + OpenApiSchema referenceSchemaForDocument = _defaultSchemaGenerator.GenerateSchema(documentType, schemaRepository); + OpenApiSchema fullSchemaForDocument = schemaRepository.Schemas[referenceSchemaForDocument.Reference.Id]; + + fullSchemaForDocument.Properties[JsonApiPropertyName.Data] = IsManyDataDocument(documentType) + ? CreateArrayTypeDataSchema(referenceSchemaForResourceData) + : CreateExtendedReferenceSchema(referenceSchemaForResourceData); + + fullSchemaForDocument.ReorderProperties(DocumentPropertyNamesInOrder); + + return referenceSchemaForDocument; + } + + private void EnsureResourceTypesAreMappedInDiscriminator(Type resourceDataConstructedType, SchemaRepository schemaRepository) + { + Type resourceDataOpenType = resourceDataConstructedType.GetGenericTypeDefinition(); + + if (resourceDataOpenType == typeof(ResourceDataInResponse<>)) + { + Type resourceClrType = resourceDataConstructedType.GetGenericArguments()[0]; + ResourceType resourceType = _resourceGraph.GetResourceType(resourceClrType); + + foreach (ResourceType nextResourceType in _includeDependencyScanner.GetReachableRelatedTypes(resourceType)) + { + Type nextResourceDataConstructedType = typeof(ResourceDataInResponse<>).MakeGenericType(nextResourceType.ClrType); + + OpenApiSchema nextReferenceSchemaForResourceData = + _resourceDataSchemaGenerator.GenerateSchema(nextResourceDataConstructedType, schemaRepository); + + _abstractResourceDataSchemaGenerator.MapDiscriminator(nextResourceDataConstructedType, nextReferenceSchemaForResourceData, schemaRepository); + } + } + } + + private static bool IsManyDataDocument(Type documentType) + { + return documentType.BaseType!.GetGenericTypeDefinition() == typeof(ManyData<>); + } + + private static bool IsDataPropertyNullableInDocument(Type documentType) + { + Type documentOpenType = documentType.GetGenericTypeDefinition(); + + return JsonApiDocumentWithNullableDataOpenTypes.Contains(documentOpenType); + } + + private static OpenApiSchema CreateArrayTypeDataSchema(OpenApiSchema referenceSchemaForResourceData) + { + return new OpenApiSchema + { + Items = referenceSchemaForResourceData, + Type = "array" + }; + } + + private static void SetDataSchemaToNullable(OpenApiSchema fullSchemaForDocument) + { + OpenApiSchema referenceSchemaForData = fullSchemaForDocument.Properties[JsonApiPropertyName.Data]; + referenceSchemaForData.Nullable = true; + fullSchemaForDocument.Properties[JsonApiPropertyName.Data] = referenceSchemaForData; + } + + private void SetJsonApiVersion(OpenApiSchema fullSchemaForDocument, SchemaRepository schemaRepository) + { + if (fullSchemaForDocument.Properties.TryGetValue(JsonApiPropertyName.Jsonapi, out OpenApiSchema? referenceSchemaForJsonapi)) + { + string jsonapiSchemaId = referenceSchemaForJsonapi.AllOf[0].Reference.Id; + + if (!_options.IncludeJsonApiVersion) + { + fullSchemaForDocument.Properties.Remove(JsonApiPropertyName.Jsonapi); + schemaRepository.Schemas.Remove(jsonapiSchemaId); + } + else + { + OpenApiSchema fullSchemaForJsonapi = schemaRepository.Schemas[jsonapiSchemaId]; + fullSchemaForJsonapi.SetValuesInMetaToNullable(); + } + } + } + + private static OpenApiSchema CreateExtendedReferenceSchema(OpenApiSchema referenceSchema) + { + return new OpenApiSchema + { + AllOf = new List + { + referenceSchema + } + }; + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ISchemaRepositoryAccessor.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ISchemaRepositoryAccessor.cs deleted file mode 100644 index 5c627bbcf1..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ISchemaRepositoryAccessor.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; - -internal interface ISchemaRepositoryAccessor -{ - SchemaRepository Current { get; } -} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiOperationDocumentationFilter.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiOperationDocumentationFilter.cs index 6421b56c07..34bf14a222 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiOperationDocumentationFilter.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiOperationDocumentationFilter.cs @@ -38,12 +38,17 @@ internal sealed class JsonApiOperationDocumentationFilter : IOperationFilter private const string TextRequestBodyValidationFailed = "Validation of the request body failed."; private const string TextRequestBodyClientId = "Client-generated IDs cannot be used at this endpoint."; - private const string TextQueryStringParameters = + private const string ResourceQueryStringParameters = "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/" + "[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/" + "[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/" + "[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters."; + private const string RelationshipQueryStringParameters = "For syntax, see the documentation for the " + + "[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/" + + "[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/" + + "[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters."; + private readonly IJsonApiOptions _options; private readonly IControllerResourceMapping _controllerResourceMapping; private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; @@ -166,7 +171,7 @@ private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType res $"Successfully returns the found {resourceType}, or an empty array if none were found."); } - AddQueryStringParameters(operation); + AddQueryStringParameters(operation, false); SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad); } else if (operation.Parameters.Count == 1) @@ -186,7 +191,7 @@ private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType res } SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularName} to retrieve."); - AddQueryStringParameters(operation); + AddQueryStringParameters(operation, false); SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad); SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularName} does not exist."); } @@ -197,7 +202,7 @@ private void ApplyPostResource(OpenApiOperation operation, ResourceType resource string singularName = resourceType.PublicName.Singularize(); SetOperationSummary(operation, $"Creates a new {singularName}."); - AddQueryStringParameters(operation); + AddQueryStringParameters(operation, false); SetRequestBodyDescription(operation.RequestBody, $"The attributes and relationships of the {singularName} to create."); SetResponseDescription(operation.Responses, HttpStatusCode.Created, @@ -225,7 +230,7 @@ private void ApplyPatchResource(OpenApiOperation operation, ResourceType resourc SetOperationSummary(operation, $"Updates an existing {singularName}."); SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularName} to update."); - AddQueryStringParameters(operation); + AddQueryStringParameters(operation, false); SetRequestBodyDescription(operation.RequestBody, $"The attributes and relationships of the {singularName} to update. Omitted fields are left unchanged."); @@ -278,7 +283,7 @@ relationship is HasOneAttribute } SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} whose related {rightName} to retrieve."); - AddQueryStringParameters(operation); + AddQueryStringParameters(operation, false); SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad); SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularLeftName} does not exist."); } @@ -311,7 +316,7 @@ relationship is HasOneAttribute } SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} whose related {singularRightName} {ident} to retrieve."); - AddQueryStringParameters(operation); + AddQueryStringParameters(operation, true); SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad); SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularLeftName} does not exist."); } @@ -427,7 +432,7 @@ private static void SetResponseDescription(OpenApiResponses responses, HttpStatu response.Description = XmlCommentsTextHelper.Humanize(description); } - private static void AddQueryStringParameters(OpenApiOperation operation) + private static void AddQueryStringParameters(OpenApiOperation operation, bool isRelationshipEndpoint) { // The JSON:API query string parameters (include, filter, sort, page[size], page[number], fields[]) are too dynamic to represent in OpenAPI. // - The parameter names for fields[] require exploding to all resource types, because outcome of possible resource types depends on @@ -452,9 +457,9 @@ private static void AddQueryStringParameters(OpenApiOperation operation) Nullable = true }, // Prevent SwaggerUI from producing sample, which fails when used because unknown query string parameters are blocked by default. - Example = new OpenApiNull() + Example = new OpenApiString(string.Empty) }, - Description = TextQueryStringParameters + Description = isRelationshipEndpoint ? RelationshipQueryStringParameters : ResourceQueryStringParameters }); } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs index 4715762f8f..9811fa783f 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs @@ -1,12 +1,10 @@ using System.Reflection; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.OpenApi.JsonApiObjects; using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents; -using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; +using SchemaGenerator = Swashbuckle.AspNetCore.SwaggerGen.Patched.SchemaGenerator; namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; @@ -25,46 +23,21 @@ internal sealed class JsonApiSchemaGenerator : ISchemaGenerator typeof(NullableResourceIdentifierResponseDocument<>) ]; - private static readonly Type[] JsonApiDocumentWithNullableDataOpenTypes = - [ - typeof(NullableSecondaryResourceResponseDocument<>), - typeof(NullableResourceIdentifierResponseDocument<>), - typeof(NullableToOneRelationshipInRequest<>) - ]; - - private static readonly string[] DocumentPropertyNamesInOrder = - [ - JsonApiPropertyName.Jsonapi, - JsonApiPropertyName.Links, - JsonApiPropertyName.Data, - JsonApiPropertyName.Errors, - JsonApiPropertyName.Included, - JsonApiPropertyName.Meta - ]; - private static readonly OpenApiSchema IdTypeSchema = new() { Type = "string" }; private readonly ISchemaGenerator _defaultSchemaGenerator; - private readonly IJsonApiOptions _options; - private readonly ResourceObjectSchemaGenerator _resourceObjectSchemaGenerator; - private readonly SchemaRepositoryAccessor _schemaRepositoryAccessor = new(); + private readonly DocumentSchemaGenerator _documentSchemaGenerator; - public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, - ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) + public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, DocumentSchemaGenerator documentSchemaGenerator) { ArgumentGuard.NotNull(defaultSchemaGenerator); - ArgumentGuard.NotNull(resourceGraph); - ArgumentGuard.NotNull(options); - ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); + ArgumentGuard.NotNull(documentSchemaGenerator); _defaultSchemaGenerator = defaultSchemaGenerator; - _options = options; - - _resourceObjectSchemaGenerator = new ResourceObjectSchemaGenerator(defaultSchemaGenerator, resourceGraph, options, _schemaRepositoryAccessor, - resourceFieldValidationMetadataProvider); + _documentSchemaGenerator = documentSchemaGenerator; } public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo? memberInfo = null, ParameterInfo? parameterInfo = null, @@ -73,8 +46,6 @@ public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepos ArgumentGuard.NotNull(modelType); ArgumentGuard.NotNull(schemaRepository); - _schemaRepositoryAccessor.Current = schemaRepository; - if (parameterInfo is { Name: "id" } && IsJsonApiParameter(parameterInfo)) { return IdTypeSchema; @@ -91,17 +62,7 @@ public OpenApiSchema GenerateSchema(Type modelType, SchemaRepository schemaRepos if (IsJsonApiDocument(modelType)) { - OpenApiSchema referenceSchemaForDocument = GenerateJsonApiDocumentSchema(modelType); - OpenApiSchema fullSchemaForDocument = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForDocument.Reference.Id]; - - if (IsDataPropertyNullableInDocument(modelType)) - { - SetDataObjectSchemaToNullable(fullSchemaForDocument); - } - - fullSchemaForDocument.SetValuesInMetaToNullable(); - - SetJsonApiVersion(fullSchemaForDocument); + _ = _documentSchemaGenerator.GenerateSchema(modelType, schemaRepository); // Schema might depend on other schemas not handled by us, so should not return here. } @@ -118,85 +79,4 @@ private static bool IsJsonApiDocument(Type type) { return type.IsConstructedGenericType && JsonApiDocumentOpenTypes.Contains(type.GetGenericTypeDefinition()); } - - private OpenApiSchema GenerateJsonApiDocumentSchema(Type documentType) - { - Type resourceObjectType = documentType.BaseType!.GenericTypeArguments[0]; - - if (!_schemaRepositoryAccessor.Current.TryLookupByType(resourceObjectType, out OpenApiSchema referenceSchemaForResourceObject)) - { - referenceSchemaForResourceObject = _resourceObjectSchemaGenerator.GenerateSchema(resourceObjectType); - } - - OpenApiSchema referenceSchemaForDocument = _defaultSchemaGenerator.GenerateSchema(documentType, _schemaRepositoryAccessor.Current); - OpenApiSchema fullSchemaForDocument = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForDocument.Reference.Id]; - - OpenApiSchema referenceSchemaForDataObject = IsManyDataDocument(documentType) - ? CreateArrayTypeDataSchema(referenceSchemaForResourceObject) - : CreateExtendedReferenceSchema(referenceSchemaForResourceObject); - - fullSchemaForDocument.Properties[JsonApiPropertyName.Data] = referenceSchemaForDataObject; - - fullSchemaForDocument.ReorderProperties(DocumentPropertyNamesInOrder); - - return referenceSchemaForDocument; - } - - private static bool IsManyDataDocument(Type documentType) - { - return documentType.BaseType!.GetGenericTypeDefinition() == typeof(ManyData<>); - } - - private static bool IsDataPropertyNullableInDocument(Type documentType) - { - Type documentOpenType = documentType.GetGenericTypeDefinition(); - - return JsonApiDocumentWithNullableDataOpenTypes.Contains(documentOpenType); - } - - private static OpenApiSchema CreateArrayTypeDataSchema(OpenApiSchema referenceSchemaForResourceObject) - { - return new OpenApiSchema - { - Items = referenceSchemaForResourceObject, - Type = "array" - }; - } - - private void SetDataObjectSchemaToNullable(OpenApiSchema fullSchemaForDocument) - { - OpenApiSchema referenceSchemaForData = fullSchemaForDocument.Properties[JsonApiPropertyName.Data]; - referenceSchemaForData.Nullable = true; - fullSchemaForDocument.Properties[JsonApiPropertyName.Data] = referenceSchemaForData; - } - - private void SetJsonApiVersion(OpenApiSchema fullSchemaForDocument) - { - if (fullSchemaForDocument.Properties.TryGetValue(JsonApiPropertyName.Jsonapi, out OpenApiSchema? referenceSchemaForJsonapi)) - { - string jsonapiSchemaId = referenceSchemaForJsonapi.AllOf[0].Reference.Id; - - if (!_options.IncludeJsonApiVersion) - { - fullSchemaForDocument.Properties.Remove(JsonApiPropertyName.Jsonapi); - _schemaRepositoryAccessor.Current.Schemas.Remove(jsonapiSchemaId); - } - else - { - OpenApiSchema fullSchemaForJsonapi = _schemaRepositoryAccessor.Current.Schemas[jsonapiSchemaId]; - fullSchemaForJsonapi.SetValuesInMetaToNullable(); - } - } - } - - private static OpenApiSchema CreateExtendedReferenceSchema(OpenApiSchema referenceSchemaForResourceObject) - { - return new OpenApiSchema - { - AllOf = new List - { - referenceSchemaForResourceObject - } - }; - } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceDataSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceDataSchemaGenerator.cs new file mode 100644 index 0000000000..10c13bcfca --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceDataSchemaGenerator.cs @@ -0,0 +1,149 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using SchemaGenerator = Swashbuckle.AspNetCore.SwaggerGen.Patched.SchemaGenerator; + +namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; + +internal sealed class ResourceDataSchemaGenerator +{ + private static readonly string[] ResourceDataPropertyNamesInOrder = + [ + JsonApiPropertyName.Type, + JsonApiPropertyName.Id, + JsonApiPropertyName.Attributes, + JsonApiPropertyName.Relationships, + JsonApiPropertyName.Links, + JsonApiPropertyName.Meta + ]; + + private readonly SchemaGenerator _defaultSchemaGenerator; + private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; + private readonly ResourceIdentifierSchemaGenerator _resourceIdentifierSchemaGenerator; + private readonly IResourceGraph _resourceGraph; + private readonly IJsonApiOptions _options; + private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; + private readonly ResourceDocumentationReader _resourceDocumentationReader; + + public ResourceDataSchemaGenerator(SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, + ResourceIdentifierSchemaGenerator resourceIdentifierSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, + ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider, ResourceDocumentationReader resourceDocumentationReader) + { + ArgumentGuard.NotNull(defaultSchemaGenerator); + ArgumentGuard.NotNull(resourceTypeSchemaGenerator); + ArgumentGuard.NotNull(resourceIdentifierSchemaGenerator); + ArgumentGuard.NotNull(resourceGraph); + ArgumentGuard.NotNull(options); + ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); + ArgumentGuard.NotNull(resourceDocumentationReader); + + _defaultSchemaGenerator = defaultSchemaGenerator; + _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator; + _resourceIdentifierSchemaGenerator = resourceIdentifierSchemaGenerator; + _resourceGraph = resourceGraph; + _options = options; + _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider; + + _resourceDocumentationReader = resourceDocumentationReader; + } + + public OpenApiSchema GenerateSchema(Type resourceDataConstructedType, SchemaRepository schemaRepository) + { + ArgumentGuard.NotNull(resourceDataConstructedType); + ArgumentGuard.NotNull(schemaRepository); + + if (schemaRepository.TryLookupByType(resourceDataConstructedType, out OpenApiSchema referenceSchemaForResourceData)) + { + return referenceSchemaForResourceData; + } + + referenceSchemaForResourceData = _defaultSchemaGenerator.GenerateSchema(resourceDataConstructedType, schemaRepository); + OpenApiSchema fullSchemaForResourceData = schemaRepository.Schemas[referenceSchemaForResourceData.Reference.Id]; + fullSchemaForResourceData.AdditionalPropertiesAllowed = false; + + var resourceTypeInfo = ResourceTypeInfo.Create(resourceDataConstructedType, _resourceGraph); + + var fieldSchemaBuilder = new ResourceFieldSchemaBuilder(_defaultSchemaGenerator, _resourceIdentifierSchemaGenerator, + _resourceFieldValidationMetadataProvider, resourceTypeInfo); + + OpenApiSchema effectiveFullSchemaForResourceData = + fullSchemaForResourceData.AllOf.Count == 0 ? fullSchemaForResourceData : fullSchemaForResourceData.AllOf[1]; + + if (effectiveFullSchemaForResourceData == fullSchemaForResourceData) + { + RemoveResourceIdIfPostResource(resourceTypeInfo, fullSchemaForResourceData); + SetResourceType(fullSchemaForResourceData, resourceTypeInfo.ResourceType, schemaRepository); + } + + fullSchemaForResourceData.Description = _resourceDocumentationReader.GetDocumentationForType(resourceTypeInfo.ResourceType); + + effectiveFullSchemaForResourceData.SetValuesInMetaToNullable(); + + SetResourceAttributes(effectiveFullSchemaForResourceData, fieldSchemaBuilder, schemaRepository); + SetResourceRelationships(effectiveFullSchemaForResourceData, fieldSchemaBuilder, schemaRepository); + + effectiveFullSchemaForResourceData.ReorderProperties(ResourceDataPropertyNamesInOrder); + + return referenceSchemaForResourceData; + } + + private void RemoveResourceIdIfPostResource(ResourceTypeInfo resourceTypeInfo, OpenApiSchema fullSchemaForResourceData) + { + if (resourceTypeInfo.ResourceDataOpenType == typeof(ResourceDataInPostRequest<>)) + { + ClientIdGenerationMode clientIdGeneration = resourceTypeInfo.ResourceType.ClientIdGeneration ?? _options.ClientIdGeneration; + + if (clientIdGeneration == ClientIdGenerationMode.Forbidden) + { + fullSchemaForResourceData.Required.Remove(JsonApiPropertyName.Id); + fullSchemaForResourceData.Properties.Remove(JsonApiPropertyName.Id); + } + else if (clientIdGeneration == ClientIdGenerationMode.Allowed) + { + fullSchemaForResourceData.Required.Remove(JsonApiPropertyName.Id); + } + } + } + + private void SetResourceType(OpenApiSchema fullSchemaForResourceData, ResourceType resourceType, SchemaRepository schemaRepository) + { + fullSchemaForResourceData.Properties[JsonApiPropertyName.Type] = _resourceTypeSchemaGenerator.Get(resourceType, schemaRepository); + } + + private void SetResourceAttributes(OpenApiSchema fullSchemaForResourceData, ResourceFieldSchemaBuilder builder, SchemaRepository schemaRepository) + { + OpenApiSchema referenceSchemaForAttributes = fullSchemaForResourceData.Properties[JsonApiPropertyName.Attributes].UnwrapExtendedReferenceSchema(); + OpenApiSchema fullSchemaForAttributes = schemaRepository.Schemas[referenceSchemaForAttributes.Reference.Id]; + + builder.SetMembersOfAttributes(fullSchemaForAttributes, schemaRepository); + + if (!fullSchemaForAttributes.Properties.Any()) + { + fullSchemaForResourceData.Properties.Remove(JsonApiPropertyName.Attributes); + schemaRepository.Schemas.Remove(referenceSchemaForAttributes.Reference.Id); + } + else + { + fullSchemaForAttributes.AdditionalPropertiesAllowed = false; + } + } + + private void SetResourceRelationships(OpenApiSchema fullSchemaForResourceData, ResourceFieldSchemaBuilder builder, SchemaRepository schemaRepository) + { + OpenApiSchema referenceSchemaForRelationships = fullSchemaForResourceData.Properties[JsonApiPropertyName.Relationships].UnwrapExtendedReferenceSchema(); + OpenApiSchema fullSchemaForRelationships = schemaRepository.Schemas[referenceSchemaForRelationships.Reference.Id]; + + builder.SetMembersOfRelationships(fullSchemaForRelationships, schemaRepository); + + if (!fullSchemaForRelationships.Properties.Any()) + { + fullSchemaForResourceData.Properties.Remove(JsonApiPropertyName.Relationships); + schemaRepository.Schemas.Remove(referenceSchemaForRelationships.Reference.Id); + } + else + { + fullSchemaForRelationships.AdditionalPropertiesAllowed = false; + } + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectDocumentationReader.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceDocumentationReader.cs similarity index 98% rename from src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectDocumentationReader.cs rename to src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceDocumentationReader.cs index fa50f98436..fe7661a4bc 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectDocumentationReader.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceDocumentationReader.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; -internal sealed class ResourceObjectDocumentationReader +internal sealed class ResourceDocumentationReader { private static readonly ConcurrentDictionary NavigatorsByAssemblyPath = new(); diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldSchemaBuilder.cs similarity index 55% rename from src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs rename to src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldSchemaBuilder.cs index 41894a3682..0641c19ae0 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldSchemaBuilder.cs @@ -1,15 +1,15 @@ using System.Reflection; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.OpenApi.JsonApiMetadata; using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships; using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; using JsonApiDotNetCore.Resources.Annotations; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; +using SchemaGenerator = Swashbuckle.AspNetCore.SwaggerGen.Patched.SchemaGenerator; namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; -internal sealed class ResourceFieldObjectSchemaBuilder +internal sealed class ResourceFieldSchemaBuilder { private static readonly Type[] RelationshipSchemaInResponseOpenTypes = [ @@ -24,43 +24,38 @@ internal sealed class ResourceFieldObjectSchemaBuilder typeof(NullableToOneRelationshipInResponse<>) ]; - private static readonly string[] RelationshipObjectPropertyNamesInOrder = + private static readonly string[] RelationshipPropertyNamesInOrder = [ JsonApiPropertyName.Links, JsonApiPropertyName.Data, JsonApiPropertyName.Meta ]; - private readonly ResourceTypeInfo _resourceTypeInfo; - private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; private readonly SchemaGenerator _defaultSchemaGenerator; - private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; - private readonly SchemaRepository _resourceSchemaRepository = new(); - private readonly IDictionary _schemasForResourceFields; + private readonly ResourceIdentifierSchemaGenerator _resourceIdentifierSchemaGenerator; + private readonly ResourceTypeInfo _resourceTypeInfo; private readonly ResourceFieldValidationMetadataProvider _resourceFieldValidationMetadataProvider; + private readonly SchemaRepository _resourceSchemaRepository = new(); private readonly RelationshipTypeFactory _relationshipTypeFactory; - private readonly NullabilityInfoContext _nullabilityInfoContext = new(); - private readonly ResourceObjectDocumentationReader _resourceObjectDocumentationReader; + private readonly IDictionary _schemasForResourceFields; + private readonly ResourceDocumentationReader _resourceDocumentationReader; - public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor, - SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, - ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) + public ResourceFieldSchemaBuilder(SchemaGenerator defaultSchemaGenerator, ResourceIdentifierSchemaGenerator resourceIdentifierSchemaGenerator, + ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider, ResourceTypeInfo resourceTypeInfo) { - ArgumentGuard.NotNull(resourceTypeInfo); - ArgumentGuard.NotNull(schemaRepositoryAccessor); ArgumentGuard.NotNull(defaultSchemaGenerator); - ArgumentGuard.NotNull(resourceTypeSchemaGenerator); + ArgumentGuard.NotNull(resourceIdentifierSchemaGenerator); + ArgumentGuard.NotNull(resourceTypeInfo); ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); - _resourceTypeInfo = resourceTypeInfo; - _schemaRepositoryAccessor = schemaRepositoryAccessor; _defaultSchemaGenerator = defaultSchemaGenerator; - _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator; + _resourceIdentifierSchemaGenerator = resourceIdentifierSchemaGenerator; + _resourceTypeInfo = resourceTypeInfo; _resourceFieldValidationMetadataProvider = resourceFieldValidationMetadataProvider; _relationshipTypeFactory = new RelationshipTypeFactory(resourceFieldValidationMetadataProvider); _schemasForResourceFields = GetFieldSchemas(); - _resourceObjectDocumentationReader = new ResourceObjectDocumentationReader(); + _resourceDocumentationReader = new ResourceDocumentationReader(); } private IDictionary GetFieldSchemas() @@ -74,11 +69,12 @@ private IDictionary GetFieldSchemas() return fullSchemaForResource.Properties; } - public void SetMembersOfAttributesObject(OpenApiSchema fullSchemaForAttributesObject) + public void SetMembersOfAttributes(OpenApiSchema fullSchemaForAttributes, SchemaRepository schemaRepository) { - ArgumentGuard.NotNull(fullSchemaForAttributesObject); + ArgumentGuard.NotNull(fullSchemaForAttributes); + ArgumentGuard.NotNull(schemaRepository); - AttrCapabilities requiredCapability = GetRequiredCapabilityForAttributes(_resourceTypeInfo.ResourceObjectOpenType); + AttrCapabilities requiredCapability = GetRequiredCapabilityForAttributes(_resourceTypeInfo.ResourceDataOpenType); foreach ((string fieldName, OpenApiSchema resourceFieldSchema) in _schemasForResourceFields) { @@ -91,35 +87,35 @@ public void SetMembersOfAttributesObject(OpenApiSchema fullSchemaForAttributesOb // Types like enum and complex attributes are not primitive and handled as reference schemas. if (!isPrimitiveOpenApiType) { - EnsureAttributeSchemaIsExposed(resourceFieldSchema, matchingAttribute); + EnsureAttributeSchemaIsExposed(resourceFieldSchema, matchingAttribute, schemaRepository); } - fullSchemaForAttributesObject.Properties[matchingAttribute.PublicName] = resourceFieldSchema; + fullSchemaForAttributes.Properties[matchingAttribute.PublicName] = resourceFieldSchema; resourceFieldSchema.Nullable = _resourceFieldValidationMetadataProvider.IsNullable(matchingAttribute); if (IsFieldRequired(matchingAttribute)) { - fullSchemaForAttributesObject.Required.Add(matchingAttribute.PublicName); + fullSchemaForAttributes.Required.Add(matchingAttribute.PublicName); } - resourceFieldSchema.Description = _resourceObjectDocumentationReader.GetDocumentationForAttribute(matchingAttribute); + resourceFieldSchema.Description = _resourceDocumentationReader.GetDocumentationForAttribute(matchingAttribute); } } } - private static AttrCapabilities GetRequiredCapabilityForAttributes(Type resourceObjectOpenType) + private static AttrCapabilities GetRequiredCapabilityForAttributes(Type resourceDataOpenType) { - return resourceObjectOpenType == typeof(ResourceObjectInResponse<>) ? AttrCapabilities.AllowView : - resourceObjectOpenType == typeof(ResourceObjectInPostRequest<>) ? AttrCapabilities.AllowCreate : - resourceObjectOpenType == typeof(ResourceObjectInPatchRequest<>) ? AttrCapabilities.AllowChange : throw new UnreachableCodeException(); + return resourceDataOpenType == typeof(ResourceDataInResponse<>) ? AttrCapabilities.AllowView : + resourceDataOpenType == typeof(ResourceDataInPostRequest<>) ? AttrCapabilities.AllowCreate : + resourceDataOpenType == typeof(ResourceDataInPatchRequest<>) ? AttrCapabilities.AllowChange : throw new UnreachableCodeException(); } - private void EnsureAttributeSchemaIsExposed(OpenApiSchema attributeReferenceSchema, AttrAttribute attribute) + private void EnsureAttributeSchemaIsExposed(OpenApiSchema attributeReferenceSchema, AttrAttribute attribute, SchemaRepository schemaRepository) { Type nonNullableTypeInPropertyType = GetRepresentedTypeForAttributeSchema(attribute); - if (_schemaRepositoryAccessor.Current.TryLookupByType(nonNullableTypeInPropertyType, out _)) + if (schemaRepository.TryLookupByType(nonNullableTypeInPropertyType, out _)) { return; } @@ -127,13 +123,14 @@ private void EnsureAttributeSchemaIsExposed(OpenApiSchema attributeReferenceSche string schemaId = attributeReferenceSchema.UnwrapExtendedReferenceSchema().Reference.Id; OpenApiSchema fullSchema = _resourceSchemaRepository.Schemas[schemaId]; - _schemaRepositoryAccessor.Current.AddDefinition(schemaId, fullSchema); - _schemaRepositoryAccessor.Current.RegisterType(nonNullableTypeInPropertyType, schemaId); + schemaRepository.AddDefinition(schemaId, fullSchema); + schemaRepository.RegisterType(nonNullableTypeInPropertyType, schemaId); } private Type GetRepresentedTypeForAttributeSchema(AttrAttribute attribute) { - NullabilityInfo attributeNullabilityInfo = _nullabilityInfoContext.Create(attribute.Property); + NullabilityInfoContext nullabilityInfoContext = new(); + NullabilityInfo attributeNullabilityInfo = nullabilityInfoContext.Create(attribute.Property); bool isNullable = attributeNullabilityInfo is { ReadState: NullabilityState.Nullable, WriteState: NullabilityState.Nullable }; @@ -146,13 +143,14 @@ private Type GetRepresentedTypeForAttributeSchema(AttrAttribute attribute) private bool IsFieldRequired(ResourceFieldAttribute field) { - bool isSchemaForPostResourceRequest = _resourceTypeInfo.ResourceObjectOpenType == typeof(ResourceObjectInPostRequest<>); + bool isSchemaForPostResourceRequest = _resourceTypeInfo.ResourceDataOpenType == typeof(ResourceDataInPostRequest<>); return isSchemaForPostResourceRequest && _resourceFieldValidationMetadataProvider.IsRequired(field); } - public void SetMembersOfRelationshipsObject(OpenApiSchema fullSchemaForRelationshipsObject) + public void SetMembersOfRelationships(OpenApiSchema fullSchemaForRelationships, SchemaRepository schemaRepository) { - ArgumentGuard.NotNull(fullSchemaForRelationshipsObject); + ArgumentGuard.NotNull(fullSchemaForRelationships); + ArgumentGuard.NotNull(schemaRepository); foreach (string fieldName in _schemasForResourceFields.Keys) { @@ -160,44 +158,19 @@ public void SetMembersOfRelationshipsObject(OpenApiSchema fullSchemaForRelations if (matchingRelationship != null) { - EnsureResourceIdentifierObjectSchemaExists(matchingRelationship); - AddRelationshipSchemaToResourceObject(matchingRelationship, fullSchemaForRelationshipsObject); + _ = _resourceIdentifierSchemaGenerator.GenerateSchema(matchingRelationship.RightType, schemaRepository); + AddRelationshipSchemaToResourceData(matchingRelationship, fullSchemaForRelationships, schemaRepository); } } } - private void EnsureResourceIdentifierObjectSchemaExists(RelationshipAttribute relationship) + private void AddRelationshipSchemaToResourceData(RelationshipAttribute relationship, OpenApiSchema fullSchemaForRelationships, + SchemaRepository schemaRepository) { - Type resourceIdentifierObjectType = typeof(ResourceIdentifierObject<>).MakeGenericType(relationship.RightType.ClrType); + Type relationshipSchemaType = GetRelationshipSchemaType(relationship, _resourceTypeInfo.ResourceDataOpenType); - if (!ResourceIdentifierObjectSchemaExists(resourceIdentifierObjectType)) - { - GenerateResourceIdentifierObjectSchema(resourceIdentifierObjectType, relationship.RightType); - } - } - - private bool ResourceIdentifierObjectSchemaExists(Type resourceIdentifierObjectType) - { - return _schemaRepositoryAccessor.Current.TryLookupByType(resourceIdentifierObjectType, out _); - } - - private void GenerateResourceIdentifierObjectSchema(Type resourceIdentifierObjectType, ResourceType resourceType) - { - OpenApiSchema referenceSchemaForResourceIdentifierObject = - _defaultSchemaGenerator.GenerateSchema(resourceIdentifierObjectType, _schemaRepositoryAccessor.Current); - - OpenApiSchema fullSchemaForResourceIdentifierObject = - _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForResourceIdentifierObject.Reference.Id]; - - fullSchemaForResourceIdentifierObject.Properties[JsonApiPropertyName.Type] = _resourceTypeSchemaGenerator.Get(resourceType); - } - - private void AddRelationshipSchemaToResourceObject(RelationshipAttribute relationship, OpenApiSchema fullSchemaForRelationshipsObject) - { - Type relationshipSchemaType = GetRelationshipSchemaType(relationship, _resourceTypeInfo.ResourceObjectOpenType); - - OpenApiSchema referenceSchemaForRelationship = - GetReferenceSchemaForRelationship(relationshipSchemaType) ?? CreateRelationshipReferenceSchema(relationshipSchemaType); + OpenApiSchema referenceSchemaForRelationship = GetReferenceSchemaForRelationship(relationshipSchemaType, schemaRepository) ?? + CreateRelationshipReferenceSchema(relationshipSchemaType, schemaRepository); var extendedReferenceSchemaForRelationship = new OpenApiSchema { @@ -205,35 +178,34 @@ private void AddRelationshipSchemaToResourceObject(RelationshipAttribute relatio { referenceSchemaForRelationship }, - Description = _resourceObjectDocumentationReader.GetDocumentationForRelationship(relationship) + Description = _resourceDocumentationReader.GetDocumentationForRelationship(relationship) }; - fullSchemaForRelationshipsObject.Properties.Add(relationship.PublicName, extendedReferenceSchemaForRelationship); + fullSchemaForRelationships.Properties.Add(relationship.PublicName, extendedReferenceSchemaForRelationship); if (IsFieldRequired(relationship)) { - fullSchemaForRelationshipsObject.Required.Add(relationship.PublicName); + fullSchemaForRelationships.Required.Add(relationship.PublicName); } } - private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type resourceObjectType) + private Type GetRelationshipSchemaType(RelationshipAttribute relationship, Type resourceDataConstructedType) { - return resourceObjectType.GetGenericTypeDefinition().IsAssignableTo(typeof(ResourceObjectInResponse<>)) + return resourceDataConstructedType.GetGenericTypeDefinition().IsAssignableTo(typeof(ResourceDataInResponse<>)) ? _relationshipTypeFactory.GetForResponse(relationship) : _relationshipTypeFactory.GetForRequest(relationship); } - private OpenApiSchema? GetReferenceSchemaForRelationship(Type relationshipSchemaType) + private OpenApiSchema? GetReferenceSchemaForRelationship(Type relationshipSchemaType, SchemaRepository schemaRepository) { - _schemaRepositoryAccessor.Current.TryLookupByType(relationshipSchemaType, out OpenApiSchema? referenceSchema); - return referenceSchema; + return schemaRepository.TryLookupByType(relationshipSchemaType, out OpenApiSchema? referenceSchema) ? referenceSchema : null; } - private OpenApiSchema CreateRelationshipReferenceSchema(Type relationshipSchemaType) + private OpenApiSchema CreateRelationshipReferenceSchema(Type relationshipSchemaType, SchemaRepository schemaRepository) { - OpenApiSchema referenceSchema = _defaultSchemaGenerator.GenerateSchema(relationshipSchemaType, _schemaRepositoryAccessor.Current); + OpenApiSchema referenceSchema = _defaultSchemaGenerator.GenerateSchema(relationshipSchemaType, schemaRepository); - OpenApiSchema fullSchema = _schemaRepositoryAccessor.Current.Schemas[referenceSchema.Reference.Id]; + OpenApiSchema fullSchema = schemaRepository.Schemas[referenceSchema.Reference.Id]; if (IsDataPropertyNullableInRelationshipSchemaType(relationshipSchemaType)) { @@ -246,7 +218,7 @@ private OpenApiSchema CreateRelationshipReferenceSchema(Type relationshipSchemaT fullSchema.SetValuesInMetaToNullable(); - fullSchema.ReorderProperties(RelationshipObjectPropertyNamesInOrder); + fullSchema.ReorderProperties(RelationshipPropertyNamesInOrder); } return referenceSchema; diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceIdentifierSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceIdentifierSchemaGenerator.cs new file mode 100644 index 0000000000..d28fc9d60e --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceIdentifierSchemaGenerator.cs @@ -0,0 +1,40 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using SchemaGenerator = Swashbuckle.AspNetCore.SwaggerGen.Patched.SchemaGenerator; + +namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; + +internal sealed class ResourceIdentifierSchemaGenerator +{ + private readonly SchemaGenerator _defaultSchemaGenerator; + private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; + + public ResourceIdentifierSchemaGenerator(SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator) + { + ArgumentGuard.NotNull(defaultSchemaGenerator); + ArgumentGuard.NotNull(resourceTypeSchemaGenerator); + + _defaultSchemaGenerator = defaultSchemaGenerator; + _resourceTypeSchemaGenerator = resourceTypeSchemaGenerator; + } + + public OpenApiSchema GenerateSchema(ResourceType resourceType, SchemaRepository schemaRepository) + { + ArgumentGuard.NotNull(resourceType); + ArgumentGuard.NotNull(schemaRepository); + + Type resourceIdentifierConstructedType = typeof(ResourceIdentifier<>).MakeGenericType(resourceType.ClrType); + + if (!schemaRepository.TryLookupByType(resourceIdentifierConstructedType, out OpenApiSchema? referenceSchemaForIdentifier)) + { + referenceSchemaForIdentifier = _defaultSchemaGenerator.GenerateSchema(resourceIdentifierConstructedType, schemaRepository); + OpenApiSchema fullSchemaForIdentifier = schemaRepository.Schemas[referenceSchemaForIdentifier.Reference.Id]; + + fullSchemaForIdentifier.Properties[JsonApiPropertyName.Type] = _resourceTypeSchemaGenerator.Get(resourceType, schemaRepository); + } + + return referenceSchemaForIdentifier; + } +} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs deleted file mode 100644 index 8d30072aae..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs +++ /dev/null @@ -1,148 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; - -internal sealed class ResourceObjectSchemaGenerator -{ - private static readonly string[] ResourceObjectPropertyNamesInOrder = - [ - JsonApiPropertyName.Type, - JsonApiPropertyName.Id, - JsonApiPropertyName.Attributes, - JsonApiPropertyName.Relationships, - JsonApiPropertyName.Links, - JsonApiPropertyName.Meta - ]; - - private readonly SchemaGenerator _defaultSchemaGenerator; - private readonly IResourceGraph _resourceGraph; - private readonly IJsonApiOptions _options; - private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; - private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator; - private readonly Func _resourceFieldObjectSchemaBuilderFactory; - private readonly ResourceObjectDocumentationReader _resourceObjectDocumentationReader; - - public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceGraph resourceGraph, IJsonApiOptions options, - ISchemaRepositoryAccessor schemaRepositoryAccessor, ResourceFieldValidationMetadataProvider resourceFieldValidationMetadataProvider) - { - ArgumentGuard.NotNull(defaultSchemaGenerator); - ArgumentGuard.NotNull(resourceGraph); - ArgumentGuard.NotNull(options); - ArgumentGuard.NotNull(schemaRepositoryAccessor); - ArgumentGuard.NotNull(resourceFieldValidationMetadataProvider); - - _defaultSchemaGenerator = defaultSchemaGenerator; - _resourceGraph = resourceGraph; - _options = options; - _schemaRepositoryAccessor = schemaRepositoryAccessor; - _resourceTypeSchemaGenerator = new ResourceTypeSchemaGenerator(schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy); - - _resourceFieldObjectSchemaBuilderFactory = resourceTypeInfo => new ResourceFieldObjectSchemaBuilder(resourceTypeInfo, schemaRepositoryAccessor, - defaultSchemaGenerator, _resourceTypeSchemaGenerator, resourceFieldValidationMetadataProvider); - - _resourceObjectDocumentationReader = new ResourceObjectDocumentationReader(); - } - - public OpenApiSchema GenerateSchema(Type resourceObjectType) - { - ArgumentGuard.NotNull(resourceObjectType); - - (OpenApiSchema fullSchemaForResourceObject, OpenApiSchema referenceSchemaForResourceObject) = EnsureSchemasExist(resourceObjectType); - - var resourceTypeInfo = ResourceTypeInfo.Create(resourceObjectType, _resourceGraph); - ResourceFieldObjectSchemaBuilder fieldObjectBuilder = _resourceFieldObjectSchemaBuilderFactory(resourceTypeInfo); - - RemoveResourceIdIfPostResourceObject(resourceTypeInfo, fullSchemaForResourceObject); - - SetResourceType(fullSchemaForResourceObject, resourceTypeInfo.ResourceType); - fullSchemaForResourceObject.SetValuesInMetaToNullable(); - - SetResourceAttributes(fullSchemaForResourceObject, fieldObjectBuilder); - - SetResourceRelationships(fullSchemaForResourceObject, fieldObjectBuilder); - - fullSchemaForResourceObject.ReorderProperties(ResourceObjectPropertyNamesInOrder); - - return referenceSchemaForResourceObject; - } - - private (OpenApiSchema fullSchema, OpenApiSchema referenceSchema) EnsureSchemasExist(Type resourceObjectType) - { - if (!_schemaRepositoryAccessor.Current.TryLookupByType(resourceObjectType, out OpenApiSchema referenceSchema)) - { - referenceSchema = _defaultSchemaGenerator.GenerateSchema(resourceObjectType, _schemaRepositoryAccessor.Current); - } - - OpenApiSchema fullSchema = _schemaRepositoryAccessor.Current.Schemas[referenceSchema.Reference.Id]; - - return (fullSchema, referenceSchema); - } - - private void RemoveResourceIdIfPostResourceObject(ResourceTypeInfo resourceTypeInfo, OpenApiSchema fullSchemaForResourceObject) - { - if (resourceTypeInfo.ResourceObjectOpenType == typeof(ResourceObjectInPostRequest<>)) - { - ClientIdGenerationMode clientIdGeneration = resourceTypeInfo.ResourceType.ClientIdGeneration ?? _options.ClientIdGeneration; - - if (clientIdGeneration == ClientIdGenerationMode.Forbidden) - { - fullSchemaForResourceObject.Required.Remove(JsonApiPropertyName.Id); - fullSchemaForResourceObject.Properties.Remove(JsonApiPropertyName.Id); - } - else if (clientIdGeneration == ClientIdGenerationMode.Allowed) - { - fullSchemaForResourceObject.Required.Remove(JsonApiPropertyName.Id); - } - } - } - - private void SetResourceType(OpenApiSchema fullSchemaForResourceObject, ResourceType resourceType) - { - fullSchemaForResourceObject.Properties[JsonApiPropertyName.Type] = _resourceTypeSchemaGenerator.Get(resourceType); - - fullSchemaForResourceObject.Description = _resourceObjectDocumentationReader.GetDocumentationForType(resourceType); - } - - private void SetResourceAttributes(OpenApiSchema fullSchemaForResourceObject, ResourceFieldObjectSchemaBuilder builder) - { - OpenApiSchema referenceSchemaForAttributesObject = - fullSchemaForResourceObject.Properties[JsonApiPropertyName.Attributes].UnwrapExtendedReferenceSchema(); - - OpenApiSchema fullSchemaForAttributesObject = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForAttributesObject.Reference.Id]; - - builder.SetMembersOfAttributesObject(fullSchemaForAttributesObject); - - if (!fullSchemaForAttributesObject.Properties.Any()) - { - fullSchemaForResourceObject.Properties.Remove(JsonApiPropertyName.Attributes); - _schemaRepositoryAccessor.Current.Schemas.Remove(referenceSchemaForAttributesObject.Reference.Id); - } - else - { - fullSchemaForAttributesObject.AdditionalPropertiesAllowed = false; - } - } - - private void SetResourceRelationships(OpenApiSchema fullSchemaForResourceObject, ResourceFieldObjectSchemaBuilder builder) - { - OpenApiSchema referenceSchemaForRelationshipsObject = - fullSchemaForResourceObject.Properties[JsonApiPropertyName.Relationships].UnwrapExtendedReferenceSchema(); - - OpenApiSchema fullSchemaForRelationshipsObject = _schemaRepositoryAccessor.Current.Schemas[referenceSchemaForRelationshipsObject.Reference.Id]; - - builder.SetMembersOfRelationshipsObject(fullSchemaForRelationshipsObject); - - if (!fullSchemaForRelationshipsObject.Properties.Any()) - { - fullSchemaForResourceObject.Properties.Remove(JsonApiPropertyName.Relationships); - _schemaRepositoryAccessor.Current.Schemas.Remove(referenceSchemaForRelationshipsObject.Reference.Id); - } - else - { - fullSchemaForRelationshipsObject.AdditionalPropertiesAllowed = false; - } - } -} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs index ea96d89a79..60ee4cb9ae 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeInfo.cs @@ -4,24 +4,29 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; internal sealed class ResourceTypeInfo { - public Type ResourceObjectOpenType { get; } + public Type ResourceDataOpenType { get; } public ResourceType ResourceType { get; } - private ResourceTypeInfo(Type resourceObjectOpenType, ResourceType resourceType) + private ResourceTypeInfo(Type resourceDataOpenType, ResourceType resourceType) { - ResourceObjectOpenType = resourceObjectOpenType; + ResourceDataOpenType = resourceDataOpenType; ResourceType = resourceType; } - public static ResourceTypeInfo Create(Type resourceObjectType, IResourceGraph resourceGraph) + public static ResourceTypeInfo Create(Type resourceDataConstructedType, IResourceGraph resourceGraph) { - ArgumentGuard.NotNull(resourceObjectType); + ArgumentGuard.NotNull(resourceDataConstructedType); ArgumentGuard.NotNull(resourceGraph); - Type resourceObjectOpenType = resourceObjectType.GetGenericTypeDefinition(); - Type resourceClrType = resourceObjectType.GenericTypeArguments[0]; + Type resourceDataOpenType = resourceDataConstructedType.GetGenericTypeDefinition(); + Type resourceClrType = resourceDataConstructedType.GenericTypeArguments[0]; ResourceType resourceType = resourceGraph.GetResourceType(resourceClrType); - return new ResourceTypeInfo(resourceObjectOpenType, resourceType); + return new ResourceTypeInfo(resourceDataOpenType, resourceType); + } + + public override string ToString() + { + return $"{ResourceDataOpenType.Name} for {ResourceType.ClrType.Name}"; } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs index 8069fbf898..4fb5d0d1a4 100644 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceTypeSchemaGenerator.cs @@ -1,33 +1,29 @@ -using System.Text.Json; -using Humanizer; using JsonApiDotNetCore.Configuration; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; internal sealed class ResourceTypeSchemaGenerator { - private const string ResourceTypeSchemaIdTemplate = "[ResourceName] Resource Type"; - private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor; - private readonly JsonNamingPolicy? _namingPolicy; - private readonly Dictionary _resourceClrTypeSchemaCache = []; + private readonly JsonApiSchemaIdSelector _schemaIdSelector; - public ResourceTypeSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor, JsonNamingPolicy? namingPolicy) + public ResourceTypeSchemaGenerator(JsonApiSchemaIdSelector schemaIdSelector) { - ArgumentGuard.NotNull(schemaRepositoryAccessor); + ArgumentGuard.NotNull(schemaIdSelector); - _schemaRepositoryAccessor = schemaRepositoryAccessor; - _namingPolicy = namingPolicy; + _schemaIdSelector = schemaIdSelector; } - public OpenApiSchema Get(ResourceType resourceType) + public OpenApiSchema Get(ResourceType resourceType, SchemaRepository schemaRepository) { ArgumentGuard.NotNull(resourceType); + ArgumentGuard.NotNull(schemaRepository); - if (_resourceClrTypeSchemaCache.TryGetValue(resourceType.ClrType, out OpenApiSchema? extendedReferenceSchema)) + if (schemaRepository.TryLookupByType(resourceType.ClrType, out OpenApiSchema? referenceSchema)) { - return extendedReferenceSchema; + return referenceSchema; } var fullSchema = new OpenApiSchema @@ -40,9 +36,9 @@ public OpenApiSchema Get(ResourceType resourceType) AdditionalPropertiesAllowed = false }; - string schemaId = GetSchemaId(resourceType); + string schemaId = _schemaIdSelector.GetSchemaId(resourceType); - var referenceSchema = new OpenApiSchema + referenceSchema = new OpenApiSchema { Reference = new OpenApiReference { @@ -51,24 +47,9 @@ public OpenApiSchema Get(ResourceType resourceType) } }; - extendedReferenceSchema = new OpenApiSchema - { - AllOf = new List - { - referenceSchema - } - }; - - _schemaRepositoryAccessor.Current.AddDefinition(schemaId, fullSchema); - _resourceClrTypeSchemaCache.Add(resourceType.ClrType, extendedReferenceSchema); - - return extendedReferenceSchema; - } - - private string GetSchemaId(ResourceType resourceType) - { - string pascalCaseSchemaId = ResourceTypeSchemaIdTemplate.Replace("[ResourceName]", resourceType.PublicName.Singularize()).ToPascalCase(); + schemaRepository.AddDefinition(schemaId, fullSchema); + schemaRepository.RegisterType(resourceType.ClrType, schemaId); - return _namingPolicy != null ? _namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId; + return referenceSchema; } } diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs deleted file mode 100644 index 578106b29d..0000000000 --- a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/SchemaRepositoryAccessor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; - -internal sealed class SchemaRepositoryAccessor : ISchemaRepositoryAccessor -{ - private SchemaRepository? _schemaRepository; - - public SchemaRepository Current - { - get - { - if (_schemaRepository == null) - { - throw new InvalidOperationException("SchemaRepository is unavailable."); - } - - return _schemaRepository; - } - set - { - ArgumentGuard.NotNull(value); - - _schemaRepository = value; - } - } -} diff --git a/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ServerDocumentFilter.cs b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ServerDocumentFilter.cs new file mode 100644 index 0000000000..0281dc4b38 --- /dev/null +++ b/src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ServerDocumentFilter.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace JsonApiDotNetCore.OpenApi.SwaggerComponents; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +internal sealed class ServerDocumentFilter : IDocumentFilter +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public ServerDocumentFilter(IHttpContextAccessor httpContextAccessor) + { + ArgumentGuard.NotNull(httpContextAccessor); + + _httpContextAccessor = httpContextAccessor; + } + + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + if (swaggerDoc.Servers.Count == 0 && _httpContextAccessor.HttpContext != null) + { + HttpRequest httpRequest = _httpContextAccessor.HttpContext.Request; + + swaggerDoc.Servers.Add(new OpenApiServer + { + Url = $"{httpRequest.Scheme}://{httpRequest.Host.Value}" + }); + } + } +} diff --git a/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs b/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs index 5a862409bc..af853af785 100644 --- a/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs +++ b/src/JsonApiDotNetCore/Diagnostics/CodeTimingSessionManager.cs @@ -31,7 +31,7 @@ public static ICodeTimer Current static CodeTimingSessionManager() { #if DEBUG - IsEnabled = !IsRunningInTest() && !IsRunningInBenchmark(); + IsEnabled = !IsRunningInTest() && !IsRunningInBenchmark() && !IsGeneratingOpenApiDocumentAtBuildTime(); #else IsEnabled = false; #endif @@ -52,6 +52,12 @@ private static bool IsRunningInBenchmark() return Assembly.GetEntryAssembly()?.GetName().Name == "Benchmarks"; } + // ReSharper disable once UnusedMember.Local + private static bool IsGeneratingOpenApiDocumentAtBuildTime() + { + return Environment.GetCommandLineArgs().Any(argument => argument.Contains("GetDocument.Insider")); + } + private static void AssertHasActiveSession() { if (_session == null) diff --git a/test/OpenApiClientTests/.editorconfig b/test/OpenApiClientTests/.editorconfig new file mode 100644 index 0000000000..e2ec1cac44 --- /dev/null +++ b/test/OpenApiClientTests/.editorconfig @@ -0,0 +1,3 @@ +# Workaround for incorrect nullability in NSwag generated clients. +[*Client.cs] +dotnet_diagnostic.CS8765.severity = none diff --git a/test/OpenApiClientTests/LegacyClient/ResponseTests.cs b/test/OpenApiClientTests/LegacyClient/ResponseTests.cs index c273a20a95..9a7f6f50ff 100644 --- a/test/OpenApiClientTests/LegacyClient/ResponseTests.cs +++ b/test/OpenApiClientTests/LegacyClient/ResponseTests.cs @@ -112,7 +112,6 @@ public async Task Getting_resource_collection_translates_response() FlightDataInResponse flight = document.Data.First(); flight.Id.Should().Be(flightId); - flight.Type.Should().Be(FlightResourceType.Flights); flight.Links.Self.Should().Be(flightResourceLink); flight.Meta.Should().HaveCount(1); flight.Meta["docs"].Should().Be(flightMetaValue); @@ -349,7 +348,6 @@ public async Task Patching_resource_with_side_effects_translates_response() }); // Assert - document.Data.Type.Should().Be(FlightResourceType.Flights); document.Data.Attributes.Should().BeNull(); document.Data.Relationships.Should().BeNull(); } diff --git a/test/OpenApiClientTests/LegacyClient/swagger.g.json b/test/OpenApiClientTests/LegacyClient/swagger.g.json index 644af8144f..1a86868059 100644 --- a/test/OpenApiClientTests/LegacyClient/swagger.g.json +++ b/test/OpenApiClientTests/LegacyClient/swagger.g.json @@ -4,6 +4,11 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { "/api/airplanes": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -674,7 +679,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -712,7 +717,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -742,7 +747,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -816,7 +821,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -866,7 +871,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -908,7 +913,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1008,7 +1013,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1058,7 +1063,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1095,14 +1100,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1145,14 +1150,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1334,7 +1339,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1384,7 +1389,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1421,14 +1426,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1471,14 +1476,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1651,7 +1656,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1689,7 +1694,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1719,7 +1724,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1793,7 +1798,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1843,7 +1848,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1885,7 +1890,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1985,7 +1990,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2035,7 +2040,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2072,14 +2077,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2122,14 +2127,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2219,7 +2224,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2269,7 +2274,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2306,14 +2311,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2356,14 +2361,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2545,7 +2550,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2595,7 +2600,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2632,14 +2637,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2682,14 +2687,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2871,7 +2876,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2921,7 +2926,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2958,14 +2963,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -3008,14 +3013,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -3077,150 +3082,475 @@ } } } - } - }, - "components": { - "schemas": { - "aircraft-kind": { - "enum": [ - "Turboprops", - "LightJet", - "MidSizeJet", - "JumboJet" + }, + "/api/passengers": { + "get": { + "tags": [ + "passengers" ], - "type": "string" - }, - "airline": { - "enum": [ - "DeltaAirLines", - "LufthansaGroup", - "AirFranceKlm" + "summary": "Retrieves a collection of passengers.", + "operationId": "get-passenger-collection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } ], - "type": "string", - "description": "Lists the various airlines used in this API." - }, - "airplane-attributes-in-patch-request": { - "type": "object", - "properties": { - "name": { - "maxLength": 255, - "type": "string" - }, - "serial-number": { - "maxLength": 16, - "type": "string", - "nullable": true - }, - "airtime-in-hours": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "last-serviced-at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "is-in-maintenance": { - "type": "boolean" + "responses": { + "200": { + "description": "Successfully returns the found passengers, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-collection-response-document" + } + } + } }, - "manufactured-in-city": { - "maxLength": 85, - "type": "string", - "nullable": true + "400": { + "description": "The query string is invalid." } - }, - "additionalProperties": false + } }, - "airplane-attributes-in-post-request": { - "required": [ - "name" + "head": { + "tags": [ + "passengers" ], - "type": "object", - "properties": { - "name": { - "maxLength": 255, - "type": "string" - }, - "serial-number": { - "maxLength": 16, - "type": "string", - "nullable": true - }, - "airtime-in-hours": { - "type": "integer", - "format": "int32", - "nullable": true + "summary": "Retrieves a collection of passengers without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "head-passenger-collection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." }, - "last-serviced-at": { - "type": "string", - "format": "date-time", - "nullable": true + "400": { + "description": "The query string is invalid." } - }, - "additionalProperties": false + } }, - "airplane-attributes-in-response": { - "type": "object", - "properties": { - "name": { - "maxLength": 255, - "type": "string" - }, - "serial-number": { - "maxLength": 16, - "type": "string", - "nullable": true - }, - "airtime-in-hours": { - "type": "integer", - "format": "int32", - "nullable": true + "post": { + "tags": [ + "passengers" + ], + "summary": "Creates a new passenger.", + "operationId": "post-passenger", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the passenger to create.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-post-request-document" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The passenger was successfully created, which resulted in additional changes. The newly created passenger is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-primary-response-document" + } + } + } }, - "last-serviced-at": { - "type": "string", - "format": "date-time", - "nullable": true + "204": { + "description": "The passenger was successfully created, which did not result in additional changes." }, - "manufactured-at": { - "type": "string", - "description": "Gets the day on which this airplane was manufactured.", - "format": "date-time" + "400": { + "description": "The query string is invalid or the request body is missing or malformed." }, - "is-in-maintenance": { - "type": "boolean" + "403": { + "description": "Client-generated IDs cannot be used at this endpoint." }, - "manufactured-in-city": { - "maxLength": 85, - "type": "string", - "nullable": true + "409": { + "description": "A resource type in the request body is incompatible." }, - "kind": { - "allOf": [ - { - "$ref": "#/components/schemas/aircraft-kind" - } - ] + "422": { + "description": "Validation of the request body failed." } - }, - "additionalProperties": false - }, - "airplane-collection-response-document": { - "required": [ - "data", - "links" + } + } + }, + "/api/passengers/{id}": { + "get": { + "tags": [ + "passengers" ], - "type": "object", - "properties": { - "jsonapi": { - "allOf": [ - { - "$ref": "#/components/schemas/jsonapi-object" - } - ] + "summary": "Retrieves an individual passenger by its identifier.", + "operationId": "get-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to retrieve.", + "required": true, + "schema": { + "type": "string" + } }, - "links": { - "allOf": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found passenger.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-primary-response-document" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The passenger does not exist." + } + } + }, + "head": { + "tags": [ + "passengers" + ], + "summary": "Retrieves an individual passenger by its identifier without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "head-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The passenger does not exist." + } + } + }, + "patch": { + "tags": [ + "passengers" + ], + "summary": "Updates an existing passenger.", + "operationId": "patch-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the passenger to update. Omitted fields are left unchanged.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-patch-request-document" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "The passenger was successfully updated, which resulted in additional changes. The updated passenger is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-primary-response-document" + } + } + } + }, + "204": { + "description": "The passenger was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "404": { + "description": "The passenger or a related resource does not exist." + }, + "409": { + "description": "A resource type or identifier in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + }, + "delete": { + "tags": [ + "passengers" + ], + "summary": "Deletes an existing passenger by its identifier.", + "operationId": "delete-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The passenger was successfully deleted." + }, + "404": { + "description": "The passenger does not exist." + } + } + } + } + }, + "components": { + "schemas": { + "aircraft-kind": { + "enum": [ + "Turboprops", + "LightJet", + "MidSizeJet", + "JumboJet" + ], + "type": "string" + }, + "airline": { + "enum": [ + "DeltaAirLines", + "LufthansaGroup", + "AirFranceKlm" + ], + "type": "string", + "description": "Lists the various airlines used in this API." + }, + "airplane-attributes-in-patch-request": { + "type": "object", + "properties": { + "name": { + "maxLength": 255, + "type": "string" + }, + "serial-number": { + "maxLength": 16, + "type": "string", + "nullable": true + }, + "airtime-in-hours": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "last-serviced-at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is-in-maintenance": { + "type": "boolean" + }, + "manufactured-in-city": { + "maxLength": 85, + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "airplane-attributes-in-post-request": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "maxLength": 255, + "type": "string" + }, + "serial-number": { + "maxLength": 16, + "type": "string", + "nullable": true + }, + "airtime-in-hours": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "last-serviced-at": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false + }, + "airplane-attributes-in-response": { + "type": "object", + "properties": { + "name": { + "maxLength": 255, + "type": "string" + }, + "serial-number": { + "maxLength": 16, + "type": "string", + "nullable": true + }, + "airtime-in-hours": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "last-serviced-at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "manufactured-at": { + "type": "string", + "description": "Gets the day on which this airplane was manufactured.", + "format": "date-time" + }, + "is-in-maintenance": { + "type": "boolean" + }, + "manufactured-in-city": { + "maxLength": 85, + "type": "string", + "nullable": true + }, + "kind": { + "allOf": [ + { + "$ref": "#/components/schemas/aircraft-kind" + } + ] + } + }, + "additionalProperties": false + }, + "airplane-collection-response-document": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi" + } + ] + }, + "links": { + "allOf": [ { "$ref": "#/components/schemas/links-in-resource-collection-document" } @@ -3232,6 +3562,12 @@ "$ref": "#/components/schemas/airplane-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3250,11 +3586,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-resource-type" - } - ] + "$ref": "#/components/schemas/airplane-resource-type" }, "id": { "minLength": 1, @@ -3284,11 +3616,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-resource-type" - } - ] + "$ref": "#/components/schemas/airplane-resource-type" }, "attributes": { "allOf": [ @@ -3308,53 +3636,48 @@ "additionalProperties": false }, "airplane-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "airplane-patch-request-document": { @@ -3399,7 +3722,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3417,6 +3740,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3481,6 +3810,34 @@ ], "type": "string" }, + "data-in-response": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "airplanes": "#/components/schemas/airplane-data-in-response", + "flight-attendants": "#/components/schemas/flight-attendant-data-in-response", + "flights": "#/components/schemas/flight-data-in-response", + "passengers": "#/components/schemas/passenger-data-in-response" + } + }, + "x-abstract": true + }, "flight-attendant-attributes-in-patch-request": { "type": "object", "properties": { @@ -3558,7 +3915,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3575,6 +3932,12 @@ "$ref": "#/components/schemas/flight-attendant-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3593,11 +3956,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] + "$ref": "#/components/schemas/flight-attendant-resource-type" }, "id": { "minLength": 1, @@ -3627,11 +3986,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] + "$ref": "#/components/schemas/flight-attendant-resource-type" }, "attributes": { "allOf": [ @@ -3651,53 +4006,48 @@ "additionalProperties": false }, "flight-attendant-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "flight-attendant-identifier": { @@ -3708,11 +4058,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] + "$ref": "#/components/schemas/flight-attendant-resource-type" }, "id": { "minLength": 1, @@ -3731,7 +4077,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3768,7 +4114,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3838,7 +4184,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3856,6 +4202,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3943,7 +4295,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3961,6 +4313,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4041,7 +4399,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4058,6 +4416,12 @@ "$ref": "#/components/schemas/flight-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4076,11 +4440,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] + "$ref": "#/components/schemas/flight-resource-type" }, "id": { "minLength": 1, @@ -4110,11 +4470,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] + "$ref": "#/components/schemas/flight-resource-type" }, "relationships": { "allOf": [ @@ -4127,53 +4483,48 @@ "additionalProperties": false }, "flight-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "flight-identifier": { @@ -4184,11 +4535,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] + "$ref": "#/components/schemas/flight-resource-type" }, "id": { "minLength": 1, @@ -4207,7 +4554,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4276,7 +4623,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4294,6 +4641,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4416,7 +4769,7 @@ "type": "string", "additionalProperties": false }, - "jsonapi-object": { + "jsonapi": { "type": "object", "properties": { "version": { @@ -4444,7 +4797,7 @@ }, "additionalProperties": false }, - "links-in-relationship-object": { + "links-in-relationship": { "required": [ "related", "self" @@ -4490,6 +4843,19 @@ }, "additionalProperties": false }, + "links-in-resource-data": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "links-in-resource-document": { "required": [ "self" @@ -4560,19 +4926,6 @@ }, "additionalProperties": false }, - "links-in-resource-object": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullable-flight-attendant-identifier-response-document": { "required": [ "data", @@ -4583,7 +4936,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4622,7 +4975,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4641,6 +4994,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4677,7 +5036,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -4699,6 +5058,29 @@ }, "additionalProperties": false }, + "passenger-attributes-in-patch-request": { + "type": "object", + "properties": { + "document-number": { + "maxLength": 9, + "type": "string" + } + }, + "additionalProperties": false + }, + "passenger-attributes-in-post-request": { + "required": [ + "document-number" + ], + "type": "object", + "properties": { + "document-number": { + "maxLength": 9, + "type": "string" + } + }, + "additionalProperties": false + }, "passenger-attributes-in-response": { "type": "object", "properties": { @@ -4726,7 +5108,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4743,6 +5125,12 @@ "$ref": "#/components/schemas/passenger-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4753,39 +5141,131 @@ }, "additionalProperties": false }, - "passenger-data-in-response": { + "passenger-data-in-patch-request": { "required": [ "id", - "links", "type" ], "type": "object", "properties": { "type": { + "$ref": "#/components/schemas/passenger-resource-type" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-attributes-in-patch-request" + } + ] + } + }, + "additionalProperties": false + }, + "passenger-data-in-post-request": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/passenger-resource-type" + }, + "attributes": { "allOf": [ { - "$ref": "#/components/schemas/passenger-resource-type" + "$ref": "#/components/schemas/passenger-attributes-in-post-request" } ] + } + }, + "additionalProperties": false + }, + "passenger-data-in-response": { + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" + }, + { + "required": [ + "links" + ], + "type": "object", + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-attributes-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + } + ], + "additionalProperties": false + }, + "passenger-identifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/passenger-resource-type" }, "id": { "minLength": 1, "type": "string" - }, - "attributes": { + } + }, + "additionalProperties": false + }, + "passenger-identifier-collection-response-document": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/passenger-attributes-in-response" + "$ref": "#/components/schemas/jsonapi" } ] }, "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-resource-object" + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" } ] }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/passenger-identifier" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4796,28 +5276,39 @@ }, "additionalProperties": false }, - "passenger-identifier": { + "passenger-patch-request-document": { "required": [ - "id", - "type" + "data" ], "type": "object", "properties": { - "type": { + "data": { "allOf": [ { - "$ref": "#/components/schemas/passenger-resource-type" + "$ref": "#/components/schemas/passenger-data-in-patch-request" } ] - }, - "id": { - "minLength": 1, - "type": "string" } }, "additionalProperties": false }, - "passenger-identifier-collection-response-document": { + "passenger-post-request-document": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-data-in-post-request" + } + ] + } + }, + "additionalProperties": false + }, + "passenger-primary-response-document": { "required": [ "data", "links" @@ -4827,21 +5318,28 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "$ref": "#/components/schemas/links-in-resource-document" } ] }, "data": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-data-in-response" + } + ] + }, + "included": { "type": "array", "items": { - "$ref": "#/components/schemas/passenger-identifier" + "$ref": "#/components/schemas/data-in-response" } }, "meta": { @@ -4885,7 +5383,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -4929,7 +5427,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -4973,7 +5471,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -5018,7 +5516,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, diff --git a/test/OpenApiClientTests/NamingConventions/CamelCase/GeneratedTypesTests.cs b/test/OpenApiClientTests/NamingConventions/CamelCase/GeneratedTypesTests.cs index 003c452c98..b5adea1a5f 100644 --- a/test/OpenApiClientTests/NamingConventions/CamelCase/GeneratedTypesTests.cs +++ b/test/OpenApiClientTests/NamingConventions/CamelCase/GeneratedTypesTests.cs @@ -1,77 +1,143 @@ using OpenApiClientTests.NamingConventions.CamelCase.GeneratedCode; using Xunit; +using GeneratedClient = OpenApiClientTests.NamingConventions.CamelCase.GeneratedCode.CamelCaseClient; namespace OpenApiClientTests.NamingConventions.CamelCase; public sealed class GeneratedTypesTests { [Fact] - public void Generated_code_is_named_as_expected() + public void Generated_endpoint_methods_are_named_as_expected() { - _ = nameof(CamelCaseClient.GetSupermarketCollectionAsync); - _ = nameof(CamelCaseClient.GetSupermarketCollectionAsync); - _ = nameof(CamelCaseClient.PostSupermarketAsync); - _ = nameof(CamelCaseClient.GetSupermarketAsync); - _ = nameof(CamelCaseClient.GetSupermarketAsync); - _ = nameof(CamelCaseClient.PatchSupermarketAsync); - _ = nameof(CamelCaseClient.DeleteSupermarketAsync); - _ = nameof(CamelCaseClient.GetSupermarketBackupStoreManagerAsync); - _ = nameof(CamelCaseClient.GetSupermarketBackupStoreManagerAsync); - _ = nameof(CamelCaseClient.GetSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(CamelCaseClient.GetSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(CamelCaseClient.PatchSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(CamelCaseClient.GetSupermarketCashiersAsync); - _ = nameof(CamelCaseClient.GetSupermarketCashiersAsync); - _ = nameof(CamelCaseClient.GetSupermarketCashiersRelationshipAsync); - _ = nameof(CamelCaseClient.GetSupermarketCashiersRelationshipAsync); - _ = nameof(CamelCaseClient.PostSupermarketCashiersRelationshipAsync); - _ = nameof(CamelCaseClient.PatchSupermarketCashiersRelationshipAsync); - _ = nameof(CamelCaseClient.DeleteSupermarketCashiersRelationshipAsync); - _ = nameof(CamelCaseClient.GetSupermarketStoreManagerAsync); - _ = nameof(CamelCaseClient.GetSupermarketStoreManagerAsync); - _ = nameof(CamelCaseClient.GetSupermarketStoreManagerRelationshipAsync); - _ = nameof(CamelCaseClient.GetSupermarketStoreManagerRelationshipAsync); - _ = nameof(CamelCaseClient.PatchSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketCollectionAsync); + _ = nameof(GeneratedClient.HeadSupermarketCollectionAsync); + _ = nameof(GeneratedClient.PostSupermarketAsync); + _ = nameof(GeneratedClient.GetSupermarketAsync); + _ = nameof(GeneratedClient.HeadSupermarketAsync); + _ = nameof(GeneratedClient.PatchSupermarketAsync); + _ = nameof(GeneratedClient.DeleteSupermarketAsync); + _ = nameof(GeneratedClient.GetSupermarketBackupStoreManagerAsync); + _ = nameof(GeneratedClient.HeadSupermarketBackupStoreManagerAsync); + _ = nameof(GeneratedClient.GetSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketCashiersAsync); + _ = nameof(GeneratedClient.HeadSupermarketCashiersAsync); + _ = nameof(GeneratedClient.GetSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.PostSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.DeleteSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketStoreManagerAsync); + _ = nameof(GeneratedClient.HeadSupermarketStoreManagerAsync); + _ = nameof(GeneratedClient.GetSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketStoreManagerRelationshipAsync); + + _ = nameof(GeneratedClient.GetStaffMemberCollectionAsync); + _ = nameof(GeneratedClient.HeadStaffMemberCollectionAsync); + _ = nameof(GeneratedClient.PostStaffMemberAsync); + _ = nameof(GeneratedClient.GetStaffMemberAsync); + _ = nameof(GeneratedClient.HeadStaffMemberAsync); + _ = nameof(GeneratedClient.PatchStaffMemberAsync); + _ = nameof(GeneratedClient.DeleteStaffMemberAsync); + } + [Fact] + public void Generated_top_level_document_types_are_named_as_expected() + { _ = nameof(SupermarketCollectionResponseDocument); + _ = nameof(SupermarketPostRequestDocument); + _ = nameof(SupermarketPrimaryResponseDocument); + _ = nameof(SupermarketPatchRequestDocument); + + _ = nameof(StaffMemberCollectionResponseDocument); + _ = nameof(StaffMemberPostRequestDocument); + _ = nameof(StaffMemberPrimaryResponseDocument); + _ = nameof(StaffMemberPatchRequestDocument); + _ = nameof(StaffMemberIdentifierCollectionResponseDocument); + _ = nameof(StaffMemberIdentifierResponseDocument); + _ = nameof(StaffMemberSecondaryResponseDocument); + _ = nameof(NullableStaffMemberSecondaryResponseDocument); + _ = nameof(NullableStaffMemberIdentifierResponseDocument); + } + + [Fact] + public void Generated_link_types_are_named_as_expected() + { _ = nameof(LinksInResourceCollectionDocument); - _ = nameof(JsonapiObject); - _ = nameof(SupermarketDataInResponse); - _ = nameof(SupermarketResourceType.Supermarkets); + _ = nameof(LinksInResourceDocument); + _ = nameof(LinksInResourceIdentifierCollectionDocument); + _ = nameof(LinksInResourceIdentifierDocument); + _ = nameof(LinksInResourceData); + _ = nameof(LinksInRelationship); + } + + [Fact] + public void Generated_resource_field_types_are_named_as_expected() + { + _ = nameof(SupermarketAttributesInPostRequest.NameOfCity); + _ = nameof(SupermarketAttributesInPostRequest.Kind); + _ = nameof(SupermarketAttributesInPatchRequest.NameOfCity); + _ = nameof(SupermarketAttributesInPatchRequest.Kind); _ = nameof(SupermarketAttributesInResponse.NameOfCity); + _ = nameof(SupermarketAttributesInResponse.Kind); + _ = nameof(SupermarketRelationshipsInPostRequest.StoreManager); + _ = nameof(SupermarketRelationshipsInPostRequest.BackupStoreManager); + _ = nameof(SupermarketRelationshipsInPostRequest.Cashiers); + _ = nameof(SupermarketRelationshipsInPatchRequest.StoreManager); + _ = nameof(SupermarketRelationshipsInPatchRequest.BackupStoreManager); + _ = nameof(SupermarketRelationshipsInPatchRequest.Cashiers); _ = nameof(SupermarketRelationshipsInResponse.StoreManager); _ = nameof(SupermarketRelationshipsInResponse.BackupStoreManager); - _ = nameof(LinksInResourceObject); + _ = nameof(SupermarketRelationshipsInResponse.Cashiers); _ = nameof(SupermarketType); - _ = nameof(CamelCaseClient.GetSupermarketAsync); + + _ = nameof(StaffMemberAttributesInPostRequest.Name); + _ = nameof(StaffMemberAttributesInPostRequest.Age); + _ = nameof(StaffMemberAttributesInPatchRequest.Name); + _ = nameof(StaffMemberAttributesInPatchRequest.Age); + _ = nameof(StaffMemberAttributesInResponse.Name); + _ = nameof(StaffMemberAttributesInResponse.Age); + } + + [Fact] + public void Generated_relationship_container_types_are_named_as_expected() + { + _ = nameof(ToOneStaffMemberInRequest); _ = nameof(ToOneStaffMemberInResponse); + _ = nameof(NullableToOneStaffMemberInRequest); _ = nameof(NullableToOneStaffMemberInResponse); + _ = nameof(ToManyStaffMemberInRequest); _ = nameof(ToManyStaffMemberInResponse); - _ = nameof(LinksInRelationshipObject); - _ = nameof(StaffMemberIdentifier); + } + + [Fact] + public void Generated_resource_type_enums_are_named_as_expected() + { + _ = nameof(SupermarketResourceType.Supermarkets); _ = nameof(StaffMemberResourceType.StaffMembers); - _ = nameof(SupermarketPrimaryResponseDocument); - _ = nameof(LinksInResourceDocument); - _ = nameof(StaffMemberSecondaryResponseDocument); - _ = nameof(StaffMemberDataInResponse); - _ = nameof(StaffMemberAttributesInResponse); - _ = nameof(NullableStaffMemberSecondaryResponseDocument); - _ = nameof(StaffMemberCollectionResponseDocument); - _ = nameof(StaffMemberIdentifierResponseDocument); - _ = nameof(LinksInResourceIdentifierDocument); - _ = nameof(NullableStaffMemberIdentifierResponseDocument); - _ = nameof(StaffMemberIdentifierCollectionResponseDocument); - _ = nameof(LinksInResourceIdentifierCollectionDocument); - _ = nameof(SupermarketPostRequestDocument); + } + + [Fact] + public void Generated_data_types_are_named_as_expected() + { _ = nameof(SupermarketDataInPostRequest); - _ = nameof(SupermarketAttributesInPostRequest); - _ = nameof(SupermarketRelationshipsInPostRequest); - _ = nameof(ToOneStaffMemberInRequest); - _ = nameof(NullableToOneStaffMemberInRequest); - _ = nameof(ToManyStaffMemberInRequest); - _ = nameof(SupermarketPatchRequestDocument); _ = nameof(SupermarketDataInPatchRequest); - _ = nameof(SupermarketAttributesInPatchRequest); - _ = nameof(SupermarketRelationshipsInPatchRequest); + _ = nameof(SupermarketDataInResponse); + + _ = nameof(StaffMemberDataInPostRequest); + _ = nameof(StaffMemberDataInPatchRequest); + _ = nameof(StaffMemberDataInResponse); + + _ = nameof(DataInResponse); + + _ = nameof(StaffMemberIdentifier); + } + + [Fact] + public void Generated_code_is_named_as_expected() + { + _ = nameof(Jsonapi); } } diff --git a/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json index da02747a29..eccc5ec2e3 100644 --- a/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/CamelCase/swagger.g.json @@ -4,7 +4,337 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { + "/staffMembers": { + "get": { + "tags": [ + "staffMembers" + ], + "summary": "Retrieves a collection of staffMembers.", + "operationId": "getStaffMemberCollection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found staffMembers, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staffMemberCollectionResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + } + } + }, + "head": { + "tags": [ + "staffMembers" + ], + "summary": "Retrieves a collection of staffMembers without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headStaffMemberCollection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + } + } + }, + "post": { + "tags": [ + "staffMembers" + ], + "summary": "Creates a new staffMember.", + "operationId": "postStaffMember", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the staffMember to create.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberPostRequestDocument" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The staffMember was successfully created, which resulted in additional changes. The newly created staffMember is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staffMemberPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "The staffMember was successfully created, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "403": { + "description": "Client-generated IDs cannot be used at this endpoint." + }, + "409": { + "description": "A resource type in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + } + }, + "/staffMembers/{id}": { + "get": { + "tags": [ + "staffMembers" + ], + "summary": "Retrieves an individual staffMember by its identifier.", + "operationId": "getStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staffMember to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found staffMember.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staffMemberPrimaryResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The staffMember does not exist." + } + } + }, + "head": { + "tags": [ + "staffMembers" + ], + "summary": "Retrieves an individual staffMember by its identifier without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staffMember to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The staffMember does not exist." + } + } + }, + "patch": { + "tags": [ + "staffMembers" + ], + "summary": "Updates an existing staffMember.", + "operationId": "patchStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staffMember to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the staffMember to update. Omitted fields are left unchanged.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberPatchRequestDocument" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "The staffMember was successfully updated, which resulted in additional changes. The updated staffMember is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staffMemberPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "The staffMember was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "404": { + "description": "The staffMember or a related resource does not exist." + }, + "409": { + "description": "A resource type or identifier in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + }, + "delete": { + "tags": [ + "staffMembers" + ], + "summary": "Deletes an existing staffMember by its identifier.", + "operationId": "deleteStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staffMember to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The staffMember was successfully deleted." + }, + "404": { + "description": "The staffMember does not exist." + } + } + } + }, "/supermarkets": { "get": { "tags": [ @@ -23,7 +353,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +391,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +421,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +495,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +545,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +587,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +687,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +737,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +774,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +824,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -591,7 +921,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -641,7 +971,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -678,14 +1008,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -728,14 +1058,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -917,7 +1247,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -967,7 +1297,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1004,14 +1334,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1054,14 +1384,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1127,7 +1457,33 @@ }, "components": { "schemas": { - "jsonapiObject": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "staffMembers": "#/components/schemas/staffMemberDataInResponse", + "supermarkets": "#/components/schemas/supermarketDataInResponse" + } + }, + "x-abstract": true + }, + "jsonapi": { "type": "object", "properties": { "version": { @@ -1155,7 +1511,7 @@ }, "additionalProperties": false }, - "linksInRelationshipObject": { + "linksInRelationship": { "required": [ "related", "self" @@ -1201,6 +1557,19 @@ }, "additionalProperties": false }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceDocument": { "required": [ "self" @@ -1271,19 +1640,6 @@ }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullableStaffMemberIdentifierResponseDocument": { "required": [ "data", @@ -1294,7 +1650,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1333,7 +1689,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1352,6 +1708,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1388,7 +1750,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -1410,6 +1772,35 @@ }, "additionalProperties": false }, + "staffMemberAttributesInPatchRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "staffMemberAttributesInPostRequest": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, "staffMemberAttributesInResponse": { "type": "object", "properties": { @@ -1433,7 +1824,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1450,6 +1841,12 @@ "$ref": "#/components/schemas/staffMemberDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1460,20 +1857,15 @@ }, "additionalProperties": false }, - "staffMemberDataInResponse": { + "staffMemberDataInPatchRequest": { "required": [ "id", - "links", "type" ], "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/staffMemberResourceType" - } - ] + "$ref": "#/components/schemas/staffMemberResourceType" }, "id": { "minLength": 1, @@ -1482,25 +1874,68 @@ "attributes": { "allOf": [ { - "$ref": "#/components/schemas/staffMemberAttributesInResponse" + "$ref": "#/components/schemas/staffMemberAttributesInPatchRequest" } ] + } + }, + "additionalProperties": false + }, + "staffMemberDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/staffMemberResourceType" }, - "links": { + "attributes": { "allOf": [ { - "$ref": "#/components/schemas/linksInResourceObject" + "$ref": "#/components/schemas/staffMemberAttributesInPostRequest" } ] + } + }, + "additionalProperties": false + }, + "staffMemberDataInResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberAttributesInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "staffMemberIdentifier": { @@ -1511,11 +1946,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/staffMemberResourceType" - } - ] + "$ref": "#/components/schemas/staffMemberResourceType" }, "id": { "minLength": 1, @@ -1534,7 +1965,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1571,7 +2002,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1599,6 +2030,82 @@ }, "additionalProperties": false }, + "staffMemberPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberDataInPatchRequest" + } + ] + } + }, + "additionalProperties": false + }, + "staffMemberPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberDataInPostRequest" + } + ] + } + }, + "additionalProperties": false + }, + "staffMemberPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/staffMemberDataInResponse" + } + ] + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, "staffMemberResourceType": { "enum": [ "staffMembers" @@ -1616,7 +2123,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1634,6 +2141,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1705,7 +2218,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1722,6 +2235,12 @@ "$ref": "#/components/schemas/supermarketDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1740,11 +2259,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarketResourceType" - } - ] + "$ref": "#/components/schemas/supermarketResourceType" }, "id": { "minLength": 1, @@ -1774,11 +2289,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarketResourceType" - } - ] + "$ref": "#/components/schemas/supermarketResourceType" }, "attributes": { "allOf": [ @@ -1798,53 +2309,48 @@ "additionalProperties": false }, "supermarketDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarketResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarketAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarketRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/supermarketAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/supermarketRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "supermarketPatchRequestDocument": { @@ -1889,7 +2395,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapiObject" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1907,6 +2413,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2040,7 +2552,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -2085,7 +2597,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, diff --git a/test/OpenApiClientTests/NamingConventions/KebabCase/GeneratedTypesTests.cs b/test/OpenApiClientTests/NamingConventions/KebabCase/GeneratedTypesTests.cs index f4884561fd..e0408281c0 100644 --- a/test/OpenApiClientTests/NamingConventions/KebabCase/GeneratedTypesTests.cs +++ b/test/OpenApiClientTests/NamingConventions/KebabCase/GeneratedTypesTests.cs @@ -1,77 +1,143 @@ using OpenApiClientTests.NamingConventions.KebabCase.GeneratedCode; using Xunit; +using GeneratedClient = OpenApiClientTests.NamingConventions.KebabCase.GeneratedCode.KebabCaseClient; namespace OpenApiClientTests.NamingConventions.KebabCase; public sealed class GeneratedTypesTests { [Fact] - public void Generated_code_is_named_as_expected() + public void Generated_endpoint_methods_are_named_as_expected() { - _ = nameof(KebabCaseClient.GetSupermarketCollectionAsync); - _ = nameof(KebabCaseClient.GetSupermarketCollectionAsync); - _ = nameof(KebabCaseClient.PostSupermarketAsync); - _ = nameof(KebabCaseClient.GetSupermarketAsync); - _ = nameof(KebabCaseClient.GetSupermarketAsync); - _ = nameof(KebabCaseClient.PatchSupermarketAsync); - _ = nameof(KebabCaseClient.DeleteSupermarketAsync); - _ = nameof(KebabCaseClient.GetSupermarketBackupStoreManagerAsync); - _ = nameof(KebabCaseClient.GetSupermarketBackupStoreManagerAsync); - _ = nameof(KebabCaseClient.GetSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(KebabCaseClient.GetSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(KebabCaseClient.PatchSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(KebabCaseClient.GetSupermarketCashiersAsync); - _ = nameof(KebabCaseClient.GetSupermarketCashiersAsync); - _ = nameof(KebabCaseClient.GetSupermarketCashiersRelationshipAsync); - _ = nameof(KebabCaseClient.GetSupermarketCashiersRelationshipAsync); - _ = nameof(KebabCaseClient.PostSupermarketCashiersRelationshipAsync); - _ = nameof(KebabCaseClient.PatchSupermarketCashiersRelationshipAsync); - _ = nameof(KebabCaseClient.DeleteSupermarketCashiersRelationshipAsync); - _ = nameof(KebabCaseClient.GetSupermarketStoreManagerAsync); - _ = nameof(KebabCaseClient.GetSupermarketStoreManagerAsync); - _ = nameof(KebabCaseClient.GetSupermarketStoreManagerRelationshipAsync); - _ = nameof(KebabCaseClient.GetSupermarketStoreManagerRelationshipAsync); - _ = nameof(KebabCaseClient.PatchSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketCollectionAsync); + _ = nameof(GeneratedClient.HeadSupermarketCollectionAsync); + _ = nameof(GeneratedClient.PostSupermarketAsync); + _ = nameof(GeneratedClient.GetSupermarketAsync); + _ = nameof(GeneratedClient.HeadSupermarketAsync); + _ = nameof(GeneratedClient.PatchSupermarketAsync); + _ = nameof(GeneratedClient.DeleteSupermarketAsync); + _ = nameof(GeneratedClient.GetSupermarketBackupStoreManagerAsync); + _ = nameof(GeneratedClient.HeadSupermarketBackupStoreManagerAsync); + _ = nameof(GeneratedClient.GetSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketCashiersAsync); + _ = nameof(GeneratedClient.HeadSupermarketCashiersAsync); + _ = nameof(GeneratedClient.GetSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.PostSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.DeleteSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketStoreManagerAsync); + _ = nameof(GeneratedClient.HeadSupermarketStoreManagerAsync); + _ = nameof(GeneratedClient.GetSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketStoreManagerRelationshipAsync); + + _ = nameof(GeneratedClient.GetStaffMemberCollectionAsync); + _ = nameof(GeneratedClient.HeadStaffMemberCollectionAsync); + _ = nameof(GeneratedClient.PostStaffMemberAsync); + _ = nameof(GeneratedClient.GetStaffMemberAsync); + _ = nameof(GeneratedClient.HeadStaffMemberAsync); + _ = nameof(GeneratedClient.PatchStaffMemberAsync); + _ = nameof(GeneratedClient.DeleteStaffMemberAsync); + } + [Fact] + public void Generated_top_level_document_types_are_named_as_expected() + { _ = nameof(SupermarketCollectionResponseDocument); + _ = nameof(SupermarketPostRequestDocument); + _ = nameof(SupermarketPrimaryResponseDocument); + _ = nameof(SupermarketPatchRequestDocument); + + _ = nameof(StaffMemberCollectionResponseDocument); + _ = nameof(StaffMemberPostRequestDocument); + _ = nameof(StaffMemberPrimaryResponseDocument); + _ = nameof(StaffMemberPatchRequestDocument); + _ = nameof(StaffMemberIdentifierCollectionResponseDocument); + _ = nameof(StaffMemberIdentifierResponseDocument); + _ = nameof(StaffMemberSecondaryResponseDocument); + _ = nameof(NullableStaffMemberSecondaryResponseDocument); + _ = nameof(NullableStaffMemberIdentifierResponseDocument); + } + + [Fact] + public void Generated_link_types_are_named_as_expected() + { _ = nameof(LinksInResourceCollectionDocument); - _ = nameof(JsonapiObject); - _ = nameof(SupermarketDataInResponse); - _ = nameof(SupermarketResourceType.Supermarkets); + _ = nameof(LinksInResourceDocument); + _ = nameof(LinksInResourceIdentifierCollectionDocument); + _ = nameof(LinksInResourceIdentifierDocument); + _ = nameof(LinksInResourceData); + _ = nameof(LinksInRelationship); + } + + [Fact] + public void Generated_resource_field_types_are_named_as_expected() + { + _ = nameof(SupermarketAttributesInPostRequest.NameOfCity); + _ = nameof(SupermarketAttributesInPostRequest.Kind); + _ = nameof(SupermarketAttributesInPatchRequest.NameOfCity); + _ = nameof(SupermarketAttributesInPatchRequest.Kind); _ = nameof(SupermarketAttributesInResponse.NameOfCity); + _ = nameof(SupermarketAttributesInResponse.Kind); + _ = nameof(SupermarketRelationshipsInPostRequest.StoreManager); + _ = nameof(SupermarketRelationshipsInPostRequest.BackupStoreManager); + _ = nameof(SupermarketRelationshipsInPostRequest.Cashiers); + _ = nameof(SupermarketRelationshipsInPatchRequest.StoreManager); + _ = nameof(SupermarketRelationshipsInPatchRequest.BackupStoreManager); + _ = nameof(SupermarketRelationshipsInPatchRequest.Cashiers); _ = nameof(SupermarketRelationshipsInResponse.StoreManager); _ = nameof(SupermarketRelationshipsInResponse.BackupStoreManager); - _ = nameof(LinksInResourceObject); + _ = nameof(SupermarketRelationshipsInResponse.Cashiers); _ = nameof(SupermarketType); - _ = nameof(KebabCaseClient.GetSupermarketAsync); + + _ = nameof(StaffMemberAttributesInPostRequest.Name); + _ = nameof(StaffMemberAttributesInPostRequest.Age); + _ = nameof(StaffMemberAttributesInPatchRequest.Name); + _ = nameof(StaffMemberAttributesInPatchRequest.Age); + _ = nameof(StaffMemberAttributesInResponse.Name); + _ = nameof(StaffMemberAttributesInResponse.Age); + } + + [Fact] + public void Generated_relationship_container_types_are_named_as_expected() + { + _ = nameof(ToOneStaffMemberInRequest); _ = nameof(ToOneStaffMemberInResponse); + _ = nameof(NullableToOneStaffMemberInRequest); _ = nameof(NullableToOneStaffMemberInResponse); + _ = nameof(ToManyStaffMemberInRequest); _ = nameof(ToManyStaffMemberInResponse); - _ = nameof(LinksInRelationshipObject); - _ = nameof(StaffMemberIdentifier); + } + + [Fact] + public void Generated_resource_type_enums_are_named_as_expected() + { + _ = nameof(SupermarketResourceType.Supermarkets); _ = nameof(StaffMemberResourceType.StaffMembers); - _ = nameof(SupermarketPrimaryResponseDocument); - _ = nameof(LinksInResourceDocument); - _ = nameof(StaffMemberSecondaryResponseDocument); - _ = nameof(StaffMemberDataInResponse); - _ = nameof(StaffMemberAttributesInResponse); - _ = nameof(NullableStaffMemberSecondaryResponseDocument); - _ = nameof(StaffMemberCollectionResponseDocument); - _ = nameof(StaffMemberIdentifierResponseDocument); - _ = nameof(LinksInResourceIdentifierDocument); - _ = nameof(NullableStaffMemberIdentifierResponseDocument); - _ = nameof(StaffMemberIdentifierCollectionResponseDocument); - _ = nameof(LinksInResourceIdentifierCollectionDocument); - _ = nameof(SupermarketPostRequestDocument); + } + + [Fact] + public void Generated_data_types_are_named_as_expected() + { _ = nameof(SupermarketDataInPostRequest); - _ = nameof(SupermarketAttributesInPostRequest); - _ = nameof(SupermarketRelationshipsInPostRequest); - _ = nameof(ToOneStaffMemberInRequest); - _ = nameof(NullableToOneStaffMemberInRequest); - _ = nameof(ToManyStaffMemberInRequest); - _ = nameof(SupermarketPatchRequestDocument); _ = nameof(SupermarketDataInPatchRequest); - _ = nameof(SupermarketAttributesInPatchRequest); - _ = nameof(SupermarketRelationshipsInPatchRequest); + _ = nameof(SupermarketDataInResponse); + + _ = nameof(StaffMemberDataInPostRequest); + _ = nameof(StaffMemberDataInPatchRequest); + _ = nameof(StaffMemberDataInResponse); + + _ = nameof(DataInResponse); + + _ = nameof(StaffMemberIdentifier); + } + + [Fact] + public void Generated_code_is_named_as_expected() + { + _ = nameof(Jsonapi); } } diff --git a/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json index f143415719..ff999f4dd7 100644 --- a/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/KebabCase/swagger.g.json @@ -4,7 +4,337 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { + "/staff-members": { + "get": { + "tags": [ + "staff-members" + ], + "summary": "Retrieves a collection of staff-members.", + "operationId": "get-staff-member-collection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found staff-members, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staff-member-collection-response-document" + } + } + } + }, + "400": { + "description": "The query string is invalid." + } + } + }, + "head": { + "tags": [ + "staff-members" + ], + "summary": "Retrieves a collection of staff-members without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "head-staff-member-collection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + } + } + }, + "post": { + "tags": [ + "staff-members" + ], + "summary": "Creates a new staff-member.", + "operationId": "post-staff-member", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the staff-member to create.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-post-request-document" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The staff-member was successfully created, which resulted in additional changes. The newly created staff-member is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staff-member-primary-response-document" + } + } + } + }, + "204": { + "description": "The staff-member was successfully created, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "403": { + "description": "Client-generated IDs cannot be used at this endpoint." + }, + "409": { + "description": "A resource type in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + } + }, + "/staff-members/{id}": { + "get": { + "tags": [ + "staff-members" + ], + "summary": "Retrieves an individual staff-member by its identifier.", + "operationId": "get-staff-member", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staff-member to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found staff-member.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staff-member-primary-response-document" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The staff-member does not exist." + } + } + }, + "head": { + "tags": [ + "staff-members" + ], + "summary": "Retrieves an individual staff-member by its identifier without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "head-staff-member", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staff-member to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The staff-member does not exist." + } + } + }, + "patch": { + "tags": [ + "staff-members" + ], + "summary": "Updates an existing staff-member.", + "operationId": "patch-staff-member", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staff-member to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the staff-member to update. Omitted fields are left unchanged.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-patch-request-document" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "The staff-member was successfully updated, which resulted in additional changes. The updated staff-member is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/staff-member-primary-response-document" + } + } + } + }, + "204": { + "description": "The staff-member was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "404": { + "description": "The staff-member or a related resource does not exist." + }, + "409": { + "description": "A resource type or identifier in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + }, + "delete": { + "tags": [ + "staff-members" + ], + "summary": "Deletes an existing staff-member by its identifier.", + "operationId": "delete-staff-member", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the staff-member to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The staff-member was successfully deleted." + }, + "404": { + "description": "The staff-member does not exist." + } + } + } + }, "/supermarkets": { "get": { "tags": [ @@ -23,7 +353,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +391,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +421,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +495,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +545,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +587,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +687,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +737,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +774,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +824,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -591,7 +921,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -641,7 +971,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -678,14 +1008,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -728,14 +1058,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -917,7 +1247,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -967,7 +1297,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1004,14 +1334,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1054,14 +1384,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1127,7 +1457,33 @@ }, "components": { "schemas": { - "jsonapi-object": { + "data-in-response": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "staff-members": "#/components/schemas/staff-member-data-in-response", + "supermarkets": "#/components/schemas/supermarket-data-in-response" + } + }, + "x-abstract": true + }, + "jsonapi": { "type": "object", "properties": { "version": { @@ -1155,7 +1511,7 @@ }, "additionalProperties": false }, - "links-in-relationship-object": { + "links-in-relationship": { "required": [ "related", "self" @@ -1201,6 +1557,19 @@ }, "additionalProperties": false }, + "links-in-resource-data": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "links-in-resource-document": { "required": [ "self" @@ -1271,19 +1640,6 @@ }, "additionalProperties": false }, - "links-in-resource-object": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullable-staff-member-identifier-response-document": { "required": [ "data", @@ -1294,7 +1650,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1333,7 +1689,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1352,6 +1708,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1388,7 +1750,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -1410,6 +1772,35 @@ }, "additionalProperties": false }, + "staff-member-attributes-in-patch-request": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "staff-member-attributes-in-post-request": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, "staff-member-attributes-in-response": { "type": "object", "properties": { @@ -1433,7 +1824,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1450,6 +1841,12 @@ "$ref": "#/components/schemas/staff-member-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1460,20 +1857,15 @@ }, "additionalProperties": false }, - "staff-member-data-in-response": { + "staff-member-data-in-patch-request": { "required": [ "id", - "links", "type" ], "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/staff-member-resource-type" - } - ] + "$ref": "#/components/schemas/staff-member-resource-type" }, "id": { "minLength": 1, @@ -1482,25 +1874,68 @@ "attributes": { "allOf": [ { - "$ref": "#/components/schemas/staff-member-attributes-in-response" + "$ref": "#/components/schemas/staff-member-attributes-in-patch-request" } ] + } + }, + "additionalProperties": false + }, + "staff-member-data-in-post-request": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/staff-member-resource-type" }, - "links": { + "attributes": { "allOf": [ { - "$ref": "#/components/schemas/links-in-resource-object" + "$ref": "#/components/schemas/staff-member-attributes-in-post-request" } ] + } + }, + "additionalProperties": false + }, + "staff-member-data-in-response": { + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-attributes-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "staff-member-identifier": { @@ -1511,11 +1946,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/staff-member-resource-type" - } - ] + "$ref": "#/components/schemas/staff-member-resource-type" }, "id": { "minLength": 1, @@ -1534,7 +1965,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1571,7 +2002,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1599,6 +2030,82 @@ }, "additionalProperties": false }, + "staff-member-patch-request-document": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-data-in-patch-request" + } + ] + } + }, + "additionalProperties": false + }, + "staff-member-post-request-document": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-data-in-post-request" + } + ] + } + }, + "additionalProperties": false + }, + "staff-member-primary-response-document": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-document" + } + ] + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/staff-member-data-in-response" + } + ] + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, "staff-member-resource-type": { "enum": [ "staff-members" @@ -1616,7 +2123,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1634,6 +2141,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1705,7 +2218,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1722,6 +2235,12 @@ "$ref": "#/components/schemas/supermarket-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1740,11 +2259,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarket-resource-type" - } - ] + "$ref": "#/components/schemas/supermarket-resource-type" }, "id": { "minLength": 1, @@ -1774,11 +2289,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarket-resource-type" - } - ] + "$ref": "#/components/schemas/supermarket-resource-type" }, "attributes": { "allOf": [ @@ -1798,53 +2309,48 @@ "additionalProperties": false }, "supermarket-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarket-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarket-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/supermarket-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/supermarket-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "supermarket-patch-request-document": { @@ -1889,7 +2395,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -1907,6 +2413,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2040,7 +2552,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -2085,7 +2597,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, diff --git a/test/OpenApiClientTests/NamingConventions/PascalCase/GeneratedTypesTests.cs b/test/OpenApiClientTests/NamingConventions/PascalCase/GeneratedTypesTests.cs index 9ec0325e58..4c33a27eec 100644 --- a/test/OpenApiClientTests/NamingConventions/PascalCase/GeneratedTypesTests.cs +++ b/test/OpenApiClientTests/NamingConventions/PascalCase/GeneratedTypesTests.cs @@ -1,77 +1,143 @@ using OpenApiClientTests.NamingConventions.PascalCase.GeneratedCode; using Xunit; +using GeneratedClient = OpenApiClientTests.NamingConventions.PascalCase.GeneratedCode.PascalCaseClient; namespace OpenApiClientTests.NamingConventions.PascalCase; public sealed class GeneratedTypesTests { [Fact] - public void Generated_code_is_named_as_expected() + public void Generated_endpoint_methods_are_named_as_expected() { - _ = nameof(PascalCaseClient.GetSupermarketCollectionAsync); - _ = nameof(PascalCaseClient.GetSupermarketCollectionAsync); - _ = nameof(PascalCaseClient.PostSupermarketAsync); - _ = nameof(PascalCaseClient.GetSupermarketAsync); - _ = nameof(PascalCaseClient.GetSupermarketAsync); - _ = nameof(PascalCaseClient.PatchSupermarketAsync); - _ = nameof(PascalCaseClient.DeleteSupermarketAsync); - _ = nameof(PascalCaseClient.GetSupermarketBackupStoreManagerAsync); - _ = nameof(PascalCaseClient.GetSupermarketBackupStoreManagerAsync); - _ = nameof(PascalCaseClient.GetSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(PascalCaseClient.GetSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(PascalCaseClient.PatchSupermarketBackupStoreManagerRelationshipAsync); - _ = nameof(PascalCaseClient.GetSupermarketCashiersAsync); - _ = nameof(PascalCaseClient.GetSupermarketCashiersAsync); - _ = nameof(PascalCaseClient.GetSupermarketCashiersRelationshipAsync); - _ = nameof(PascalCaseClient.GetSupermarketCashiersRelationshipAsync); - _ = nameof(PascalCaseClient.PostSupermarketCashiersRelationshipAsync); - _ = nameof(PascalCaseClient.PatchSupermarketCashiersRelationshipAsync); - _ = nameof(PascalCaseClient.DeleteSupermarketCashiersRelationshipAsync); - _ = nameof(PascalCaseClient.GetSupermarketStoreManagerAsync); - _ = nameof(PascalCaseClient.GetSupermarketStoreManagerAsync); - _ = nameof(PascalCaseClient.GetSupermarketStoreManagerRelationshipAsync); - _ = nameof(PascalCaseClient.GetSupermarketStoreManagerRelationshipAsync); - _ = nameof(PascalCaseClient.PatchSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketCollectionAsync); + _ = nameof(GeneratedClient.HeadSupermarketCollectionAsync); + _ = nameof(GeneratedClient.PostSupermarketAsync); + _ = nameof(GeneratedClient.GetSupermarketAsync); + _ = nameof(GeneratedClient.HeadSupermarketAsync); + _ = nameof(GeneratedClient.PatchSupermarketAsync); + _ = nameof(GeneratedClient.DeleteSupermarketAsync); + _ = nameof(GeneratedClient.GetSupermarketBackupStoreManagerAsync); + _ = nameof(GeneratedClient.HeadSupermarketBackupStoreManagerAsync); + _ = nameof(GeneratedClient.GetSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketBackupStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketCashiersAsync); + _ = nameof(GeneratedClient.HeadSupermarketCashiersAsync); + _ = nameof(GeneratedClient.GetSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.PostSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.DeleteSupermarketCashiersRelationshipAsync); + _ = nameof(GeneratedClient.GetSupermarketStoreManagerAsync); + _ = nameof(GeneratedClient.HeadSupermarketStoreManagerAsync); + _ = nameof(GeneratedClient.GetSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.HeadSupermarketStoreManagerRelationshipAsync); + _ = nameof(GeneratedClient.PatchSupermarketStoreManagerRelationshipAsync); + + _ = nameof(GeneratedClient.GetStaffMemberCollectionAsync); + _ = nameof(GeneratedClient.HeadStaffMemberCollectionAsync); + _ = nameof(GeneratedClient.PostStaffMemberAsync); + _ = nameof(GeneratedClient.GetStaffMemberAsync); + _ = nameof(GeneratedClient.HeadStaffMemberAsync); + _ = nameof(GeneratedClient.PatchStaffMemberAsync); + _ = nameof(GeneratedClient.DeleteStaffMemberAsync); + } + [Fact] + public void Generated_top_level_document_types_are_named_as_expected() + { _ = nameof(SupermarketCollectionResponseDocument); + _ = nameof(SupermarketPostRequestDocument); + _ = nameof(SupermarketPrimaryResponseDocument); + _ = nameof(SupermarketPatchRequestDocument); + + _ = nameof(StaffMemberCollectionResponseDocument); + _ = nameof(StaffMemberPostRequestDocument); + _ = nameof(StaffMemberPrimaryResponseDocument); + _ = nameof(StaffMemberPatchRequestDocument); + _ = nameof(StaffMemberIdentifierCollectionResponseDocument); + _ = nameof(StaffMemberIdentifierResponseDocument); + _ = nameof(StaffMemberSecondaryResponseDocument); + _ = nameof(NullableStaffMemberSecondaryResponseDocument); + _ = nameof(NullableStaffMemberIdentifierResponseDocument); + } + + [Fact] + public void Generated_link_types_are_named_as_expected() + { _ = nameof(LinksInResourceCollectionDocument); - _ = nameof(JsonapiObject); - _ = nameof(SupermarketDataInResponse); - _ = nameof(SupermarketResourceType.Supermarkets); + _ = nameof(LinksInResourceDocument); + _ = nameof(LinksInResourceIdentifierCollectionDocument); + _ = nameof(LinksInResourceIdentifierDocument); + _ = nameof(LinksInResourceData); + _ = nameof(LinksInRelationship); + } + + [Fact] + public void Generated_resource_field_types_are_named_as_expected() + { + _ = nameof(SupermarketAttributesInPostRequest.NameOfCity); + _ = nameof(SupermarketAttributesInPostRequest.Kind); + _ = nameof(SupermarketAttributesInPatchRequest.NameOfCity); + _ = nameof(SupermarketAttributesInPatchRequest.Kind); _ = nameof(SupermarketAttributesInResponse.NameOfCity); + _ = nameof(SupermarketAttributesInResponse.Kind); + _ = nameof(SupermarketRelationshipsInPostRequest.StoreManager); + _ = nameof(SupermarketRelationshipsInPostRequest.BackupStoreManager); + _ = nameof(SupermarketRelationshipsInPostRequest.Cashiers); + _ = nameof(SupermarketRelationshipsInPatchRequest.StoreManager); + _ = nameof(SupermarketRelationshipsInPatchRequest.BackupStoreManager); + _ = nameof(SupermarketRelationshipsInPatchRequest.Cashiers); _ = nameof(SupermarketRelationshipsInResponse.StoreManager); _ = nameof(SupermarketRelationshipsInResponse.BackupStoreManager); - _ = nameof(LinksInResourceObject); + _ = nameof(SupermarketRelationshipsInResponse.Cashiers); _ = nameof(SupermarketType); - _ = nameof(PascalCaseClient.GetSupermarketAsync); + + _ = nameof(StaffMemberAttributesInPostRequest.Name); + _ = nameof(StaffMemberAttributesInPostRequest.Age); + _ = nameof(StaffMemberAttributesInPatchRequest.Name); + _ = nameof(StaffMemberAttributesInPatchRequest.Age); + _ = nameof(StaffMemberAttributesInResponse.Name); + _ = nameof(StaffMemberAttributesInResponse.Age); + } + + [Fact] + public void Generated_relationship_container_types_are_named_as_expected() + { + _ = nameof(ToOneStaffMemberInRequest); _ = nameof(ToOneStaffMemberInResponse); + _ = nameof(NullableToOneStaffMemberInRequest); _ = nameof(NullableToOneStaffMemberInResponse); + _ = nameof(ToManyStaffMemberInRequest); _ = nameof(ToManyStaffMemberInResponse); - _ = nameof(LinksInRelationshipObject); - _ = nameof(StaffMemberIdentifier); + } + + [Fact] + public void Generated_resource_type_enums_are_named_as_expected() + { + _ = nameof(SupermarketResourceType.Supermarkets); _ = nameof(StaffMemberResourceType.StaffMembers); - _ = nameof(SupermarketPrimaryResponseDocument); - _ = nameof(LinksInResourceDocument); - _ = nameof(StaffMemberSecondaryResponseDocument); - _ = nameof(StaffMemberDataInResponse); - _ = nameof(StaffMemberAttributesInResponse); - _ = nameof(NullableStaffMemberSecondaryResponseDocument); - _ = nameof(StaffMemberCollectionResponseDocument); - _ = nameof(StaffMemberIdentifierResponseDocument); - _ = nameof(LinksInResourceIdentifierDocument); - _ = nameof(NullableStaffMemberIdentifierResponseDocument); - _ = nameof(StaffMemberIdentifierCollectionResponseDocument); - _ = nameof(LinksInResourceIdentifierCollectionDocument); - _ = nameof(SupermarketPostRequestDocument); + } + + [Fact] + public void Generated_data_types_are_named_as_expected() + { _ = nameof(SupermarketDataInPostRequest); - _ = nameof(SupermarketAttributesInPostRequest); - _ = nameof(SupermarketRelationshipsInPostRequest); - _ = nameof(ToOneStaffMemberInRequest); - _ = nameof(NullableToOneStaffMemberInRequest); - _ = nameof(ToManyStaffMemberInRequest); - _ = nameof(SupermarketPatchRequestDocument); _ = nameof(SupermarketDataInPatchRequest); - _ = nameof(SupermarketAttributesInPatchRequest); - _ = nameof(SupermarketRelationshipsInPatchRequest); + _ = nameof(SupermarketDataInResponse); + + _ = nameof(StaffMemberDataInPostRequest); + _ = nameof(StaffMemberDataInPatchRequest); + _ = nameof(StaffMemberDataInResponse); + + _ = nameof(DataInResponse); + + _ = nameof(StaffMemberIdentifier); + } + + [Fact] + public void Generated_code_is_named_as_expected() + { + _ = nameof(Jsonapi); } } diff --git a/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json b/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json index 436bfe3c39..5e88a84c38 100644 --- a/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json +++ b/test/OpenApiClientTests/NamingConventions/PascalCase/swagger.g.json @@ -4,7 +4,337 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { + "/StaffMembers": { + "get": { + "tags": [ + "StaffMembers" + ], + "summary": "Retrieves a collection of StaffMembers.", + "operationId": "GetStaffMemberCollection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found StaffMembers, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/StaffMemberCollectionResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + } + } + }, + "head": { + "tags": [ + "StaffMembers" + ], + "summary": "Retrieves a collection of StaffMembers without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "HeadStaffMemberCollection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + } + } + }, + "post": { + "tags": [ + "StaffMembers" + ], + "summary": "Creates a new StaffMember.", + "operationId": "PostStaffMember", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the StaffMember to create.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberPostRequestDocument" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The StaffMember was successfully created, which resulted in additional changes. The newly created StaffMember is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/StaffMemberPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "The StaffMember was successfully created, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "403": { + "description": "Client-generated IDs cannot be used at this endpoint." + }, + "409": { + "description": "A resource type in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + } + }, + "/StaffMembers/{id}": { + "get": { + "tags": [ + "StaffMembers" + ], + "summary": "Retrieves an individual StaffMember by its identifier.", + "operationId": "GetStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the StaffMember to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found StaffMember.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/StaffMemberPrimaryResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The StaffMember does not exist." + } + } + }, + "head": { + "tags": [ + "StaffMembers" + ], + "summary": "Retrieves an individual StaffMember by its identifier without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "HeadStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the StaffMember to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The StaffMember does not exist." + } + } + }, + "patch": { + "tags": [ + "StaffMembers" + ], + "summary": "Updates an existing StaffMember.", + "operationId": "PatchStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the StaffMember to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the StaffMember to update. Omitted fields are left unchanged.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberPatchRequestDocument" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "The StaffMember was successfully updated, which resulted in additional changes. The updated StaffMember is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/StaffMemberPrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "The StaffMember was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "404": { + "description": "The StaffMember or a related resource does not exist." + }, + "409": { + "description": "A resource type or identifier in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + }, + "delete": { + "tags": [ + "StaffMembers" + ], + "summary": "Deletes an existing StaffMember by its identifier.", + "operationId": "DeleteStaffMember", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the StaffMember to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The StaffMember was successfully deleted." + }, + "404": { + "description": "The StaffMember does not exist." + } + } + } + }, "/Supermarkets": { "get": { "tags": [ @@ -23,7 +353,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +391,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +421,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +495,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +545,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +587,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +687,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +737,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +774,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +824,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -591,7 +921,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -641,7 +971,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -678,14 +1008,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -728,14 +1058,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -917,7 +1247,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -967,7 +1297,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1004,14 +1334,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1054,14 +1384,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1127,7 +1457,33 @@ }, "components": { "schemas": { - "JsonapiObject": { + "DataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "StaffMembers": "#/components/schemas/StaffMemberDataInResponse", + "Supermarkets": "#/components/schemas/SupermarketDataInResponse" + } + }, + "x-abstract": true + }, + "Jsonapi": { "type": "object", "properties": { "version": { @@ -1155,7 +1511,7 @@ }, "additionalProperties": false }, - "LinksInRelationshipObject": { + "LinksInRelationship": { "required": [ "related", "self" @@ -1201,6 +1557,19 @@ }, "additionalProperties": false }, + "LinksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "LinksInResourceDocument": { "required": [ "self" @@ -1271,19 +1640,6 @@ }, "additionalProperties": false }, - "LinksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "NullableStaffMemberIdentifierResponseDocument": { "required": [ "data", @@ -1294,7 +1650,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1333,7 +1689,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1352,6 +1708,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1388,7 +1750,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/LinksInRelationshipObject" + "$ref": "#/components/schemas/LinksInRelationship" } ] }, @@ -1410,6 +1772,35 @@ }, "additionalProperties": false }, + "StaffMemberAttributesInPatchRequest": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Age": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "StaffMemberAttributesInPostRequest": { + "required": [ + "Name" + ], + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Age": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, "StaffMemberAttributesInResponse": { "type": "object", "properties": { @@ -1433,7 +1824,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1450,6 +1841,12 @@ "$ref": "#/components/schemas/StaffMemberDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1460,20 +1857,15 @@ }, "additionalProperties": false }, - "StaffMemberDataInResponse": { + "StaffMemberDataInPatchRequest": { "required": [ "id", - "links", "type" ], "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/StaffMemberResourceType" - } - ] + "$ref": "#/components/schemas/StaffMemberResourceType" }, "id": { "minLength": 1, @@ -1482,25 +1874,68 @@ "attributes": { "allOf": [ { - "$ref": "#/components/schemas/StaffMemberAttributesInResponse" + "$ref": "#/components/schemas/StaffMemberAttributesInPatchRequest" } ] + } + }, + "additionalProperties": false + }, + "StaffMemberDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/StaffMemberResourceType" }, - "links": { + "attributes": { "allOf": [ { - "$ref": "#/components/schemas/LinksInResourceObject" + "$ref": "#/components/schemas/StaffMemberAttributesInPostRequest" } ] + } + }, + "additionalProperties": false + }, + "StaffMemberDataInResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/DataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberAttributesInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "StaffMemberIdentifier": { @@ -1511,11 +1946,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/StaffMemberResourceType" - } - ] + "$ref": "#/components/schemas/StaffMemberResourceType" }, "id": { "minLength": 1, @@ -1534,7 +1965,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1571,7 +2002,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1599,6 +2030,82 @@ }, "additionalProperties": false }, + "StaffMemberPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberDataInPatchRequest" + } + ] + } + }, + "additionalProperties": false + }, + "StaffMemberPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberDataInPostRequest" + } + ] + } + }, + "additionalProperties": false + }, + "StaffMemberPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/Jsonapi" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceDocument" + } + ] + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/StaffMemberDataInResponse" + } + ] + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, "StaffMemberResourceType": { "enum": [ "StaffMembers" @@ -1616,7 +2123,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1634,6 +2141,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1705,7 +2218,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1722,6 +2235,12 @@ "$ref": "#/components/schemas/SupermarketDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1740,11 +2259,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/SupermarketResourceType" - } - ] + "$ref": "#/components/schemas/SupermarketResourceType" }, "id": { "minLength": 1, @@ -1774,11 +2289,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/SupermarketResourceType" - } - ] + "$ref": "#/components/schemas/SupermarketResourceType" }, "attributes": { "allOf": [ @@ -1798,53 +2309,48 @@ "additionalProperties": false }, "SupermarketDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/SupermarketResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/SupermarketAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/SupermarketRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/LinksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/DataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/SupermarketRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/LinksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "SupermarketPatchRequestDocument": { @@ -1889,7 +2395,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/JsonapiObject" + "$ref": "#/components/schemas/Jsonapi" } ] }, @@ -1907,6 +2413,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2040,7 +2552,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/LinksInRelationshipObject" + "$ref": "#/components/schemas/LinksInRelationship" } ] }, @@ -2085,7 +2597,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/LinksInRelationshipObject" + "$ref": "#/components/schemas/LinksInRelationship" } ] }, diff --git a/test/OpenApiClientTests/OpenApiClientTests.csproj b/test/OpenApiClientTests/OpenApiClientTests.csproj index cc08d79054..e849696f0c 100644 --- a/test/OpenApiClientTests/OpenApiClientTests.csproj +++ b/test/OpenApiClientTests/OpenApiClientTests.csproj @@ -28,56 +28,56 @@ OpenApiClient OpenApiClient.cs NSwagCSharp - /UseBaseUrl:false /GenerateClientInterfaces:true /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions + /GenerateClientInterfaces:true /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions OpenApiClientTests.NamingConventions.KebabCase.GeneratedCode KebabCaseClient KebabCaseClient.cs NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions OpenApiClientTests.NamingConventions.CamelCase.GeneratedCode CamelCaseClient CamelCaseClient.cs NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions OpenApiClientTests.NamingConventions.PascalCase.GeneratedCode PascalCaseClient PascalCaseClient.cs NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions NrtOffMsvOffClient NrtOffMsvOffClient.cs NSwagCSharp OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOff.GeneratedCode - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false NrtOffMsvOnClient NrtOffMsvOnClient.cs NSwagCSharp OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOff.ModelStateValidationOn.GeneratedCode - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:false NrtOnMsvOffClient NrtOnMsvOffClient.cs NSwagCSharp OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOff.GeneratedCode - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true NrtOnMsvOnClient NrtOnMsvOnClient.cs NSwagCSharp OpenApiClientTests.ResourceFieldValidation.NullableReferenceTypesOn.ModelStateValidationOn.GeneratedCode - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json index 20b6d88eea..fd3f337728 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/swagger.g.json @@ -4,6 +4,11 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { "/resources": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -683,7 +688,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -733,7 +738,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -770,14 +775,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -820,14 +825,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -917,7 +922,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -967,7 +972,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1004,14 +1009,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1054,14 +1059,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1243,7 +1248,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1293,7 +1298,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1330,14 +1335,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1380,14 +1385,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1453,6 +1458,32 @@ }, "components": { "schemas": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "empties": "#/components/schemas/emptyDataInResponse", + "resources": "#/components/schemas/resourceDataInResponse" + } + }, + "x-abstract": true + }, "emptyCollectionResponseDocument": { "required": [ "data", @@ -1473,6 +1504,12 @@ "$ref": "#/components/schemas/emptyDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1484,39 +1521,34 @@ "additionalProperties": false }, "emptyDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "emptyIdentifier": { @@ -1527,11 +1559,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] + "$ref": "#/components/schemas/emptyResourceType" }, "id": { "minLength": 1, @@ -1577,7 +1605,7 @@ "type": "string", "additionalProperties": false }, - "linksInRelationshipObject": { + "linksInRelationship": { "required": [ "related", "self" @@ -1623,6 +1651,19 @@ }, "additionalProperties": false }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceDocument": { "required": [ "self" @@ -1693,19 +1734,6 @@ }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullableEmptyIdentifierResponseDocument": { "required": [ "data", @@ -1760,6 +1788,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1796,7 +1830,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -1942,6 +1976,12 @@ "$ref": "#/components/schemas/resourceDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1960,11 +2000,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "id": { "minLength": 1, @@ -1994,11 +2030,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "attributes": { "allOf": [ @@ -2018,53 +2050,48 @@ "additionalProperties": false }, "resourceDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "resourcePatchRequestDocument": { @@ -2120,6 +2147,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2267,7 +2300,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json index 0d276409e9..c24cae3373 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/swagger.g.json @@ -4,6 +4,11 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { "/resources": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -683,7 +688,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -733,7 +738,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -770,14 +775,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -820,14 +825,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -917,7 +922,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -967,7 +972,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1004,14 +1009,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1054,14 +1059,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1243,7 +1248,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1293,7 +1298,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1330,14 +1335,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1380,14 +1385,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1453,6 +1458,32 @@ }, "components": { "schemas": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "empties": "#/components/schemas/emptyDataInResponse", + "resources": "#/components/schemas/resourceDataInResponse" + } + }, + "x-abstract": true + }, "emptyCollectionResponseDocument": { "required": [ "data", @@ -1473,6 +1504,12 @@ "$ref": "#/components/schemas/emptyDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1484,39 +1521,34 @@ "additionalProperties": false }, "emptyDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "emptyIdentifier": { @@ -1527,11 +1559,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] + "$ref": "#/components/schemas/emptyResourceType" }, "id": { "minLength": 1, @@ -1629,6 +1657,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1639,7 +1673,7 @@ }, "additionalProperties": false }, - "linksInRelationshipObject": { + "linksInRelationship": { "required": [ "related", "self" @@ -1685,6 +1719,19 @@ }, "additionalProperties": false }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceDocument": { "required": [ "self" @@ -1755,19 +1802,6 @@ }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullableEmptyIdentifierResponseDocument": { "required": [ "data", @@ -1822,6 +1856,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1858,7 +1898,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -1997,6 +2037,12 @@ "$ref": "#/components/schemas/resourceDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2015,11 +2061,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "id": { "minLength": 1, @@ -2049,11 +2091,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "attributes": { "allOf": [ @@ -2073,53 +2111,48 @@ "additionalProperties": false }, "resourceDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "resourcePatchRequestDocument": { @@ -2175,6 +2208,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2321,7 +2360,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -2366,7 +2405,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/swagger.g.json index fc50f639a4..ee74823c55 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/swagger.g.json @@ -4,6 +4,11 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { "/resources": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -591,7 +596,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -641,7 +646,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -678,14 +683,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -728,14 +733,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -825,7 +830,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -875,7 +880,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -912,14 +917,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -962,14 +967,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1059,7 +1064,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1109,7 +1114,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1146,14 +1151,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1196,14 +1201,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1293,7 +1298,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1343,7 +1348,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1380,14 +1385,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1430,14 +1435,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1619,7 +1624,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1669,7 +1674,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1706,14 +1711,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1756,14 +1761,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1921,6 +1926,32 @@ }, "components": { "schemas": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "empties": "#/components/schemas/emptyDataInResponse", + "resources": "#/components/schemas/resourceDataInResponse" + } + }, + "x-abstract": true + }, "emptyCollectionResponseDocument": { "required": [ "data", @@ -1941,6 +1972,12 @@ "$ref": "#/components/schemas/emptyDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1952,39 +1989,34 @@ "additionalProperties": false }, "emptyDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "emptyIdentifier": { @@ -1995,11 +2027,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] + "$ref": "#/components/schemas/emptyResourceType" }, "id": { "minLength": 1, @@ -2097,6 +2125,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2107,7 +2141,7 @@ }, "additionalProperties": false }, - "linksInRelationshipObject": { + "linksInRelationship": { "required": [ "related", "self" @@ -2153,6 +2187,19 @@ }, "additionalProperties": false }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceDocument": { "required": [ "self" @@ -2223,19 +2270,6 @@ }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullableEmptyIdentifierResponseDocument": { "required": [ "data", @@ -2290,6 +2324,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2326,7 +2366,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -2494,6 +2534,12 @@ "$ref": "#/components/schemas/resourceDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2512,11 +2558,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "id": { "minLength": 1, @@ -2546,11 +2588,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "attributes": { "allOf": [ @@ -2570,53 +2608,48 @@ "additionalProperties": false }, "resourceDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "resourcePatchRequestDocument": { @@ -2672,6 +2705,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2862,7 +2901,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -2907,7 +2946,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, diff --git a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/swagger.g.json b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/swagger.g.json index 5d4bd9a6c7..f8b8d264f7 100644 --- a/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/swagger.g.json +++ b/test/OpenApiClientTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/swagger.g.json @@ -4,6 +4,11 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { "/resources": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -591,7 +596,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -641,7 +646,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -678,14 +683,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -728,14 +733,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -825,7 +830,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -875,7 +880,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -912,14 +917,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -962,14 +967,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1059,7 +1064,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1109,7 +1114,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1146,14 +1151,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1196,14 +1201,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1293,7 +1298,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1343,7 +1348,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1380,14 +1385,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1430,14 +1435,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1619,7 +1624,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1669,7 +1674,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1706,14 +1711,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1756,14 +1761,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1921,6 +1926,32 @@ }, "components": { "schemas": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "empties": "#/components/schemas/emptyDataInResponse", + "resources": "#/components/schemas/resourceDataInResponse" + } + }, + "x-abstract": true + }, "emptyCollectionResponseDocument": { "required": [ "data", @@ -1941,6 +1972,12 @@ "$ref": "#/components/schemas/emptyDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1952,39 +1989,34 @@ "additionalProperties": false }, "emptyDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "emptyIdentifier": { @@ -1995,11 +2027,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/emptyResourceType" - } - ] + "$ref": "#/components/schemas/emptyResourceType" }, "id": { "minLength": 1, @@ -2097,6 +2125,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2107,7 +2141,7 @@ }, "additionalProperties": false }, - "linksInRelationshipObject": { + "linksInRelationship": { "required": [ "related", "self" @@ -2153,6 +2187,19 @@ }, "additionalProperties": false }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "linksInResourceDocument": { "required": [ "self" @@ -2223,19 +2270,6 @@ }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullableEmptyIdentifierResponseDocument": { "required": [ "data", @@ -2290,6 +2324,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2326,7 +2366,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -2488,6 +2528,12 @@ "$ref": "#/components/schemas/resourceDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2506,11 +2552,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "id": { "minLength": 1, @@ -2540,11 +2582,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] + "$ref": "#/components/schemas/resourceResourceType" }, "attributes": { "allOf": [ @@ -2564,53 +2602,48 @@ "additionalProperties": false }, "resourceDataInResponse": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceResourceType" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/resourceRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/resourceRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "resourcePatchRequestDocument": { @@ -2666,6 +2699,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -2856,7 +2895,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -2901,7 +2940,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, diff --git a/test/OpenApiEndToEndTests/.editorconfig b/test/OpenApiEndToEndTests/.editorconfig new file mode 100644 index 0000000000..e2ec1cac44 --- /dev/null +++ b/test/OpenApiEndToEndTests/.editorconfig @@ -0,0 +1,3 @@ +# Workaround for incorrect nullability in NSwag generated clients. +[*Client.cs] +dotnet_diagnostic.CS8765.severity = none diff --git a/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj b/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj index b55395b6d6..d2fae2dc01 100644 --- a/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj +++ b/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj @@ -28,7 +28,7 @@ QueryStringsClient QueryStringsClient.cs NSwagCSharp - /UseBaseUrl:false /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions /GenerateNullableReferenceTypes:true diff --git a/test/OpenApiEndToEndTests/QueryStrings/IncludeTests.cs b/test/OpenApiEndToEndTests/QueryStrings/IncludeTests.cs new file mode 100644 index 0000000000..3cd1348d52 --- /dev/null +++ b/test/OpenApiEndToEndTests/QueryStrings/IncludeTests.cs @@ -0,0 +1,194 @@ +using FluentAssertions; +using OpenApiEndToEndTests.QueryStrings.GeneratedCode; +using OpenApiTests; +using OpenApiTests.QueryStrings; +using TestBuildingBlocks; +using Xunit; +using Xunit.Abstractions; + +namespace OpenApiEndToEndTests.QueryStrings; + +public sealed class IncludeTests : IClassFixture, QueryStringsDbContext>> +{ + private readonly IntegrationTestContext, QueryStringsDbContext> _testContext; + private readonly ITestOutputHelper _testOutputHelper; + private readonly QueryStringFakers _fakers = new(); + + public IncludeTests(IntegrationTestContext, QueryStringsDbContext> testContext, ITestOutputHelper testOutputHelper) + { + _testContext = testContext; + _testOutputHelper = testOutputHelper; + + testContext.UseController(); + testContext.UseController(); + } + + [Fact] + public async Task Can_include_in_primary_resources() + { + // Arrange + Node node = _fakers.Node.Generate(); + node.Values = _fakers.NameValuePair.Generate(2); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.Nodes.Add(node); + await dbContext.SaveChangesAsync(); + }); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new QueryStringsClient(httpClient, _testOutputHelper); + + var queryString = new Dictionary + { + ["include"] = "values.owner" + }; + + // Act + NodeCollectionResponseDocument response = await apiClient.GetNodeCollectionAsync(queryString); + + // Assert + response.Data.ShouldHaveCount(1); + response.Data.ElementAt(0).Id.Should().Be(node.StringId); + + response.Included.Should().HaveCount(2); + response.Included.Should().ContainSingle(include => include is NameValuePairDataInResponse && include.Id == node.Values.ElementAt(0).StringId); + response.Included.Should().ContainSingle(include => include is NameValuePairDataInResponse && include.Id == node.Values.ElementAt(1).StringId); + } + + [Fact] + public async Task Can_include_in_primary_resource() + { + // Arrange + Node node = _fakers.Node.Generate(); + node.Values = _fakers.NameValuePair.Generate(1); + node.Children = _fakers.Node.Generate(2).ToHashSet(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.Nodes.Add(node); + await dbContext.SaveChangesAsync(); + }); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new QueryStringsClient(httpClient, _testOutputHelper); + + var queryString = new Dictionary + { + ["include"] = "children.parent,values" + }; + + // Act + NodePrimaryResponseDocument response = await apiClient.GetNodeAsync(node.StringId!, queryString); + + // Assert + response.Data.Id.Should().Be(node.StringId); + + response.Included.Should().HaveCount(3); + response.Included.Should().ContainSingle(include => include is NodeDataInResponse && include.Id == node.Children.ElementAt(0).StringId); + response.Included.Should().ContainSingle(include => include is NodeDataInResponse && include.Id == node.Children.ElementAt(1).StringId); + response.Included.Should().ContainSingle(include => include is NameValuePairDataInResponse && include.Id == node.Values[0].StringId); + } + + [Fact] + public async Task Can_include_in_secondary_resources() + { + // Arrange + Node node = _fakers.Node.Generate(); + node.Parent = _fakers.Node.Generate(); + node.Values = _fakers.NameValuePair.Generate(2); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.Nodes.Add(node); + await dbContext.SaveChangesAsync(); + }); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new QueryStringsClient(httpClient, _testOutputHelper); + + var queryString = new Dictionary + { + ["include"] = "owner.parent,owner.values" + }; + + // Act + NameValuePairCollectionResponseDocument response = await apiClient.GetNodeValuesAsync(node.StringId!, queryString); + + // Assert + response.Data.ShouldHaveCount(2); + + response.Included.ShouldHaveCount(2); + response.Included.Should().ContainSingle(include => include is NodeDataInResponse && include.Id == node.StringId); + response.Included.Should().ContainSingle(include => include is NodeDataInResponse && include.Id == node.Parent.StringId); + } + + [Fact] + public async Task Can_include_in_secondary_resource() + { + // Arrange + Node node = _fakers.Node.Generate(); + node.Parent = _fakers.Node.Generate(); + node.Parent.Parent = _fakers.Node.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.Nodes.Add(node); + await dbContext.SaveChangesAsync(); + }); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new QueryStringsClient(httpClient, _testOutputHelper); + + var queryString = new Dictionary + { + ["include"] = "parent.parent" + }; + + // Act + NullableNodeSecondaryResponseDocument response = await apiClient.GetNodeParentAsync(node.StringId!, queryString); + + // Assert + response.Data.ShouldNotBeNull(); + response.Data.Id.Should().Be(node.Parent.StringId); + + response.Included.Should().HaveCount(1); + + NodeDataInResponse? include = response.Included.ElementAt(0).Should().BeOfType().Subject; + include.Id.Should().Be(node.Parent.Parent.StringId); + include.Attributes.Name.Should().Be(node.Parent.Parent.Name); + } + + [Fact] + public async Task Can_use_empty_include() + { + // Arrange + Node node = _fakers.Node.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Nodes.Add(node); + await dbContext.SaveChangesAsync(); + }); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new QueryStringsClient(httpClient, _testOutputHelper); + + var queryString = new Dictionary + { + ["include"] = null + }; + + // Act + NodePrimaryResponseDocument response = await apiClient.GetNodeAsync(node.StringId!, queryString); + + // Assert + response.Data.Id.Should().Be(node.StringId); + + response.Included.Should().BeEmpty(); + } +} diff --git a/test/OpenApiEndToEndTests/QueryStrings/swagger.g.json b/test/OpenApiEndToEndTests/QueryStrings/swagger.g.json index 03457ce2a8..8e3d17aaeb 100644 --- a/test/OpenApiEndToEndTests/QueryStrings/swagger.g.json +++ b/test/OpenApiEndToEndTests/QueryStrings/swagger.g.json @@ -4,14 +4,19 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { - "/nodes": { + "/nameValuePairs": { "get": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves a collection of nodes.", - "operationId": "getNodeCollection", + "summary": "Retrieves a collection of nameValuePairs.", + "operationId": "getNameValuePairCollection", "parameters": [ { "name": "query", @@ -23,17 +28,17 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "responses": { "200": { - "description": "Successfully returns the found nodes, or an empty array if none were found.", + "description": "Successfully returns the found nameValuePairs, or an empty array if none were found.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nodeCollectionResponseDocument" + "$ref": "#/components/schemas/nameValuePairCollectionResponseDocument" } } } @@ -45,11 +50,11 @@ }, "head": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves a collection of nodes without returning them.", + "summary": "Retrieves a collection of nameValuePairs without returning them.", "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", - "operationId": "headNodeCollection", + "operationId": "headNameValuePairCollection", "parameters": [ { "name": "query", @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -76,10 +81,10 @@ }, "post": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Creates a new node.", - "operationId": "postNode", + "summary": "Creates a new nameValuePair.", + "operationId": "postNameValuePair", "parameters": [ { "name": "query", @@ -91,18 +96,18 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "requestBody": { - "description": "The attributes and relationships of the node to create.", + "description": "The attributes and relationships of the nameValuePair to create.", "content": { "application/vnd.api+json": { "schema": { "allOf": [ { - "$ref": "#/components/schemas/nodePostRequestDocument" + "$ref": "#/components/schemas/nameValuePairPostRequestDocument" } ] } @@ -111,17 +116,17 @@ }, "responses": { "201": { - "description": "The node was successfully created, which resulted in additional changes. The newly created node is returned.", + "description": "The nameValuePair was successfully created, which resulted in additional changes. The newly created nameValuePair is returned.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nodePrimaryResponseDocument" + "$ref": "#/components/schemas/nameValuePairPrimaryResponseDocument" } } } }, "204": { - "description": "The node was successfully created, which did not result in additional changes." + "description": "The nameValuePair was successfully created, which did not result in additional changes." }, "400": { "description": "The query string is invalid or the request body is missing or malformed." @@ -138,18 +143,18 @@ } } }, - "/nodes/{id}": { + "/nameValuePairs/{id}": { "get": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves an individual node by its identifier.", - "operationId": "getNode", + "summary": "Retrieves an individual nameValuePair by its identifier.", + "operationId": "getNameValuePair", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node to retrieve.", + "description": "The identifier of the nameValuePair to retrieve.", "required": true, "schema": { "type": "string" @@ -165,17 +170,17 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "responses": { "200": { - "description": "Successfully returns the found node.", + "description": "Successfully returns the found nameValuePair.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nodePrimaryResponseDocument" + "$ref": "#/components/schemas/nameValuePairPrimaryResponseDocument" } } } @@ -184,22 +189,22 @@ "description": "The query string is invalid." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } }, "head": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves an individual node by its identifier without returning it.", + "summary": "Retrieves an individual nameValuePair by its identifier without returning it.", "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", - "operationId": "headNode", + "operationId": "headNameValuePair", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node to retrieve.", + "description": "The identifier of the nameValuePair to retrieve.", "required": true, "schema": { "type": "string" @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -227,21 +232,21 @@ "description": "The query string is invalid." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } }, "patch": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Updates an existing node.", - "operationId": "patchNode", + "summary": "Updates an existing nameValuePair.", + "operationId": "patchNameValuePair", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node to update.", + "description": "The identifier of the nameValuePair to update.", "required": true, "schema": { "type": "string" @@ -257,18 +262,18 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "requestBody": { - "description": "The attributes and relationships of the node to update. Omitted fields are left unchanged.", + "description": "The attributes and relationships of the nameValuePair to update. Omitted fields are left unchanged.", "content": { "application/vnd.api+json": { "schema": { "allOf": [ { - "$ref": "#/components/schemas/nodePatchRequestDocument" + "$ref": "#/components/schemas/nameValuePairPatchRequestDocument" } ] } @@ -277,23 +282,23 @@ }, "responses": { "200": { - "description": "The node was successfully updated, which resulted in additional changes. The updated node is returned.", + "description": "The nameValuePair was successfully updated, which resulted in additional changes. The updated nameValuePair is returned.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nodePrimaryResponseDocument" + "$ref": "#/components/schemas/nameValuePairPrimaryResponseDocument" } } } }, "204": { - "description": "The node was successfully updated, which did not result in additional changes." + "description": "The nameValuePair was successfully updated, which did not result in additional changes." }, "400": { "description": "The query string is invalid or the request body is missing or malformed." }, "404": { - "description": "The node or a related resource does not exist." + "description": "The nameValuePair or a related resource does not exist." }, "409": { "description": "A resource type or identifier in the request body is incompatible." @@ -305,15 +310,15 @@ }, "delete": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Deletes an existing node by its identifier.", - "operationId": "deleteNode", + "summary": "Deletes an existing nameValuePair by its identifier.", + "operationId": "deleteNameValuePair", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node to delete.", + "description": "The identifier of the nameValuePair to delete.", "required": true, "schema": { "type": "string" @@ -322,26 +327,26 @@ ], "responses": { "204": { - "description": "The node was successfully deleted." + "description": "The nameValuePair was successfully deleted." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } } }, - "/nodes/{id}/children": { + "/nameValuePairs/{id}/owner": { "get": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves the related nodes of an individual node's children relationship.", - "operationId": "getNodeChildren", + "summary": "Retrieves the related node of an individual nameValuePair's owner relationship.", + "operationId": "getNameValuePairOwner", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related nodes to retrieve.", + "description": "The identifier of the nameValuePair whose related node to retrieve.", "required": true, "schema": { "type": "string" @@ -357,17 +362,17 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "responses": { "200": { - "description": "Successfully returns the found nodes, or an empty array if none were found.", + "description": "Successfully returns the found node, or `null` if it was not found.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nodeCollectionResponseDocument" + "$ref": "#/components/schemas/nodeSecondaryResponseDocument" } } } @@ -376,22 +381,22 @@ "description": "The query string is invalid." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } }, "head": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves the related nodes of an individual node's children relationship without returning them.", + "summary": "Retrieves the related node of an individual nameValuePair's owner relationship without returning it.", "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", - "operationId": "headNodeChildren", + "operationId": "headNameValuePairOwner", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related nodes to retrieve.", + "description": "The identifier of the nameValuePair whose related node to retrieve.", "required": true, "schema": { "type": "string" @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -419,23 +424,23 @@ "description": "The query string is invalid." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } } }, - "/nodes/{id}/relationships/children": { + "/nameValuePairs/{id}/relationships/owner": { "get": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves the related node identities of an individual node's children relationship.", - "operationId": "getNodeChildrenRelationship", + "summary": "Retrieves the related node identity of an individual nameValuePair's owner relationship.", + "operationId": "getNameValuePairOwnerRelationship", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related node identities to retrieve.", + "description": "The identifier of the nameValuePair whose related node identity to retrieve.", "required": true, "schema": { "type": "string" @@ -444,24 +449,24 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "responses": { "200": { - "description": "Successfully returns the found node identities, or an empty array if none were found.", + "description": "Successfully returns the found node identity, or `null` if it was not found.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nodeIdentifierCollectionResponseDocument" + "$ref": "#/components/schemas/nodeIdentifierResponseDocument" } } } @@ -470,22 +475,22 @@ "description": "The query string is invalid." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } }, "head": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Retrieves the related node identities of an individual node's children relationship without returning them.", + "summary": "Retrieves the related node identity of an individual nameValuePair's owner relationship without returning it.", "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", - "operationId": "headNodeChildrenRelationship", + "operationId": "headNameValuePairOwnerRelationship", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related node identities to retrieve.", + "description": "The identifier of the nameValuePair whose related node identity to retrieve.", "required": true, "schema": { "type": "string" @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -513,21 +518,21 @@ "description": "The query string is invalid." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." } } }, - "post": { + "patch": { "tags": [ - "nodes" + "nameValuePairs" ], - "summary": "Adds existing nodes to the children relationship of an individual node.", - "operationId": "postNodeChildrenRelationship", + "summary": "Assigns an existing node to the owner relationship of an individual nameValuePair.", + "operationId": "patchNameValuePairOwnerRelationship", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node to add nodes to.", + "description": "The identifier of the nameValuePair whose owner relationship to assign.", "required": true, "schema": { "type": "string" @@ -535,13 +540,13 @@ } ], "requestBody": { - "description": "The identities of the nodes to add to the children relationship.", + "description": "The identity of the node to assign to the owner relationship.", "content": { "application/vnd.api+json": { "schema": { "allOf": [ { - "$ref": "#/components/schemas/toManyNodeInRequest" + "$ref": "#/components/schemas/toOneNodeInRequest" } ] } @@ -550,90 +555,118 @@ }, "responses": { "204": { - "description": "The nodes were successfully added, which did not result in additional changes." + "description": "The owner relationship was successfully updated, which did not result in additional changes." }, "400": { "description": "The request body is missing or malformed." }, "404": { - "description": "The node does not exist." + "description": "The nameValuePair does not exist." }, "409": { "description": "A resource type in the request body is incompatible." } } - }, - "patch": { + } + }, + "/nodes": { + "get": { "tags": [ "nodes" ], - "summary": "Assigns existing nodes to the children relationship of an individual node.", - "operationId": "patchNodeChildrenRelationship", + "summary": "Retrieves a collection of nodes.", + "operationId": "getNodeCollection", "parameters": [ { - "name": "id", - "in": "path", - "description": "The identifier of the node whose children relationship to assign.", - "required": true, + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { - "type": "string" + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" } } ], - "requestBody": { - "description": "The identities of the nodes to assign to the children relationship, or an empty array to clear the relationship.", - "content": { - "application/vnd.api+json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/toManyNodeInRequest" - } - ] + "responses": { + "200": { + "description": "Successfully returns the found nodes, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nodeCollectionResponseDocument" + } } } + }, + "400": { + "description": "The query string is invalid." } - }, + } + }, + "head": { + "tags": [ + "nodes" + ], + "summary": "Retrieves a collection of nodes without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headNodeCollection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], "responses": { - "204": { - "description": "The children relationship was successfully updated, which did not result in additional changes." + "200": { + "description": "The operation completed successfully." }, "400": { - "description": "The request body is missing or malformed." - }, - "404": { - "description": "The node does not exist." - }, - "409": { - "description": "A resource type in the request body is incompatible." + "description": "The query string is invalid." } } }, - "delete": { + "post": { "tags": [ "nodes" ], - "summary": "Removes existing nodes from the children relationship of an individual node.", - "operationId": "deleteNodeChildrenRelationship", + "summary": "Creates a new node.", + "operationId": "postNode", "parameters": [ { - "name": "id", - "in": "path", - "description": "The identifier of the node to remove nodes from.", - "required": true, + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { - "type": "string" + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" } } ], "requestBody": { - "description": "The identities of the nodes to remove from the children relationship.", + "description": "The attributes and relationships of the node to create.", "content": { "application/vnd.api+json": { "schema": { "allOf": [ { - "$ref": "#/components/schemas/toManyNodeInRequest" + "$ref": "#/components/schemas/nodePostRequestDocument" } ] } @@ -641,33 +674,46 @@ } }, "responses": { + "201": { + "description": "The node was successfully created, which resulted in additional changes. The newly created node is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nodePrimaryResponseDocument" + } + } + } + }, "204": { - "description": "The nodes were successfully removed, which did not result in additional changes." + "description": "The node was successfully created, which did not result in additional changes." }, "400": { - "description": "The request body is missing or malformed." + "description": "The query string is invalid or the request body is missing or malformed." }, - "404": { - "description": "The node does not exist." + "403": { + "description": "Client-generated IDs cannot be used at this endpoint." }, "409": { "description": "A resource type in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." } } } }, - "/nodes/{id}/parent": { + "/nodes/{id}": { "get": { "tags": [ "nodes" ], - "summary": "Retrieves the related node of an individual node's parent relationship.", - "operationId": "getNodeParent", + "summary": "Retrieves an individual node by its identifier.", + "operationId": "getNode", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related node to retrieve.", + "description": "The identifier of the node to retrieve.", "required": true, "schema": { "type": "string" @@ -683,17 +729,17 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], "responses": { "200": { - "description": "Successfully returns the found node, or `null` if it was not found.", + "description": "Successfully returns the found node.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableNodeSecondaryResponseDocument" + "$ref": "#/components/schemas/nodePrimaryResponseDocument" } } } @@ -710,14 +756,14 @@ "tags": [ "nodes" ], - "summary": "Retrieves the related node of an individual node's parent relationship without returning it.", + "summary": "Retrieves an individual node by its identifier without returning it.", "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", - "operationId": "headNodeParent", + "operationId": "headNode", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related node to retrieve.", + "description": "The identifier of the node to retrieve.", "required": true, "schema": { "type": "string" @@ -733,7 +779,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -748,20 +794,18 @@ "description": "The node does not exist." } } - } - }, - "/nodes/{id}/relationships/parent": { - "get": { + }, + "patch": { "tags": [ "nodes" ], - "summary": "Retrieves the related node identity of an individual node's parent relationship.", - "operationId": "getNodeParentRelationship", + "summary": "Updates an existing node.", + "operationId": "patchNode", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related node identity to retrieve.", + "description": "The identifier of the node to update.", "required": true, "schema": { "type": "string" @@ -777,17 +821,117 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], + "requestBody": { + "description": "The attributes and relationships of the node to update. Omitted fields are left unchanged.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/nodePatchRequestDocument" + } + ] + } + } + } + }, "responses": { "200": { - "description": "Successfully returns the found node identity, or `null` if it was not found.", + "description": "The node was successfully updated, which resulted in additional changes. The updated node is returned.", "content": { "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/nullableNodeIdentifierResponseDocument" + "$ref": "#/components/schemas/nodePrimaryResponseDocument" + } + } + } + }, + "204": { + "description": "The node was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "404": { + "description": "The node or a related resource does not exist." + }, + "409": { + "description": "A resource type or identifier in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + }, + "delete": { + "tags": [ + "nodes" + ], + "summary": "Deletes an existing node by its identifier.", + "operationId": "deleteNode", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The node was successfully deleted." + }, + "404": { + "description": "The node does not exist." + } + } + } + }, + "/nodes/{id}/children": { + "get": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related nodes of an individual node's children relationship.", + "operationId": "getNodeChildren", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related nodes to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found nodes, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nodeCollectionResponseDocument" } } } @@ -804,14 +948,14 @@ "tags": [ "nodes" ], - "summary": "Retrieves the related node identity of an individual node's parent relationship without returning it.", + "summary": "Retrieves the related nodes of an individual node's children relationship without returning them.", "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", - "operationId": "headNodeParentRelationship", + "operationId": "headNodeChildren", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose related node identity to retrieve.", + "description": "The identifier of the node whose related nodes to retrieve.", "required": true, "schema": { "type": "string" @@ -827,7 +971,101 @@ "type": "string", "nullable": true }, - "example": null + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + } + }, + "/nodes/{id}/relationships/children": { + "get": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related node identities of an individual node's children relationship.", + "operationId": "getNodeChildrenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related node identities to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found node identities, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nodeIdentifierCollectionResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "head": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related node identities of an individual node's children relationship without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headNodeChildrenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related node identities to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" } } ], @@ -843,17 +1081,17 @@ } } }, - "patch": { + "post": { "tags": [ "nodes" ], - "summary": "Clears or assigns an existing node to the parent relationship of an individual node.", - "operationId": "patchNodeParentRelationship", + "summary": "Adds existing nodes to the children relationship of an individual node.", + "operationId": "postNodeChildrenRelationship", "parameters": [ { "name": "id", "in": "path", - "description": "The identifier of the node whose parent relationship to assign or clear.", + "description": "The identifier of the node to add nodes to.", "required": true, "schema": { "type": "string" @@ -861,13 +1099,13 @@ } ], "requestBody": { - "description": "The identity of the node to assign to the parent relationship, or `null` to clear the relationship.", + "description": "The identities of the nodes to add to the children relationship.", "content": { "application/vnd.api+json": { "schema": { "allOf": [ { - "$ref": "#/components/schemas/nullableToOneNodeInRequest" + "$ref": "#/components/schemas/toManyNodeInRequest" } ] } @@ -876,7 +1114,7 @@ }, "responses": { "204": { - "description": "The parent relationship was successfully updated, which did not result in additional changes." + "description": "The nodes were successfully added, which did not result in additional changes." }, "400": { "description": "The request body is missing or malformed." @@ -888,138 +1126,1161 @@ "description": "A resource type in the request body is incompatible." } } - } - } - }, - "components": { - "schemas": { - "linksInRelationshipObject": { - "required": [ - "related", - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "related": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false }, - "linksInResourceCollectionDocument": { - "required": [ - "self" + "patch": { + "tags": [ + "nodes" ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - }, - "describedby": { - "type": "string" - }, - "first": { - "type": "string" - }, - "last": { - "type": "string" - }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" + "summary": "Assigns existing nodes to the children relationship of an individual node.", + "operationId": "patchNodeChildrenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose children relationship to assign.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The identities of the nodes to assign to the children relationship, or an empty array to clear the relationship.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNodeInRequest" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "The children relationship was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The request body is missing or malformed." + }, + "404": { + "description": "The node does not exist." + }, + "409": { + "description": "A resource type in the request body is incompatible." + } + } + }, + "delete": { + "tags": [ + "nodes" + ], + "summary": "Removes existing nodes from the children relationship of an individual node.", + "operationId": "deleteNodeChildrenRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node to remove nodes from.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The identities of the nodes to remove from the children relationship.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNodeInRequest" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "The nodes were successfully removed, which did not result in additional changes." + }, + "400": { + "description": "The request body is missing or malformed." + }, + "404": { + "description": "The node does not exist." + }, + "409": { + "description": "A resource type in the request body is incompatible." + } + } + } + }, + "/nodes/{id}/parent": { + "get": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related node of an individual node's parent relationship.", + "operationId": "getNodeParent", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related node to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found node, or `null` if it was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableNodeSecondaryResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "head": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related node of an individual node's parent relationship without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headNodeParent", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related node to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + } + }, + "/nodes/{id}/relationships/parent": { + "get": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related node identity of an individual node's parent relationship.", + "operationId": "getNodeParentRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related node identity to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found node identity, or `null` if it was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nullableNodeIdentifierResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "head": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related node identity of an individual node's parent relationship without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headNodeParentRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related node identity to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "patch": { + "tags": [ + "nodes" + ], + "summary": "Clears or assigns an existing node to the parent relationship of an individual node.", + "operationId": "patchNodeParentRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose parent relationship to assign or clear.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The identity of the node to assign to the parent relationship, or `null` to clear the relationship.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/nullableToOneNodeInRequest" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "The parent relationship was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The request body is missing or malformed." + }, + "404": { + "description": "The node does not exist." + }, + "409": { + "description": "A resource type in the request body is incompatible." + } + } + } + }, + "/nodes/{id}/values": { + "get": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related nameValuePairs of an individual node's values relationship.", + "operationId": "getNodeValues", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related nameValuePairs to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found nameValuePairs, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nameValuePairCollectionResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "head": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related nameValuePairs of an individual node's values relationship without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headNodeValues", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related nameValuePairs to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + } + }, + "/nodes/{id}/relationships/values": { + "get": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related nameValuePair identities of an individual node's values relationship.", + "operationId": "getNodeValuesRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related nameValuePair identities to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found nameValuePair identities, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/nameValuePairIdentifierCollectionResponseDocument" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "head": { + "tags": [ + "nodes" + ], + "summary": "Retrieves the related nameValuePair identities of an individual node's values relationship without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "headNodeValuesRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose related nameValuePair identities to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The node does not exist." + } + } + }, + "post": { + "tags": [ + "nodes" + ], + "summary": "Adds existing nameValuePairs to the values relationship of an individual node.", + "operationId": "postNodeValuesRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node to add nameValuePairs to.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The identities of the nameValuePairs to add to the values relationship.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNameValuePairInRequest" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "The nameValuePairs were successfully added, which did not result in additional changes." + }, + "400": { + "description": "The request body is missing or malformed." + }, + "404": { + "description": "The node does not exist." + }, + "409": { + "description": "A resource type in the request body is incompatible." + } + } + }, + "patch": { + "tags": [ + "nodes" + ], + "summary": "Assigns existing nameValuePairs to the values relationship of an individual node.", + "operationId": "patchNodeValuesRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node whose values relationship to assign.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The identities of the nameValuePairs to assign to the values relationship, or an empty array to clear the relationship.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNameValuePairInRequest" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "The values relationship was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The request body is missing or malformed." + }, + "404": { + "description": "The node does not exist." + }, + "409": { + "description": "A resource type in the request body is incompatible." + } + } + }, + "delete": { + "tags": [ + "nodes" + ], + "summary": "Removes existing nameValuePairs from the values relationship of an individual node.", + "operationId": "deleteNodeValuesRelationship", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the node to remove nameValuePairs from.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The identities of the nameValuePairs to remove from the values relationship.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNameValuePairInRequest" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "The nameValuePairs were successfully removed, which did not result in additional changes." + }, + "400": { + "description": "The request body is missing or malformed." + }, + "404": { + "description": "The node does not exist." + }, + "409": { + "description": "A resource type in the request body is incompatible." + } + } + } + } + }, + "components": { + "schemas": { + "dataInResponse": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "nameValuePairs": "#/components/schemas/nameValuePairDataInResponse", + "nodes": "#/components/schemas/nodeDataInResponse" + } + }, + "x-abstract": true + }, + "linksInRelationship": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceCollectionDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceData": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceDocument": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierCollectionDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + }, + "first": { + "type": "string" + }, + "last": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "next": { + "type": "string" + } + }, + "additionalProperties": false + }, + "linksInResourceIdentifierDocument": { + "required": [ + "related", + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + }, + "related": { + "minLength": 1, + "type": "string" + }, + "describedby": { + "type": "string" + } + }, + "additionalProperties": false + }, + "nameValuePairAttributesInPatchRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "nameValuePairAttributesInPostRequest": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string", + "nullable": true } }, "additionalProperties": false }, - "linksInResourceDocument": { + "nameValuePairAttributesInResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "nameValuePairCollectionResponseDocument": { "required": [ - "self" + "data", + "links" ], "type": "object", "properties": { - "self": { - "minLength": 1, - "type": "string" + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceCollectionDocument" + } + ] }, - "describedby": { - "type": "string" + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nameValuePairDataInResponse" + } + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } } }, "additionalProperties": false }, - "linksInResourceIdentifierCollectionDocument": { + "nameValuePairDataInPatchRequest": { "required": [ - "related", - "self" + "id", + "type" ], "type": "object", "properties": { - "self": { - "minLength": 1, - "type": "string" + "type": { + "$ref": "#/components/schemas/nameValuePairResourceType" }, - "related": { + "id": { "minLength": 1, "type": "string" }, - "describedby": { - "type": "string" - }, - "first": { - "type": "string" + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairAttributesInPatchRequest" + } + ] }, - "last": { - "type": "string" + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairRelationshipsInPatchRequest" + } + ] + } + }, + "additionalProperties": false + }, + "nameValuePairDataInPostRequest": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/nameValuePairResourceType" }, - "prev": { - "type": "string" + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairAttributesInPostRequest" + } + ] }, - "next": { - "type": "string" + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairRelationshipsInPostRequest" + } + ] } }, "additionalProperties": false }, - "linksInResourceIdentifierDocument": { + "nameValuePairDataInResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" + }, + { + "required": [ + "links" + ], + "type": "object", + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + } + ], + "additionalProperties": false + }, + "nameValuePairIdentifier": { "required": [ - "related", - "self" + "id", + "type" ], "type": "object", "properties": { - "self": { - "minLength": 1, - "type": "string" + "type": { + "$ref": "#/components/schemas/nameValuePairResourceType" }, - "related": { + "id": { "minLength": 1, "type": "string" + } + }, + "additionalProperties": false + }, + "nameValuePairIdentifierCollectionResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + } + ] }, - "describedby": { - "type": "string" + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nameValuePairIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, + "nameValuePairPatchRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairDataInPatchRequest" + } + ] + } + }, + "additionalProperties": false + }, + "nameValuePairPostRequestDocument": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairDataInPostRequest" + } + ] + } + }, + "additionalProperties": false + }, + "nameValuePairPrimaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/nameValuePairDataInResponse" + } + ] + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, + "nameValuePairRelationshipsInPatchRequest": { + "type": "object", + "properties": { + "owner": { + "allOf": [ + { + "$ref": "#/components/schemas/toOneNodeInRequest" + } + ] + } + }, + "additionalProperties": false + }, + "nameValuePairRelationshipsInPostRequest": { + "required": [ + "owner" + ], + "type": "object", + "properties": { + "owner": { + "allOf": [ + { + "$ref": "#/components/schemas/toOneNodeInRequest" + } + ] + } + }, + "additionalProperties": false + }, + "nameValuePairRelationshipsInResponse": { + "type": "object", + "properties": { + "owner": { + "allOf": [ + { + "$ref": "#/components/schemas/toOneNodeInResponse" + } + ] } }, "additionalProperties": false }, - "linksInResourceObject": { - "required": [ - "self" + "nameValuePairResourceType": { + "enum": [ + "nameValuePairs" ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, + "type": "string", "additionalProperties": false }, "nodeAttributesInPatchRequest": { @@ -1084,6 +2345,12 @@ "$ref": "#/components/schemas/nodeDataInResponse" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1102,11 +2369,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/nodeResourceType" - } - ] + "$ref": "#/components/schemas/nodeResourceType" }, "id": { "minLength": 1, @@ -1136,11 +2399,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/nodeResourceType" - } - ] + "$ref": "#/components/schemas/nodeResourceType" }, "attributes": { "allOf": [ @@ -1160,77 +2419,98 @@ "additionalProperties": false }, "nodeDataInResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/dataInResponse" + }, + { + "required": [ + "links" + ], + "type": "object", + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeAttributesInResponse" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeRelationshipsInResponse" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceData" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + } + ], + "additionalProperties": false + }, + "nodeIdentifier": { "required": [ "id", - "links", "type" ], "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/nodeResourceType" - } - ] + "$ref": "#/components/schemas/nodeResourceType" }, "id": { "minLength": 1, "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/nodeAttributesInResponse" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/nodeRelationshipsInResponse" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/linksInResourceObject" - } - ] - }, - "meta": { - "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } } }, "additionalProperties": false }, - "nodeIdentifier": { + "nodeIdentifierCollectionResponseDocument": { "required": [ - "id", - "type" + "data", + "links" ], "type": "object", "properties": { - "type": { + "links": { "allOf": [ { - "$ref": "#/components/schemas/nodeResourceType" + "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" } ] }, - "id": { - "minLength": 1, - "type": "string" + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nodeIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } } }, "additionalProperties": false }, - "nodeIdentifierCollectionResponseDocument": { + "nodeIdentifierResponseDocument": { "required": [ "data", "links" @@ -1240,15 +2520,16 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInResourceIdentifierCollectionDocument" + "$ref": "#/components/schemas/linksInResourceIdentifierDocument" } ] }, "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/nodeIdentifier" - } + "allOf": [ + { + "$ref": "#/components/schemas/nodeIdentifier" + } + ] }, "meta": { "type": "object", @@ -1313,6 +2594,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1326,6 +2613,13 @@ "nodeRelationshipsInPatchRequest": { "type": "object", "properties": { + "values": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNameValuePairInRequest" + } + ] + }, "parent": { "allOf": [ { @@ -1346,6 +2640,13 @@ "nodeRelationshipsInPostRequest": { "type": "object", "properties": { + "values": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNameValuePairInRequest" + } + ] + }, "parent": { "allOf": [ { @@ -1366,6 +2667,13 @@ "nodeRelationshipsInResponse": { "type": "object", "properties": { + "values": { + "allOf": [ + { + "$ref": "#/components/schemas/toManyNameValuePairInResponse" + } + ] + }, "parent": { "allOf": [ { @@ -1390,6 +2698,43 @@ "type": "string", "additionalProperties": false }, + "nodeSecondaryResponseDocument": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInResourceDocument" + } + ] + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeDataInResponse" + } + ] + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, "nullableNodeIdentifierResponseDocument": { "required": [ "data", @@ -1444,6 +2789,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/dataInResponse" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -1480,7 +2831,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -1502,6 +2853,50 @@ }, "additionalProperties": false }, + "toManyNameValuePairInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nameValuePairIdentifier" + } + } + }, + "additionalProperties": false + }, + "toManyNameValuePairInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationship" + } + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nameValuePairIdentifier" + } + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + }, "toManyNodeInRequest": { "required": [ "data" @@ -1526,7 +2921,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/linksInRelationshipObject" + "$ref": "#/components/schemas/linksInRelationship" } ] }, @@ -1545,6 +2940,52 @@ } }, "additionalProperties": false + }, + "toOneNodeInRequest": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeIdentifier" + } + ] + } + }, + "additionalProperties": false + }, + "toOneNodeInResponse": { + "required": [ + "links" + ], + "type": "object", + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/linksInRelationship" + } + ] + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeIdentifier" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } } } diff --git a/test/OpenApiTests/DocComments/DocCommentsTests.cs b/test/OpenApiTests/DocComments/DocCommentsTests.cs index ae9788a0df..fc31208918 100644 --- a/test/OpenApiTests/DocComments/DocCommentsTests.cs +++ b/test/OpenApiTests/DocComments/DocCommentsTests.cs @@ -8,9 +8,12 @@ namespace OpenApiTests.DocComments; public sealed class DocCommentsTests : IClassFixture, DocCommentsDbContext>> { - private const string TextQueryString = + private const string ResourceTextQueryString = "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters."; + private const string RelationshipTextQueryString = + "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters."; + private readonly OpenApiTestContext, DocCommentsDbContext> _testContext; public DocCommentsTests(OpenApiTestContext, DocCommentsDbContext> testContext) @@ -66,7 +69,7 @@ public async Task Endpoints_are_documented() { parametersElement.EnumerateArray().ShouldHaveCount(1); parametersElement.Should().HaveProperty("[0].in", "query"); - parametersElement.Should().HaveProperty("[0].description", TextQueryString); + parametersElement.Should().HaveProperty("[0].description", ResourceTextQueryString); }); getElement.Should().ContainPath("responses").With(responseElement => @@ -86,7 +89,7 @@ public async Task Endpoints_are_documented() { parametersElement.EnumerateArray().ShouldHaveCount(1); parametersElement.Should().HaveProperty("[0].in", "query"); - parametersElement.Should().HaveProperty("[0].description", TextQueryString); + parametersElement.Should().HaveProperty("[0].description", ResourceTextQueryString); }); headElement.Should().ContainPath("responses").With(responseElement => @@ -105,7 +108,7 @@ public async Task Endpoints_are_documented() { parametersElement.EnumerateArray().ShouldHaveCount(1); parametersElement.Should().HaveProperty("[0].in", "query"); - parametersElement.Should().HaveProperty("[0].description", TextQueryString); + parametersElement.Should().HaveProperty("[0].description", ResourceTextQueryString); }); postElement.Should().ContainPath("responses").With(responseElement => @@ -132,7 +135,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); getElement.Should().ContainPath("responses").With(responseElement => @@ -155,7 +158,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); headElement.Should().ContainPath("responses").With(responseElement => @@ -177,7 +180,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper to update."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); patchElement.Should().HaveProperty("requestBody.description", "The attributes and relationships of the skyscraper to update. Omitted fields are left unchanged."); @@ -226,7 +229,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related elevator to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); getElement.Should().ContainPath("responses").With(responseElement => @@ -249,7 +252,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related elevator to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); headElement.Should().ContainPath("responses").With(responseElement => @@ -274,7 +277,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related elevator identity to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", RelationshipTextQueryString); }); getElement.Should().ContainPath("responses").With(responseElement => @@ -297,7 +300,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related elevator identity to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", RelationshipTextQueryString); }); headElement.Should().ContainPath("responses").With(responseElement => @@ -345,7 +348,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related spaces to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); getElement.Should().ContainPath("responses").With(responseElement => @@ -368,7 +371,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related spaces to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", ResourceTextQueryString); }); headElement.Should().ContainPath("responses").With(responseElement => @@ -393,7 +396,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related space identities to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", RelationshipTextQueryString); }); getElement.Should().ContainPath("responses").With(responseElement => @@ -416,7 +419,7 @@ public async Task Endpoints_are_documented() parametersElement.Should().HaveProperty("[0].in", "path"); parametersElement.Should().HaveProperty("[0].description", "The identifier of the skyscraper whose related space identities to retrieve."); parametersElement.Should().HaveProperty("[1].in", "query"); - parametersElement.Should().HaveProperty("[1].description", TextQueryString); + parametersElement.Should().HaveProperty("[1].description", RelationshipTextQueryString); }); headElement.Should().ContainPath("responses").With(responseElement => diff --git a/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationTests.cs b/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationTests.cs index e68f97fe0c..75bc093b25 100644 --- a/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationTests.cs +++ b/test/OpenApiTests/LegacyOpenApiIntegration/LegacyOpenApiIntegrationTests.cs @@ -13,6 +13,7 @@ public LegacyOpenApiIntegrationTests() UseController(); UseController(); UseController(); + UseController(); SwaggerDocumentOutputDirectory = "test/OpenApiClientTests/LegacyClient"; } diff --git a/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json b/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json index 644af8144f..1a86868059 100644 --- a/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json +++ b/test/OpenApiTests/LegacyOpenApiIntegration/swagger.json @@ -4,6 +4,11 @@ "title": "OpenApiTests", "version": "1.0" }, + "servers": [ + { + "url": "http://localhost" + } + ], "paths": { "/api/airplanes": { "get": { @@ -23,7 +28,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -61,7 +66,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -91,7 +96,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -165,7 +170,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -215,7 +220,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -257,7 +262,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -357,7 +362,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -407,7 +412,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -444,14 +449,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -494,14 +499,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -674,7 +679,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -712,7 +717,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -742,7 +747,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -816,7 +821,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -866,7 +871,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -908,7 +913,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1008,7 +1013,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1058,7 +1063,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1095,14 +1100,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1145,14 +1150,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1334,7 +1339,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1384,7 +1389,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1421,14 +1426,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1471,14 +1476,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1651,7 +1656,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1689,7 +1694,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1719,7 +1724,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1793,7 +1798,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1843,7 +1848,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1885,7 +1890,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -1985,7 +1990,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2035,7 +2040,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2072,14 +2077,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2122,14 +2127,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2219,7 +2224,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2269,7 +2274,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2306,14 +2311,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2356,14 +2361,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2545,7 +2550,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2595,7 +2600,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2632,14 +2637,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2682,14 +2687,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2871,7 +2876,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2921,7 +2926,7 @@ "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -2958,14 +2963,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -3008,14 +3013,14 @@ { "name": "query", "in": "query", - "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "description": "For syntax, see the documentation for the [`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", "schema": { "type": "object", "additionalProperties": { "type": "string", "nullable": true }, - "example": null + "example": "" } } ], @@ -3077,150 +3082,475 @@ } } } - } - }, - "components": { - "schemas": { - "aircraft-kind": { - "enum": [ - "Turboprops", - "LightJet", - "MidSizeJet", - "JumboJet" + }, + "/api/passengers": { + "get": { + "tags": [ + "passengers" ], - "type": "string" - }, - "airline": { - "enum": [ - "DeltaAirLines", - "LufthansaGroup", - "AirFranceKlm" + "summary": "Retrieves a collection of passengers.", + "operationId": "get-passenger-collection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } ], - "type": "string", - "description": "Lists the various airlines used in this API." - }, - "airplane-attributes-in-patch-request": { - "type": "object", - "properties": { - "name": { - "maxLength": 255, - "type": "string" - }, - "serial-number": { - "maxLength": 16, - "type": "string", - "nullable": true - }, - "airtime-in-hours": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "last-serviced-at": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "is-in-maintenance": { - "type": "boolean" + "responses": { + "200": { + "description": "Successfully returns the found passengers, or an empty array if none were found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-collection-response-document" + } + } + } }, - "manufactured-in-city": { - "maxLength": 85, - "type": "string", - "nullable": true + "400": { + "description": "The query string is invalid." } - }, - "additionalProperties": false + } }, - "airplane-attributes-in-post-request": { - "required": [ - "name" + "head": { + "tags": [ + "passengers" ], - "type": "object", - "properties": { - "name": { - "maxLength": 255, - "type": "string" - }, - "serial-number": { - "maxLength": 16, - "type": "string", - "nullable": true - }, - "airtime-in-hours": { - "type": "integer", - "format": "int32", - "nullable": true + "summary": "Retrieves a collection of passengers without returning them.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "head-passenger-collection", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." }, - "last-serviced-at": { - "type": "string", - "format": "date-time", - "nullable": true + "400": { + "description": "The query string is invalid." } - }, - "additionalProperties": false + } }, - "airplane-attributes-in-response": { - "type": "object", - "properties": { - "name": { - "maxLength": 255, - "type": "string" - }, - "serial-number": { - "maxLength": 16, - "type": "string", - "nullable": true - }, - "airtime-in-hours": { - "type": "integer", - "format": "int32", - "nullable": true + "post": { + "tags": [ + "passengers" + ], + "summary": "Creates a new passenger.", + "operationId": "post-passenger", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the passenger to create.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-post-request-document" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The passenger was successfully created, which resulted in additional changes. The newly created passenger is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-primary-response-document" + } + } + } }, - "last-serviced-at": { - "type": "string", - "format": "date-time", - "nullable": true + "204": { + "description": "The passenger was successfully created, which did not result in additional changes." }, - "manufactured-at": { - "type": "string", - "description": "Gets the day on which this airplane was manufactured.", - "format": "date-time" + "400": { + "description": "The query string is invalid or the request body is missing or malformed." }, - "is-in-maintenance": { - "type": "boolean" + "403": { + "description": "Client-generated IDs cannot be used at this endpoint." }, - "manufactured-in-city": { - "maxLength": 85, - "type": "string", - "nullable": true + "409": { + "description": "A resource type in the request body is incompatible." }, - "kind": { - "allOf": [ - { - "$ref": "#/components/schemas/aircraft-kind" - } - ] + "422": { + "description": "Validation of the request body failed." } - }, - "additionalProperties": false - }, - "airplane-collection-response-document": { - "required": [ - "data", - "links" + } + } + }, + "/api/passengers/{id}": { + "get": { + "tags": [ + "passengers" ], - "type": "object", - "properties": { - "jsonapi": { - "allOf": [ - { - "$ref": "#/components/schemas/jsonapi-object" - } - ] + "summary": "Retrieves an individual passenger by its identifier.", + "operationId": "get-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to retrieve.", + "required": true, + "schema": { + "type": "string" + } }, - "links": { - "allOf": [ + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "Successfully returns the found passenger.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-primary-response-document" + } + } + } + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The passenger does not exist." + } + } + }, + "head": { + "tags": [ + "passengers" + ], + "summary": "Retrieves an individual passenger by its identifier without returning it.", + "description": "Compare the returned ETag HTTP header with an earlier one to determine if the response has changed since it was fetched.", + "operationId": "head-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to retrieve.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "responses": { + "200": { + "description": "The operation completed successfully." + }, + "400": { + "description": "The query string is invalid." + }, + "404": { + "description": "The passenger does not exist." + } + } + }, + "patch": { + "tags": [ + "passengers" + ], + "summary": "Updates an existing passenger.", + "operationId": "patch-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to update.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "For syntax, see the documentation for the [`include`](https://www.jsonapi.net/usage/reading/including-relationships.html)/[`filter`](https://www.jsonapi.net/usage/reading/filtering.html)/[`sort`](https://www.jsonapi.net/usage/reading/sorting.html)/[`page`](https://www.jsonapi.net/usage/reading/pagination.html)/[`fields`](https://www.jsonapi.net/usage/reading/sparse-fieldset-selection.html) query string parameters.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "example": "" + } + } + ], + "requestBody": { + "description": "The attributes and relationships of the passenger to update. Omitted fields are left unchanged.", + "content": { + "application/vnd.api+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-patch-request-document" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "The passenger was successfully updated, which resulted in additional changes. The updated passenger is returned.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/passenger-primary-response-document" + } + } + } + }, + "204": { + "description": "The passenger was successfully updated, which did not result in additional changes." + }, + "400": { + "description": "The query string is invalid or the request body is missing or malformed." + }, + "404": { + "description": "The passenger or a related resource does not exist." + }, + "409": { + "description": "A resource type or identifier in the request body is incompatible." + }, + "422": { + "description": "Validation of the request body failed." + } + } + }, + "delete": { + "tags": [ + "passengers" + ], + "summary": "Deletes an existing passenger by its identifier.", + "operationId": "delete-passenger", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The identifier of the passenger to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The passenger was successfully deleted." + }, + "404": { + "description": "The passenger does not exist." + } + } + } + } + }, + "components": { + "schemas": { + "aircraft-kind": { + "enum": [ + "Turboprops", + "LightJet", + "MidSizeJet", + "JumboJet" + ], + "type": "string" + }, + "airline": { + "enum": [ + "DeltaAirLines", + "LufthansaGroup", + "AirFranceKlm" + ], + "type": "string", + "description": "Lists the various airlines used in this API." + }, + "airplane-attributes-in-patch-request": { + "type": "object", + "properties": { + "name": { + "maxLength": 255, + "type": "string" + }, + "serial-number": { + "maxLength": 16, + "type": "string", + "nullable": true + }, + "airtime-in-hours": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "last-serviced-at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is-in-maintenance": { + "type": "boolean" + }, + "manufactured-in-city": { + "maxLength": 85, + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "airplane-attributes-in-post-request": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "maxLength": 255, + "type": "string" + }, + "serial-number": { + "maxLength": 16, + "type": "string", + "nullable": true + }, + "airtime-in-hours": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "last-serviced-at": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false + }, + "airplane-attributes-in-response": { + "type": "object", + "properties": { + "name": { + "maxLength": 255, + "type": "string" + }, + "serial-number": { + "maxLength": 16, + "type": "string", + "nullable": true + }, + "airtime-in-hours": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "last-serviced-at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "manufactured-at": { + "type": "string", + "description": "Gets the day on which this airplane was manufactured.", + "format": "date-time" + }, + "is-in-maintenance": { + "type": "boolean" + }, + "manufactured-in-city": { + "maxLength": 85, + "type": "string", + "nullable": true + }, + "kind": { + "allOf": [ + { + "$ref": "#/components/schemas/aircraft-kind" + } + ] + } + }, + "additionalProperties": false + }, + "airplane-collection-response-document": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/jsonapi" + } + ] + }, + "links": { + "allOf": [ { "$ref": "#/components/schemas/links-in-resource-collection-document" } @@ -3232,6 +3562,12 @@ "$ref": "#/components/schemas/airplane-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3250,11 +3586,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-resource-type" - } - ] + "$ref": "#/components/schemas/airplane-resource-type" }, "id": { "minLength": 1, @@ -3284,11 +3616,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-resource-type" - } - ] + "$ref": "#/components/schemas/airplane-resource-type" }, "attributes": { "allOf": [ @@ -3308,53 +3636,48 @@ "additionalProperties": false }, "airplane-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/airplane-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/airplane-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/airplane-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "airplane-patch-request-document": { @@ -3399,7 +3722,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3417,6 +3740,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3481,6 +3810,34 @@ ], "type": "string" }, + "data-in-response": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "minLength": 1, + "type": "string" + }, + "id": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "type", + "mapping": { + "airplanes": "#/components/schemas/airplane-data-in-response", + "flight-attendants": "#/components/schemas/flight-attendant-data-in-response", + "flights": "#/components/schemas/flight-data-in-response", + "passengers": "#/components/schemas/passenger-data-in-response" + } + }, + "x-abstract": true + }, "flight-attendant-attributes-in-patch-request": { "type": "object", "properties": { @@ -3558,7 +3915,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3575,6 +3932,12 @@ "$ref": "#/components/schemas/flight-attendant-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3593,11 +3956,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] + "$ref": "#/components/schemas/flight-attendant-resource-type" }, "id": { "minLength": 1, @@ -3627,11 +3986,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] + "$ref": "#/components/schemas/flight-attendant-resource-type" }, "attributes": { "allOf": [ @@ -3651,53 +4006,48 @@ "additionalProperties": false }, "flight-attendant-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-attendant-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "flight-attendant-identifier": { @@ -3708,11 +4058,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attendant-resource-type" - } - ] + "$ref": "#/components/schemas/flight-attendant-resource-type" }, "id": { "minLength": 1, @@ -3731,7 +4077,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3768,7 +4114,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3838,7 +4184,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3856,6 +4202,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -3943,7 +4295,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -3961,6 +4313,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4041,7 +4399,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4058,6 +4416,12 @@ "$ref": "#/components/schemas/flight-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4076,11 +4440,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] + "$ref": "#/components/schemas/flight-resource-type" }, "id": { "minLength": 1, @@ -4110,11 +4470,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] + "$ref": "#/components/schemas/flight-resource-type" }, "relationships": { "allOf": [ @@ -4127,53 +4483,48 @@ "additionalProperties": false }, "flight-data-in-response": { - "required": [ - "id", - "links", - "type" - ], - "type": "object", - "properties": { - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] - }, - "id": { - "minLength": 1, - "type": "string" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-attributes-in-response" - } - ] - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-relationships-in-response" - } - ] - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/links-in-resource-object" - } - ] + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" }, - "meta": { + { + "required": [ + "links" + ], "type": "object", - "additionalProperties": { - "type": "object", - "nullable": true - } + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-attributes-in-response" + } + ] + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/flight-relationships-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false } - }, + ], "additionalProperties": false }, "flight-identifier": { @@ -4184,11 +4535,7 @@ "type": "object", "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/flight-resource-type" - } - ] + "$ref": "#/components/schemas/flight-resource-type" }, "id": { "minLength": 1, @@ -4207,7 +4554,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4276,7 +4623,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4294,6 +4641,12 @@ } ] }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4416,7 +4769,7 @@ "type": "string", "additionalProperties": false }, - "jsonapi-object": { + "jsonapi": { "type": "object", "properties": { "version": { @@ -4444,7 +4797,7 @@ }, "additionalProperties": false }, - "links-in-relationship-object": { + "links-in-relationship": { "required": [ "related", "self" @@ -4490,6 +4843,19 @@ }, "additionalProperties": false }, + "links-in-resource-data": { + "required": [ + "self" + ], + "type": "object", + "properties": { + "self": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "links-in-resource-document": { "required": [ "self" @@ -4560,19 +4926,6 @@ }, "additionalProperties": false }, - "links-in-resource-object": { - "required": [ - "self" - ], - "type": "object", - "properties": { - "self": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, "nullable-flight-attendant-identifier-response-document": { "required": [ "data", @@ -4583,7 +4936,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4622,7 +4975,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4641,6 +4994,12 @@ ], "nullable": true }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4677,7 +5036,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -4699,6 +5058,29 @@ }, "additionalProperties": false }, + "passenger-attributes-in-patch-request": { + "type": "object", + "properties": { + "document-number": { + "maxLength": 9, + "type": "string" + } + }, + "additionalProperties": false + }, + "passenger-attributes-in-post-request": { + "required": [ + "document-number" + ], + "type": "object", + "properties": { + "document-number": { + "maxLength": 9, + "type": "string" + } + }, + "additionalProperties": false + }, "passenger-attributes-in-response": { "type": "object", "properties": { @@ -4726,7 +5108,7 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, @@ -4743,6 +5125,12 @@ "$ref": "#/components/schemas/passenger-data-in-response" } }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/data-in-response" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4753,39 +5141,131 @@ }, "additionalProperties": false }, - "passenger-data-in-response": { + "passenger-data-in-patch-request": { "required": [ "id", - "links", "type" ], "type": "object", "properties": { "type": { + "$ref": "#/components/schemas/passenger-resource-type" + }, + "id": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-attributes-in-patch-request" + } + ] + } + }, + "additionalProperties": false + }, + "passenger-data-in-post-request": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/passenger-resource-type" + }, + "attributes": { "allOf": [ { - "$ref": "#/components/schemas/passenger-resource-type" + "$ref": "#/components/schemas/passenger-attributes-in-post-request" } ] + } + }, + "additionalProperties": false + }, + "passenger-data-in-response": { + "allOf": [ + { + "$ref": "#/components/schemas/data-in-response" + }, + { + "required": [ + "links" + ], + "type": "object", + "properties": { + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-attributes-in-response" + } + ] + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/links-in-resource-data" + } + ] + }, + "meta": { + "type": "object", + "additionalProperties": { + "type": "object", + "nullable": true + } + } + }, + "additionalProperties": false + } + ], + "additionalProperties": false + }, + "passenger-identifier": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/passenger-resource-type" }, "id": { "minLength": 1, "type": "string" - }, - "attributes": { + } + }, + "additionalProperties": false + }, + "passenger-identifier-collection-response-document": { + "required": [ + "data", + "links" + ], + "type": "object", + "properties": { + "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/passenger-attributes-in-response" + "$ref": "#/components/schemas/jsonapi" } ] }, "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-resource-object" + "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" } ] }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/passenger-identifier" + } + }, "meta": { "type": "object", "additionalProperties": { @@ -4796,28 +5276,39 @@ }, "additionalProperties": false }, - "passenger-identifier": { + "passenger-patch-request-document": { "required": [ - "id", - "type" + "data" ], "type": "object", "properties": { - "type": { + "data": { "allOf": [ { - "$ref": "#/components/schemas/passenger-resource-type" + "$ref": "#/components/schemas/passenger-data-in-patch-request" } ] - }, - "id": { - "minLength": 1, - "type": "string" } }, "additionalProperties": false }, - "passenger-identifier-collection-response-document": { + "passenger-post-request-document": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-data-in-post-request" + } + ] + } + }, + "additionalProperties": false + }, + "passenger-primary-response-document": { "required": [ "data", "links" @@ -4827,21 +5318,28 @@ "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/jsonapi-object" + "$ref": "#/components/schemas/jsonapi" } ] }, "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-resource-identifier-collection-document" + "$ref": "#/components/schemas/links-in-resource-document" } ] }, "data": { + "allOf": [ + { + "$ref": "#/components/schemas/passenger-data-in-response" + } + ] + }, + "included": { "type": "array", "items": { - "$ref": "#/components/schemas/passenger-identifier" + "$ref": "#/components/schemas/data-in-response" } }, "meta": { @@ -4885,7 +5383,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -4929,7 +5427,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -4973,7 +5471,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, @@ -5018,7 +5516,7 @@ "links": { "allOf": [ { - "$ref": "#/components/schemas/links-in-relationship-object" + "$ref": "#/components/schemas/links-in-relationship" } ] }, diff --git a/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs b/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs index 9b623ceb16..db8744fcbc 100644 --- a/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs +++ b/test/OpenApiTests/NamingConventions/CamelCase/CamelCaseTests.cs @@ -13,6 +13,8 @@ public CamelCaseTests(OpenApiTestContext(); + testContext.UseController(); + testContext.SwaggerDocumentOutputDirectory = "test/OpenApiClientTests/NamingConventions/CamelCase"; } @@ -43,7 +45,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.Should().ContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.Should().ContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("jsonapiObject"); + propertiesElement.Should().ContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("jsonapi"); linksInResourceCollectionDocumentSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("linksInResourceCollectionDocument").SchemaReferenceId; @@ -62,18 +64,23 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() propertiesElement.Should().ContainProperty("next"); }); - string? linksInResourceObjectSchemaRefId = null; - string? primaryResourceTypeSchemaRefId = null; + string? linksInResourceDataSchemaRefId = null; string? resourceAttributesInResponseSchemaRefId = null; string? resourceRelationshipInResponseSchemaRefId = null; - schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => + string abstractResourceDataSchemaRefId = schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[0].$ref") + .ShouldBeSchemaReferenceId("dataInResponse").SchemaReferenceId; + + schemasElement.Should().ContainPath($"{abstractResourceDataSchemaRefId}.discriminator.mapping").With(mappingElement => { - linksInResourceObjectSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") - .ShouldBeSchemaReferenceId("linksInResourceObject").SchemaReferenceId; + mappingElement.Should().ContainPath("supermarkets").ShouldBeSchemaReferenceId("supermarketDataInResponse"); + mappingElement.Should().ContainPath("staffMembers").ShouldBeSchemaReferenceId("staffMemberDataInResponse"); + }); - primaryResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.allOf[0].$ref") - .ShouldBeSchemaReferenceId("supermarketResourceType").SchemaReferenceId; + schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[1].properties").With(propertiesElement => + { + linksInResourceDataSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref").ShouldBeSchemaReferenceId("linksInResourceData") + .SchemaReferenceId; resourceAttributesInResponseSchemaRefId = propertiesElement.Should().ContainPath("attributes.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarketAttributesInResponse").SchemaReferenceId; @@ -82,16 +89,11 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() .ShouldBeSchemaReferenceId("supermarketRelationshipsInResponse").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{linksInResourceObjectSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{linksInResourceDataSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("self"); }); - schemasElement.Should().ContainPath($"{primaryResourceTypeSchemaRefId}.enum[0]").With(enumValueElement => - { - enumValueElement.Should().Be("supermarkets"); - }); - schemasElement.Should().ContainPath($"{resourceAttributesInResponseSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("nameOfCity"); @@ -114,19 +116,19 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() propertiesElement.Should().ContainPath("cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("toManyStaffMemberInResponse"); }); - string? linksInRelationshipObjectSchemaRefId = null; + string? linksInRelationshipSchemaRefId = null; string? relatedResourceIdentifierSchemaRefId = null; schemasElement.Should().ContainPath($"{nullableToOneResourceResponseDataSchemaRefId}.properties").With(propertiesElement => { - linksInRelationshipObjectSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") - .ShouldBeSchemaReferenceId("linksInRelationshipObject").SchemaReferenceId; + linksInRelationshipSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref").ShouldBeSchemaReferenceId("linksInRelationship") + .SchemaReferenceId; relatedResourceIdentifierSchemaRefId = propertiesElement.Should().ContainPath("data.allOf[0].$ref") .ShouldBeSchemaReferenceId("staffMemberIdentifier").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{linksInRelationshipObjectSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{linksInRelationshipSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("self"); propertiesElement.Should().ContainProperty("related"); @@ -136,11 +138,14 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.Should().ContainPath($"{relatedResourceIdentifierSchemaRefId}.properties").With(propertiesElement => { - relatedResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.allOf[0].$ref") - .ShouldBeSchemaReferenceId("staffMemberResourceType").SchemaReferenceId; + relatedResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.$ref").ShouldBeSchemaReferenceId("staffMemberResourceType") + .SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").ShouldBeSchemaReferenceId("staffMembers"); + schemasElement.Should().ContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").With(enumValueElement => + { + enumValueElement.Should().Be("staffMembers"); + }); }); } @@ -212,7 +217,7 @@ public async Task Casing_convention_is_applied_to_GetSecondary_endpoint_with_sin .SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[1].properties").With(propertiesElement => { propertiesElement.Should().ContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("staffMemberAttributesInResponse"); }); diff --git a/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs b/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs index 78521a15ee..ef97791ab1 100644 --- a/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs +++ b/test/OpenApiTests/NamingConventions/KebabCase/KebabCaseTests.cs @@ -13,6 +13,8 @@ public KebabCaseTests(OpenApiTestContext(); + testContext.UseController(); + testContext.SwaggerDocumentOutputDirectory = "test/OpenApiClientTests/NamingConventions/KebabCase"; } @@ -43,7 +45,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.Should().ContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.Should().ContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("jsonapi-object"); + propertiesElement.Should().ContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("jsonapi"); linksInResourceCollectionDocumentSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("links-in-resource-collection-document").SchemaReferenceId; @@ -62,18 +64,23 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() propertiesElement.Should().ContainProperty("next"); }); - string? linksInResourceObjectSchemaRefId = null; - string? primaryResourceTypeSchemaRefId = null; + string? linksInResourceDataSchemaRefId = null; string? resourceAttributesInResponseSchemaRefId = null; string? resourceRelationshipInResponseSchemaRefId = null; - schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => + string abstractResourceDataSchemaRefId = schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[0].$ref") + .ShouldBeSchemaReferenceId("data-in-response").SchemaReferenceId; + + schemasElement.Should().ContainPath($"{abstractResourceDataSchemaRefId}.discriminator.mapping").With(mappingElement => { - linksInResourceObjectSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") - .ShouldBeSchemaReferenceId("links-in-resource-object").SchemaReferenceId; + mappingElement.Should().ContainPath("supermarkets").ShouldBeSchemaReferenceId("supermarket-data-in-response"); + mappingElement.Should().ContainPath("staff-members").ShouldBeSchemaReferenceId("staff-member-data-in-response"); + }); - primaryResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.allOf[0].$ref") - .ShouldBeSchemaReferenceId("supermarket-resource-type").SchemaReferenceId; + schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[1].properties").With(propertiesElement => + { + linksInResourceDataSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("links-in-resource-data").SchemaReferenceId; resourceAttributesInResponseSchemaRefId = propertiesElement.Should().ContainPath("attributes.allOf[0].$ref") .ShouldBeSchemaReferenceId("supermarket-attributes-in-response").SchemaReferenceId; @@ -82,16 +89,11 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() .ShouldBeSchemaReferenceId("supermarket-relationships-in-response").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{linksInResourceObjectSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{linksInResourceDataSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("self"); }); - schemasElement.Should().ContainPath($"{primaryResourceTypeSchemaRefId}.enum[0]").With(enumValueElement => - { - enumValueElement.Should().Be("supermarkets"); - }); - schemasElement.Should().ContainPath($"{resourceAttributesInResponseSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("name-of-city"); @@ -114,19 +116,19 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() propertiesElement.Should().ContainPath("cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("to-many-staff-member-in-response"); }); - string? linksInRelationshipObjectSchemaRefId = null; + string? linksInRelationshipSchemaRefId = null; string? relatedResourceIdentifierSchemaRefId = null; schemasElement.Should().ContainPath($"{nullableToOneResourceResponseDataSchemaRefId}.properties").With(propertiesElement => { - linksInRelationshipObjectSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") - .ShouldBeSchemaReferenceId("links-in-relationship-object").SchemaReferenceId; + linksInRelationshipSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") + .ShouldBeSchemaReferenceId("links-in-relationship").SchemaReferenceId; relatedResourceIdentifierSchemaRefId = propertiesElement.Should().ContainPath("data.allOf[0].$ref") .ShouldBeSchemaReferenceId("staff-member-identifier").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{linksInRelationshipObjectSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{linksInRelationshipSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("self"); propertiesElement.Should().ContainProperty("related"); @@ -136,11 +138,14 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.Should().ContainPath($"{relatedResourceIdentifierSchemaRefId}.properties").With(propertiesElement => { - relatedResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.allOf[0].$ref") - .ShouldBeSchemaReferenceId("staff-member-resource-type").SchemaReferenceId; + relatedResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.$ref").ShouldBeSchemaReferenceId("staff-member-resource-type") + .SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").ShouldBeSchemaReferenceId("staff-members"); + schemasElement.Should().ContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").With(enumValueElement => + { + enumValueElement.Should().Be("staff-members"); + }); }); } @@ -212,7 +217,7 @@ public async Task Casing_convention_is_applied_to_GetSecondary_endpoint_with_sin .ShouldBeSchemaReferenceId("staff-member-data-in-response").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[1].properties").With(propertiesElement => { propertiesElement.Should().ContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("staff-member-attributes-in-response"); }); diff --git a/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs b/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs index fe801a500f..f357e095eb 100644 --- a/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs +++ b/test/OpenApiTests/NamingConventions/PascalCase/PascalCaseTests.cs @@ -14,6 +14,8 @@ public PascalCaseTests(OpenApiTestContext(); + testContext.UseController(); + testContext.SwaggerDocumentOutputDirectory = "test/OpenApiClientTests/NamingConventions/PascalCase"; } @@ -44,7 +46,7 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.Should().ContainPath($"{documentSchemaRefId}.properties").With(propertiesElement => { - propertiesElement.Should().ContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("JsonapiObject"); + propertiesElement.Should().ContainPath("jsonapi.allOf[0].$ref").ShouldBeSchemaReferenceId("Jsonapi"); linksInResourceCollectionDocumentSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") .ShouldBeSchemaReferenceId("LinksInResourceCollectionDocument").SchemaReferenceId; @@ -63,18 +65,23 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() propertiesElement.Should().ContainProperty("next"); }); - string? linksInResourceObjectSchemaRefId = null; - string? primaryResourceTypeSchemaRefId = null; + string? linksInResourceDataSchemaRefId = null; string? resourceAttributesInResponseSchemaRefId = null; string? resourceRelationshipInResponseSchemaRefId = null; - schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => + string abstractResourceDataSchemaRefId = schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[0].$ref") + .ShouldBeSchemaReferenceId("DataInResponse").SchemaReferenceId; + + schemasElement.Should().ContainPath($"{abstractResourceDataSchemaRefId}.discriminator.mapping").With(mappingElement => { - linksInResourceObjectSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") - .ShouldBeSchemaReferenceId("LinksInResourceObject").SchemaReferenceId; + mappingElement.Should().ContainPath("Supermarkets").ShouldBeSchemaReferenceId("SupermarketDataInResponse"); + mappingElement.Should().ContainPath("StaffMembers").ShouldBeSchemaReferenceId("StaffMemberDataInResponse"); + }); - primaryResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.allOf[0].$ref") - .ShouldBeSchemaReferenceId("SupermarketResourceType").SchemaReferenceId; + schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[1].properties").With(propertiesElement => + { + linksInResourceDataSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref").ShouldBeSchemaReferenceId("LinksInResourceData") + .SchemaReferenceId; resourceAttributesInResponseSchemaRefId = propertiesElement.Should().ContainPath("attributes.allOf[0].$ref") .ShouldBeSchemaReferenceId("SupermarketAttributesInResponse").SchemaReferenceId; @@ -83,16 +90,11 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() .ShouldBeSchemaReferenceId("SupermarketRelationshipsInResponse").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{linksInResourceObjectSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{linksInResourceDataSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("self"); }); - schemasElement.Should().ContainPath($"{primaryResourceTypeSchemaRefId}.enum[0]").With(enumValueElement => - { - enumValueElement.Should().Be("Supermarkets"); - }); - schemasElement.Should().ContainPath($"{resourceAttributesInResponseSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("NameOfCity"); @@ -115,19 +117,19 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() propertiesElement.Should().ContainPath("Cashiers.allOf[0].$ref").ShouldBeSchemaReferenceId("ToManyStaffMemberInResponse"); }); - string? linksInRelationshipObjectSchemaRefId = null; + string? linksInRelationshipSchemaRefId = null; string? relatedResourceIdentifierSchemaRefId = null; schemasElement.Should().ContainPath($"{nullableToOneResourceResponseDataSchemaRefId}.properties").With(propertiesElement => { - linksInRelationshipObjectSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref") - .ShouldBeSchemaReferenceId("LinksInRelationshipObject").SchemaReferenceId; + linksInRelationshipSchemaRefId = propertiesElement.Should().ContainPath("links.allOf[0].$ref").ShouldBeSchemaReferenceId("LinksInRelationship") + .SchemaReferenceId; relatedResourceIdentifierSchemaRefId = propertiesElement.Should().ContainPath("data.allOf[0].$ref") .ShouldBeSchemaReferenceId("StaffMemberIdentifier").SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{linksInRelationshipObjectSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{linksInRelationshipSchemaRefId}.properties").With(propertiesElement => { propertiesElement.Should().ContainProperty("self"); propertiesElement.Should().ContainProperty("related"); @@ -137,11 +139,14 @@ public async Task Casing_convention_is_applied_to_GetCollection_endpoint() schemasElement.Should().ContainPath($"{relatedResourceIdentifierSchemaRefId}.properties").With(propertiesElement => { - relatedResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.allOf[0].$ref") - .ShouldBeSchemaReferenceId("StaffMemberResourceType").SchemaReferenceId; + relatedResourceTypeSchemaRefId = propertiesElement.Should().ContainPath("type.$ref").ShouldBeSchemaReferenceId("StaffMemberResourceType") + .SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").ShouldBeSchemaReferenceId("StaffMembers"); + schemasElement.Should().ContainPath($"{relatedResourceTypeSchemaRefId}.enum[0]").With(enumValueElement => + { + enumValueElement.Should().Be("StaffMembers"); + }); }); } @@ -213,7 +218,7 @@ public async Task Casing_convention_is_applied_to_GetSecondary_endpoint_with_sin .SchemaReferenceId; }); - schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.properties").With(propertiesElement => + schemasElement.Should().ContainPath($"{resourceDataSchemaRefId}.allOf[1].properties").With(propertiesElement => { propertiesElement.Should().ContainPath("attributes.allOf[0].$ref").ShouldBeSchemaReferenceId("StaffMemberAttributesInResponse"); }); diff --git a/test/OpenApiTests/QueryStrings/IncludeTests.cs b/test/OpenApiTests/QueryStrings/IncludeTests.cs new file mode 100644 index 0000000000..3151562da0 --- /dev/null +++ b/test/OpenApiTests/QueryStrings/IncludeTests.cs @@ -0,0 +1,75 @@ +using System.Text.Json; +using FluentAssertions; +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using TestBuildingBlocks; +using Xunit; + +namespace OpenApiTests.QueryStrings; + +public sealed class IncludeTests : IClassFixture, QueryStringsDbContext>> +{ + private readonly OpenApiTestContext, QueryStringsDbContext> _testContext; + + public IncludeTests(OpenApiTestContext, QueryStringsDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Discriminator_is_generated_for_all_resource_types_when_subset_of_endpoints_is_exposed() + { + // Arrange + var resourceGraph = _testContext.Factory.Services.GetRequiredService(); + int count = resourceGraph.GetResourceTypes().Count; + + // Act + JsonElement document = await _testContext.GetSwaggerDocumentAsync(); + + // Assert + document.Should().ContainPath("components.schemas").With(schemasElement => + { + var discriminatorRefIds = new List(); + + schemasElement.Should().ContainPath("dataInResponse.discriminator").With(discriminatorElement => + { + discriminatorElement.Should().HaveProperty("propertyName", "type"); + + discriminatorElement.Should().ContainPath("mapping").With(mappingElement => + { + foreach (JsonProperty jsonProperty in mappingElement.EnumerateObject()) + { + string discriminatorRefId = jsonProperty.Value.GetSchemaReferenceId(); + discriminatorRefIds.Add(discriminatorRefId); + } + }); + }); + + discriminatorRefIds.Should().HaveCount(count); + + foreach (string discriminatorRefId in discriminatorRefIds) + { + schemasElement.Should().ContainPath($"{discriminatorRefId}.allOf[0].$ref").ShouldBeSchemaReferenceId("dataInResponse"); + } + }); + } + + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + private sealed class OnlyGetSingleNodeController( + IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IResourceService resourceService) + : BaseJsonApiController(options, resourceGraph, loggerFactory, resourceService) + { + [HttpGet("{id}")] + public override Task GetAsync(long id, CancellationToken cancellationToken) + { + return base.GetAsync(id, cancellationToken); + } + } +} diff --git a/test/OpenApiTests/QueryStrings/NameValuePair.cs b/test/OpenApiTests/QueryStrings/NameValuePair.cs new file mode 100644 index 0000000000..ab7fd2f61e --- /dev/null +++ b/test/OpenApiTests/QueryStrings/NameValuePair.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace OpenApiTests.QueryStrings; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "OpenApiTests.QueryStrings")] +public sealed class NameValuePair : Identifiable +{ + [Attr] + public string Name { get; set; } = null!; + + [Attr] + public string? Value { get; set; } + + [HasOne] + public Node Owner { get; set; } = null!; +} diff --git a/test/OpenApiTests/QueryStrings/Node.cs b/test/OpenApiTests/QueryStrings/Node.cs index d1010b6197..e9da915295 100644 --- a/test/OpenApiTests/QueryStrings/Node.cs +++ b/test/OpenApiTests/QueryStrings/Node.cs @@ -14,6 +14,9 @@ public sealed class Node : Identifiable [Attr] public string? Comment { get; set; } + [HasMany] + public IList Values { get; set; } = new List(); + [HasOne] public Node? Parent { get; set; } diff --git a/test/OpenApiTests/QueryStrings/QueryStringFakers.cs b/test/OpenApiTests/QueryStrings/QueryStringFakers.cs index 26b9db2798..4800134151 100644 --- a/test/OpenApiTests/QueryStrings/QueryStringFakers.cs +++ b/test/OpenApiTests/QueryStrings/QueryStringFakers.cs @@ -15,5 +15,11 @@ public sealed class QueryStringFakers : FakerContainer .RuleFor(node => node.Name, faker => faker.Lorem.Word()) .RuleFor(node => node.Comment, faker => faker.Lorem.Sentence())); + private readonly Lazy> _lazyNameValuePairFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(node => node.Name, faker => faker.Lorem.Word()) + .RuleFor(node => node.Value, faker => faker.Lorem.Sentence())); + public Faker Node => _lazyNodeFaker.Value; + public Faker NameValuePair => _lazyNameValuePairFaker.Value; } diff --git a/test/OpenApiTests/QueryStrings/QueryStringTests.cs b/test/OpenApiTests/QueryStrings/QueryStringTests.cs index bc7dfb7a6b..ecff8a33ed 100644 --- a/test/OpenApiTests/QueryStrings/QueryStringTests.cs +++ b/test/OpenApiTests/QueryStrings/QueryStringTests.cs @@ -14,6 +14,8 @@ public QueryStringTests(OpenApiTestContext _testContext = testContext; testContext.UseController(); + testContext.UseController(); + testContext.SwaggerDocumentOutputDirectory = "test/OpenApiEndToEndTests/QueryStrings"; } @@ -56,7 +58,7 @@ public async Task Endpoints_have_query_string_parameter(string endpointPath) propertiesElement.Should().HaveProperty("nullable", true); }); - schemaElement.Should().HaveProperty("example", null); + schemaElement.Should().HaveProperty("example", string.Empty); }); }); }); diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/RequiredTests.cs index 6974a721c2..b834f351e0 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOff/RequiredTests.cs @@ -25,10 +25,10 @@ public async Task Schema_property_for_attribute_is_required_for_creating_resourc JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -42,10 +42,10 @@ public async Task Schema_property_for_attribute_is_not_required_for_creating_res JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } @@ -58,10 +58,10 @@ public async Task Schema_property_for_relationship_is_required_for_creating_reso JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -74,10 +74,10 @@ public async Task Schema_property_for_relationship_is_not_required_for_creating_ JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/RequiredTests.cs index bab06b8b23..ee6f46cd1d 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOff/ModelStateValidationOn/RequiredTests.cs @@ -24,10 +24,10 @@ public async Task Schema_property_for_attribute_is_required_for_creating_resourc JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -42,10 +42,10 @@ public async Task Schema_property_for_attribute_is_not_required_for_creating_res JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } @@ -57,10 +57,10 @@ public async Task Schema_property_for_relationship_is_required_for_creating_reso JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -74,10 +74,10 @@ public async Task Schema_property_for_relationship_is_not_required_for_creating_ JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/RequiredTests.cs index 7020004021..536bcb3456 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOff/RequiredTests.cs @@ -26,10 +26,10 @@ public async Task Schema_property_for_attribute_is_required_for_creating_resourc JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -44,10 +44,10 @@ public async Task Schema_property_for_attribute_is_not_required_for_creating_res JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } @@ -61,10 +61,10 @@ public async Task Schema_property_for_relationship_is_required_for_creating_reso JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -78,10 +78,10 @@ public async Task Schema_property_for_relationship_is_not_required_for_creating_ JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } diff --git a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/RequiredTests.cs b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/RequiredTests.cs index e33a4ae3d7..c2010167bb 100644 --- a/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/RequiredTests.cs +++ b/test/OpenApiTests/ResourceFieldValidation/NullableReferenceTypesOn/ModelStateValidationOn/RequiredTests.cs @@ -26,10 +26,10 @@ public async Task Schema_property_for_attribute_is_required_for_creating_resourc JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -44,10 +44,10 @@ public async Task Schema_property_for_attribute_is_not_required_for_creating_res JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesObjectSchema => + document.Should().ContainPath("components.schemas.resourceAttributesInPostRequest").With(attributesSchema => { - attributesObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - attributesObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + attributesSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + attributesSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } @@ -61,10 +61,10 @@ public async Task Schema_property_for_relationship_is_required_for_creating_reso JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().ContainArrayElement(jsonPropertyName)); }); } @@ -78,10 +78,10 @@ public async Task Schema_property_for_relationship_is_not_required_for_creating_ JsonElement document = await _testContext.GetSwaggerDocumentAsync(); // Assert - document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsObjectSchema => + document.Should().ContainPath("components.schemas.resourceRelationshipsInPostRequest").With(relationshipsSchema => { - relationshipsObjectSchema.Should().ContainPath($"properties.{jsonPropertyName}"); - relationshipsObjectSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); + relationshipsSchema.Should().ContainPath($"properties.{jsonPropertyName}"); + relationshipsSchema.Should().ContainPath("required").With(propertySet => propertySet.Should().NotContainArrayElement(jsonPropertyName)); }); } diff --git a/test/TestBuildingBlocks/JsonElementAssertionExtensions.cs b/test/TestBuildingBlocks/JsonElementAssertionExtensions.cs index f71ca9d84b..e9688234b1 100644 --- a/test/TestBuildingBlocks/JsonElementAssertionExtensions.cs +++ b/test/TestBuildingBlocks/JsonElementAssertionExtensions.cs @@ -8,6 +8,8 @@ namespace TestBuildingBlocks; public static class JsonElementAssertionExtensions { + private const string ComponentSchemaPrefix = "#/components/schemas/"; + public static JsonElementAssertions Should(this JsonElement source) { return new JsonElementAssertions(source); @@ -23,14 +25,14 @@ public static SchemaReferenceIdContainer ShouldBeSchemaReferenceId(this JsonElem } [CustomAssertion] - private static string GetSchemaReferenceId(this JsonElement source) + public static string GetSchemaReferenceId(this JsonElement source) { source.ValueKind.Should().Be(JsonValueKind.String); string? jsonElementValue = source.GetString(); - jsonElementValue.ShouldNotBeNull(); + jsonElementValue.Should().StartWith(ComponentSchemaPrefix); - return jsonElementValue.Split('/').Last(); + return jsonElementValue![ComponentSchemaPrefix.Length..]; } [CustomAssertion]