From a87fb1dccbd1cef8619ff24f46c5d447cfba9119 Mon Sep 17 00:00:00 2001 From: Konstantin Babushkin Date: Mon, 25 Nov 2024 10:30:10 +0100 Subject: [PATCH] Support [JsonPolymorphic] and [JsonDerivedType] (#3170) * Support of JsonPolymorphicAttribute Fix integration Basic and NSwagClientExample tests to have different outputs for .net6 * Apply suggestions from code review Co-authored-by: Martin Costello * add `Produces` to NSwagClientExmaple to strip unnecessary fields fix `SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6` test - it wasn't using parameters * Add README from https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2671/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5 * Apply suggestions from code review --------- Co-authored-by: martincostello Co-authored-by: schnerring <3743342+schnerring@users.noreply.github.com> --- README.md | 39 +- .../AnnotationsSwaggerGenOptionsExtensions.cs | 34 + ...lidSwaggerJson_Basic_DotNet_6.verified.txt | 1610 ---------------- ...lidSwaggerJson_Basic_DotNet_8.verified.txt | 1609 ---------------- ....Startup_swaggerRequestUri=v1.verified.txt | 1636 +++++++++++++++++ ....Startup_swaggerRequestUri=v1.verified.txt | 225 +++ ....Startup_swaggerRequestUri=v1.verified.txt | 128 +- .../SwaggerVerifyIntegrationTest.cs | 27 +- .../Controllers/AnimalsController.cs | 1 + .../Controllers/SecondLevelController.cs | 1 + .../SystemTextJsonAnimalsController.cs | 39 + .../NswagClientExample/swagger_net6.0.json | 17 +- .../NswagClientExample/swagger_net8.0.json | 133 +- .../NswagClientExample/swagger_net9.0.json | 133 +- 14 files changed, 2341 insertions(+), 3291 deletions(-) delete mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6.verified.txt delete mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8.verified.txt create mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt create mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt create mode 100644 test/WebSites/NswagClientExample/Controllers/SystemTextJsonAnimalsController.cs diff --git a/README.md b/README.md index 7e49c770e8..d254c3cc92 100644 --- a/README.md +++ b/README.md @@ -1166,7 +1166,8 @@ services.AddSwaggerGen(c => }); ``` -_NOTE: If you're using the [Swashbuckle Annotations library](#swashbuckleaspnetcoreannotations), it contains a custom selector that's based on the presence of `SwaggerSubType` attributes on base class definitions. This way, you can use simple attributes to explicitly list the inheritance and/or polymorphism relationships you want to expose. To enable this behavior, check out the [Annotations docs](#list-known-subtypes-for-inheritance-and-polymorphism)._ +> [!NOTE] +> If you're using the [Swashbuckle Annotations library](#swashbuckleaspnetcoreannotations), it contains a custom selector that's based on the presence of `[JsonDerivedType]` (or `[SwaggerSubType]` for .NET 6 or earlier) attributes on base class definitions. This way, you can use simple attributes to explicitly list the inheritance and/or polymorphism relationships you want to expose. To enable this behavior, check out the [Annotations docs](#list-known-subtypes-for-inheritance-and-polymorphism). #### Describing Discriminators #### @@ -1232,7 +1233,8 @@ services.AddSwaggerGen(c => }); ``` -_NOTE: If you're using the [Swashbuckle Annotations library](#swashbuckleaspnetcoreannotations), it contains custom selector functions that are based on the presence of `SwaggerDiscriminator` and `SwaggerSubType` attributes on base class definitions. This way, you can use simple attributes to explicitly provide discriminator metadata. To enable this behavior, check out the [Annotations docs](#enrich-polymorphic-base-classes-with-discriminator-metadata)._ +> [!NOTE] +> If you're using the [Swashbuckle Annotations library](#swashbuckleaspnetcoreannotations), it contains custom selector functions that are based on the presence of `[JsonPolymorphic]` (or `[SwaggerDiscriminator]` for .NET 6 or earlier) and `[JsonDerivedType]` (or `[SwaggerSubType]` for .NET 6 or earlier) attributes on base class definitions. This way, you can use simple attributes to explicitly provide discriminator metadata. To enable this behavior, check out the [Annotations docs](#enrich-polymorphic-base-classes-with-discriminator-metadata). ## Swashbuckle.AspNetCore.SwaggerUI ## @@ -1540,6 +1542,15 @@ services.AddSwaggerGen(c => }); // Shape.cs + +// .NET 7 or later +[JsonDerivedType(typeof(Rectangle))] +[JsonDerivedType(typeof(Circle))] +public abstract class Shape +{ +} + +// .NET 6 or earlier [SwaggerSubType(typeof(Rectangle))] [SwaggerSubType(typeof(Circle))] public abstract class Shape @@ -1549,7 +1560,7 @@ public abstract class Shape ### Enrich Polymorphic Base Classes with Discriminator Metadata ### -If you're using annotations to _explicitly_ indicate the "known" subtypes for a polymorphic base type, you can combine the `SwaggerDiscriminatorAttribute` with the `SwaggerSubTypeAttribute` to provide additional metadata about the "discriminator" property, which will then be incorporated into the generated schema definition: +If you're using annotations to _explicitly_ indicate the "known" subtypes for a polymorphic base type, you can combine the `JsonPolymorphicAttribute` with the `JsonDerivedTypeAttribute` to provide additional metadata about the "discriminator" property, which will then be incorporated into the generated schema definition: ```csharp @@ -1560,12 +1571,32 @@ services.AddSwaggerGen(c => }); // Shape.cs + +// .NET 7 or later +[JsonPolymorphic(TypeDiscriminatorPropertyName = "shapeType")] +[JsonDerivedType(typeof(Rectangle), "rectangle")] +[JsonDerivedType(typeof(Circle), "circle")] +public abstract class Shape +{ + // Avoid using a JsonPolymorphicAttribute.TypeDiscriminatorPropertyName + // that conflicts with a property in your type hierarchy. + // Related issue: https://github.com/dotnet/runtime/issues/72170 +} + +// .NET 6 or earlier [SwaggerDiscriminator("shapeType")] [SwaggerSubType(typeof(Rectangle), DiscriminatorValue = "rectangle")] [SwaggerSubType(typeof(Circle), DiscriminatorValue = "circle")] public abstract class Shape { - public ShapeType { get; set; } + public ShapeType ShapeType { get; set; } +} + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ShapeType +{ + Circle, + Rectangle } ``` diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs index e0422cb8a3..bb4f02b10b 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.Annotations; @@ -75,6 +76,17 @@ private static IEnumerable AnnotationsSubTypesSelector(Type type) return obsoleteAttribute.SubTypes; } +#if NET7_0_OR_GREATER + var jsonDerivedTypeAttributes = type.GetCustomAttributes(false) + .OfType() + .ToList(); + + if (jsonDerivedTypeAttributes.Count > 0) + { + return jsonDerivedTypeAttributes.Select(attr => attr.DerivedType); + } +#endif + return Enumerable.Empty(); } @@ -100,6 +112,17 @@ private static string AnnotationsDiscriminatorNameSelector(Type baseType) return obsoleteAttribute.Discriminator; } +#if NET7_0_OR_GREATER + var jsonPolymorphicAttributes = baseType.GetCustomAttributes(false) + .OfType() + .FirstOrDefault(); + + if (jsonPolymorphicAttributes != null) + { + return jsonPolymorphicAttributes.TypeDiscriminatorPropertyName; + } +#endif + return null; } @@ -117,6 +140,17 @@ private static string AnnotationsDiscriminatorValueSelector(Type subType) return subTypeAttribute.DiscriminatorValue; } +#if NET7_0_OR_GREATER + var jsonDerivedTypeAttributes = baseType.GetCustomAttributes(false) + .OfType() + .FirstOrDefault(attr => attr.DerivedType == subType); + + if (jsonDerivedTypeAttributes is { TypeDiscriminator: string discriminator }) + { + return discriminator; + } +#endif + baseType = baseType.BaseType; } diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6.verified.txt deleted file mode 100644 index 7ba505525a..0000000000 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6.verified.txt +++ /dev/null @@ -1,1610 +0,0 @@ -{ - openapi: 3.0.1, - info: { - title: Test API V1, - description: A sample API for testing Swashbuckle, - termsOfService: http://tempuri.org/terms, - version: v1 - }, - paths: { - /products: { - post: { - tags: [ - CrudActions - ], - summary: Creates a product, - description: -## Heading 1 - - POST /products - { - "id": "123", - "description": "Some product" - }, - operationId: CreateProduct, - requestBody: { - description: , - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/Product - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - } - } - } - }, - x-purpose: test - }, - get: { - tags: [ - CrudActions - ], - summary: Searches the collection of products by description key words, - operationId: SearchProducts, - parameters: [ - { - name: kw, - in: query, - description: A list of search terms, - schema: { - type: string, - default: foobar - }, - example: hello - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: array, - items: { - $ref: #/components/schemas/Product - } - } - } - } - } - }, - x-purpose: test - } - }, - /products/{id}: { - get: { - tags: [ - CrudActions - ], - summary: Returns a specific product, - operationId: GetProduct, - parameters: [ - { - name: id, - in: path, - description: The product id, - required: true, - schema: { - type: integer, - format: int32 - }, - example: 111 - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - } - } - } - }, - x-purpose: test - }, - put: { - tags: [ - CrudActions - ], - summary: Updates all properties of a specific product, - operationId: UpdateProduct, - parameters: [ - { - name: id, - in: path, - description: , - required: true, - schema: { - type: integer, - format: int32 - }, - example: 222 - } - ], - requestBody: { - description: , - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/Product - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - }, - patch: { - tags: [ - CrudActions - ], - summary: Updates some properties of a specific product, - operationId: PatchProduct, - parameters: [ - { - name: id, - in: path, - description: , - required: true, - schema: { - type: integer, - format: int32 - }, - example: 333 - } - ], - requestBody: { - description: , - content: { - application/json: { - schema: { - type: object - } - }, - text/json: { - schema: { - type: object - } - }, - application/*+json: { - schema: { - type: object - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - }, - delete: { - tags: [ - CrudActions - ], - summary: Deletes a specific product, - operationId: DeleteProduct, - parameters: [ - { - name: id, - in: path, - description: , - required: true, - schema: { - type: integer, - format: int32 - }, - example: 444 - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /payments/authorize: { - post: { - tags: [ - DataAnnotations - ], - requestBody: { - content: { - application/json: { - schema: { - $ref: #/components/schemas/PaymentRequest - } - }, - text/json: { - schema: { - $ref: #/components/schemas/PaymentRequest - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/PaymentRequest - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: string - } - } - } - } - }, - x-purpose: test - } - }, - /payments/{paymentId}/cancel: { - put: { - tags: [ - DataAnnotations - ], - parameters: [ - { - name: paymentId, - in: path, - required: true, - schema: { - minLength: 6, - type: string - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /kittens: { - post: { - tags: [ - DynamicTypes - ], - requestBody: { - content: { - application/json: {}, - text/json: {}, - application/*+json: {} - }, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: integer, - format: int32 - } - } - } - } - }, - x-purpose: test - } - }, - /unicorns: { - get: { - tags: [ - DynamicTypes - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: object - } - } - } - } - }, - x-purpose: test - } - }, - /dragons: { - post: { - tags: [ - DynamicTypes - ], - requestBody: { - content: { - application/json: {}, - text/json: {}, - application/*+json: {} - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/single: { - post: { - tags: [ - Files - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - file: { - type: string, - format: binary - } - } - }, - encoding: { - file: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/multiple: { - post: { - tags: [ - Files - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - files: { - type: array, - items: { - type: string, - format: binary - } - } - } - }, - encoding: { - files: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/form-with-file: { - post: { - tags: [ - Files - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - name: { - type: string - }, - file: { - type: string, - format: binary - } - } - }, - encoding: { - name: { - style: form - }, - file: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/{name}: { - get: { - tags: [ - Files - ], - parameters: [ - { - name: name, - in: path, - required: true, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - oneOf: [ - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - } - ] - } - }, - application/zip: { - schema: { - oneOf: [ - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - } - ] - } - } - } - } - }, - x-purpose: test - } - }, - /registrations: { - post: { - tags: [ - FromFormParams - ], - summary: Form parameters with description, - requestBody: { - content: { - application/x-www-form-urlencoded: { - schema: { - type: object, - properties: { - name: { - type: string, - description: Summary for Name, - example: MyName - }, - phoneNumbers: { - type: array, - items: { - type: integer, - format: int32 - }, - description: Sumary for PhoneNumbers - }, - formFile: { - type: string, - description: Description for file, - format: binary - }, - text: { - type: string, - description: Description for Text - } - } - }, - encoding: { - name: { - style: form - }, - phoneNumbers: { - style: form - }, - formFile: { - style: form - }, - text: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /registrationsWithIgnoreProperties: { - post: { - tags: [ - FromFormParams - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - phoneNumbers: { - type: array, - items: { - type: integer, - format: int32 - } - } - } - }, - encoding: { - phoneNumbers: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /registrationsWithEnumParameter: { - post: { - tags: [ - FromFormParams - ], - summary: Form parameters with description, - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - name: { - type: string, - description: Summary for Name, - example: MyName - }, - phoneNumbers: { - type: array, - items: { - type: integer, - format: int32 - }, - description: Sumary for PhoneNumbers - }, - logLevel: { - $ref: #/components/schemas/LogLevel - }, - formFile: { - type: string, - description: Description for file, - format: binary - }, - dateTimeKind: { - $ref: #/components/schemas/DateTimeKind - } - } - }, - encoding: { - name: { - style: form - }, - phoneNumbers: { - style: form - }, - logLevel: { - style: form - }, - formFile: { - style: form - }, - dateTimeKind: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /country/validate: { - get: { - tags: [ - FromHeaderParams - ], - parameters: [ - { - name: country, - in: query, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /addresses/validate: { - get: { - tags: [ - FromQueryParams - ], - parameters: [ - { - name: country, - in: query, - description: 3-letter ISO country code, - required: true, - schema: { - type: string - } - }, - { - name: city, - in: query, - description: Name of city, - schema: { - type: string, - default: Seattle - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /zip-codes/validate: { - get: { - tags: [ - FromQueryParams - ], - parameters: [ - { - name: zipCodes, - in: query, - schema: { - type: array, - items: { - type: string - } - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /Issue3013/Get: { - get: { - tags: [ - Issue3013 - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/TestResponse - } - }, - application/json: { - schema: { - $ref: #/components/schemas/TestResponse - } - }, - text/json: { - schema: { - $ref: #/components/schemas/TestResponse - } - } - } - } - }, - x-purpose: test - } - }, - /promotions: { - get: { - tags: [ - JsonAnnotations - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: array, - items: { - $ref: #/components/schemas/Promotion - } - } - } - } - } - }, - x-purpose: test - } - }, - /shapes: { - post: { - tags: [ - PolymorphicTypes - ], - requestBody: { - content: { - application/json: { - schema: { - oneOf: [ - null, - null - ] - } - }, - text/json: { - schema: { - oneOf: [ - null, - null - ] - } - }, - application/*+json: { - schema: { - oneOf: [ - null, - null - ] - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - type: integer, - format: int32 - } - }, - application/json: { - schema: { - type: integer, - format: int32 - } - }, - text/json: { - schema: { - type: integer, - format: int32 - } - } - } - } - }, - x-purpose: test - } - }, - /orders: { - post: { - tags: [ - ResponseTypeAnnotations - ], - summary: Creates an order, - requestBody: { - description: , - content: { - application/xml: { - schema: { - $ref: #/components/schemas/Order - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 201: { - description: Order created, - content: { - application/xml: { - schema: { - type: integer, - format: int32 - } - } - } - }, - 400: { - description: Order invalid, - content: { - application/xml: { - schema: { - $ref: #/components/schemas/ValidationProblemDetails - } - } - } - } - }, - x-purpose: test - } - }, - /carts: { - post: { - tags: [ - SwaggerAnnotations - ], - operationId: CreateCart, - requestBody: { - description: The cart request body, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/Cart - } - } - }, - x-purpose: test - }, - responses: { - 201: { - description: The cart was created, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - } - } - }, - 400: { - description: The cart data is invalid - } - }, - x-purpose: test - } - }, - /carts/{id}: { - get: { - tags: [ - SwaggerAnnotations - ], - externalDocs: { - description: External docs for CartsByIdGet, - url: https://tempuri.org/carts-by-id-get - }, - operationId: GetCart, - parameters: [ - { - name: id, - in: path, - description: The cart identifier, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - } - } - } - }, - x-purpose: test - }, - delete: { - tags: [ - SwaggerAnnotations - ], - summary: Deletes a specific cart, - description: Requires admin privileges, - operationId: DeleteCart, - parameters: [ - { - name: id, - in: path, - description: The cart identifier, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - } - } - } - }, - x-purpose: test - } - }, - /stores: { - post: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: query, - schema: { - type: integer, - format: int32 - } - }, - { - name: location, - in: query, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: integer, - format: int32 - } - } - } - } - }, - x-purpose: test - }, - get: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: locations, - in: query, - schema: { - type: array, - items: { - type: string - } - } - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: array, - items: { - $ref: #/components/schemas/Store - } - } - } - } - } - }, - x-purpose: test - } - }, - /stores/{id}: { - get: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: path, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Store - } - } - } - } - }, - x-purpose: test - }, - put: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: path, - required: true, - schema: { - type: integer, - format: int32 - } - }, - { - name: id, - in: query, - schema: { - type: integer, - format: int32 - } - }, - { - name: location, - in: query, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - }, - delete: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: path, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - } - }, - components: { - schemas: { - Cart: { - required: [ - Id - ], - type: object, - properties: { - id: { - type: integer, - description: The cart identifier, - format: int32, - readOnly: true - }, - cartType: { - $ref: #/components/schemas/CartType - } - }, - additionalProperties: false - }, - CartType: { - enum: [ - 0, - 1 - ], - type: integer, - description: The cart type, - format: int32 - }, - Circle: { - allOf: [ - null, - { - type: object, - properties: { - radius: { - type: integer, - format: int32 - } - }, - additionalProperties: false - } - ] - }, - CreditCard: { - required: [ - cardNumber, - expMonth, - expYear - ], - type: object, - properties: { - cardNumber: { - minLength: 1, - pattern: ^[3-6]?\d{12,15}$, - type: string - }, - expMonth: { - maximum: 12, - minimum: 1, - type: integer, - format: int32 - }, - expYear: { - maximum: 99, - minimum: 14, - type: integer, - format: int32 - } - }, - additionalProperties: false - }, - DateTimeKind: { - enum: [ - 0, - 1, - 2 - ], - type: integer, - format: int32 - }, - DiscountType: { - enum: [ - Percentage, - Amount - ], - type: string - }, - LogLevel: { - enum: [ - 0, - 1, - 2, - 3, - 4, - 5, - 6 - ], - type: integer, - format: int32 - }, - Order: { - type: object, - properties: { - id: { - type: integer, - format: int32 - }, - description: { - type: string, - nullable: true - }, - total: { - type: number, - format: double - } - }, - additionalProperties: false - }, - PaymentRequest: { - required: [ - creditCard, - transaction - ], - type: object, - properties: { - transaction: { - $ref: #/components/schemas/Transaction - }, - creditCard: { - $ref: #/components/schemas/CreditCard - } - }, - additionalProperties: false - }, - Product: { - type: object, - properties: { - id: { - type: integer, - description: Uniquely identifies the product, - format: int32 - }, - description: { - type: string, - description: Describes the product, - nullable: true - }, - status: { - $ref: #/components/schemas/ProductStatus - }, - status2: { - $ref: #/components/schemas/ProductStatus - } - }, - additionalProperties: false, - description: Represents a product, - example: { - id: 123, - description: foobar, - price: 14.37 - } - }, - ProductStatus: { - enum: [ - 0, - 1, - 2 - ], - type: integer, - format: int32 - }, - Promotion: { - type: object, - properties: { - promo-code: { - type: string, - nullable: true - }, - discountType: { - $ref: #/components/schemas/DiscountType - } - }, - additionalProperties: false - }, - Rectangle: { - allOf: [ - null, - { - type: object, - properties: { - height: { - type: integer, - format: int32 - }, - width: { - type: integer, - format: int32 - } - }, - additionalProperties: false - } - ] - }, - Shape: { - required: [ - TypeName - ], - type: object, - properties: { - TypeName: { - type: string - }, - name: { - type: string, - nullable: true - } - }, - additionalProperties: false, - discriminator: { - propertyName: TypeName, - mapping: { - Rectangle: #/components/schemas/Rectangle, - Circle: #/components/schemas/Circle - } - } - }, - Store: { - type: object, - properties: { - id: { - type: integer, - format: int32 - }, - location: { - type: string, - nullable: true - } - }, - additionalProperties: false - }, - TestResponse: { - type: object, - properties: { - foo: { - $ref: #/components/schemas/TestStruct - } - }, - additionalProperties: false - }, - TestStruct: { - type: object, - properties: { - a: { - type: integer, - format: int32 - }, - b: { - type: integer, - format: int32 - } - }, - additionalProperties: false - }, - Transaction: { - required: [ - amount - ], - type: object, - properties: { - amount: { - type: number, - format: double - }, - note: { - type: string, - nullable: true - } - }, - additionalProperties: false - }, - ValidationProblemDetails: { - type: object, - properties: { - type: { - type: string, - nullable: true - }, - title: { - type: string, - nullable: true - }, - status: { - type: integer, - format: int32, - nullable: true - }, - detail: { - type: string, - nullable: true - }, - instance: { - type: string, - nullable: true - }, - errors: { - type: object, - additionalProperties: { - type: array, - items: { - type: string - } - }, - nullable: true, - readOnly: true - } - } - } - } - }, - tags: [ - { - name: SwaggerAnnotations, - description: Manipulate Carts to your heart's content, - externalDocs: { - url: http://www.tempuri.org - } - } - ] -} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8.verified.txt deleted file mode 100644 index 80bafac961..0000000000 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8.verified.txt +++ /dev/null @@ -1,1609 +0,0 @@ -{ - openapi: 3.0.1, - info: { - title: Test API V1, - description: A sample API for testing Swashbuckle, - termsOfService: http://tempuri.org/terms, - version: v1 - }, - paths: { - /products: { - post: { - tags: [ - CrudActions - ], - summary: Creates a product, - description: -## Heading 1 - - POST /products - { - "id": "123", - "description": "Some product" - }, - operationId: CreateProduct, - requestBody: { - description: , - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/Product - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - } - } - } - }, - x-purpose: test - }, - get: { - tags: [ - CrudActions - ], - summary: Searches the collection of products by description key words, - operationId: SearchProducts, - parameters: [ - { - name: kw, - in: query, - description: A list of search terms, - schema: { - type: string, - default: foobar - }, - example: hello - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: array, - items: { - $ref: #/components/schemas/Product - } - } - } - } - } - }, - x-purpose: test - } - }, - /products/{id}: { - get: { - tags: [ - CrudActions - ], - summary: Returns a specific product, - operationId: GetProduct, - parameters: [ - { - name: id, - in: path, - description: The product id, - required: true, - schema: { - type: integer, - format: int32 - }, - example: 111 - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - } - } - } - }, - x-purpose: test - }, - put: { - tags: [ - CrudActions - ], - summary: Updates all properties of a specific product, - operationId: UpdateProduct, - parameters: [ - { - name: id, - in: path, - description: , - required: true, - schema: { - type: integer, - format: int32 - }, - example: 222 - } - ], - requestBody: { - description: , - content: { - application/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Product - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/Product - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - }, - patch: { - tags: [ - CrudActions - ], - summary: Updates some properties of a specific product, - operationId: PatchProduct, - parameters: [ - { - name: id, - in: path, - description: , - required: true, - schema: { - type: integer, - format: int32 - }, - example: 333 - } - ], - requestBody: { - description: , - content: { - application/json: { - schema: { - type: object - } - }, - text/json: { - schema: { - type: object - } - }, - application/*+json: { - schema: { - type: object - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - }, - delete: { - tags: [ - CrudActions - ], - summary: Deletes a specific product, - operationId: DeleteProduct, - parameters: [ - { - name: id, - in: path, - description: , - required: true, - schema: { - type: integer, - format: int32 - }, - example: 444 - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /payments/authorize: { - post: { - tags: [ - DataAnnotations - ], - requestBody: { - content: { - application/json: { - schema: { - $ref: #/components/schemas/PaymentRequest - } - }, - text/json: { - schema: { - $ref: #/components/schemas/PaymentRequest - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/PaymentRequest - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: string - } - } - } - } - }, - x-purpose: test - } - }, - /payments/{paymentId}/cancel: { - put: { - tags: [ - DataAnnotations - ], - parameters: [ - { - name: paymentId, - in: path, - required: true, - schema: { - minLength: 6, - type: string - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /kittens: { - post: { - tags: [ - DynamicTypes - ], - requestBody: { - content: { - application/json: {}, - text/json: {}, - application/*+json: {} - }, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: integer, - format: int32 - } - } - } - } - }, - x-purpose: test - } - }, - /unicorns: { - get: { - tags: [ - DynamicTypes - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: object - } - } - } - } - }, - x-purpose: test - } - }, - /dragons: { - post: { - tags: [ - DynamicTypes - ], - requestBody: { - content: { - application/json: {}, - text/json: {}, - application/*+json: {} - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/single: { - post: { - tags: [ - Files - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - file: { - type: string, - format: binary - } - } - }, - encoding: { - file: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/multiple: { - post: { - tags: [ - Files - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - files: { - type: array, - items: { - type: string, - format: binary - } - } - } - }, - encoding: { - files: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/form-with-file: { - post: { - tags: [ - Files - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - name: { - type: string - }, - file: { - type: string, - format: binary - } - } - }, - encoding: { - name: { - style: form - }, - file: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /files/{name}: { - get: { - tags: [ - Files - ], - parameters: [ - { - name: name, - in: path, - required: true, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - oneOf: [ - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - } - ] - } - }, - application/zip: { - schema: { - oneOf: [ - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - }, - { - type: string, - format: binary - } - ] - } - } - } - } - }, - x-purpose: test - } - }, - /registrations: { - post: { - tags: [ - FromFormParams - ], - summary: Form parameters with description, - requestBody: { - content: { - application/x-www-form-urlencoded: { - schema: { - type: object, - properties: { - name: { - type: string, - description: Summary for Name, - example: MyName - }, - phoneNumbers: { - type: array, - items: { - type: integer, - format: int32 - }, - description: Sumary for PhoneNumbers - }, - formFile: { - type: string, - description: Description for file, - format: binary - }, - text: { - type: string, - description: Description for Text - } - } - }, - encoding: { - name: { - style: form - }, - phoneNumbers: { - style: form - }, - formFile: { - style: form - }, - text: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /registrationsWithIgnoreProperties: { - post: { - tags: [ - FromFormParams - ], - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - phoneNumbers: { - type: array, - items: { - type: integer, - format: int32 - } - } - } - }, - encoding: { - phoneNumbers: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /registrationsWithEnumParameter: { - post: { - tags: [ - FromFormParams - ], - summary: Form parameters with description, - requestBody: { - content: { - multipart/form-data: { - schema: { - type: object, - properties: { - name: { - type: string, - description: Summary for Name, - example: MyName - }, - phoneNumbers: { - type: array, - items: { - type: integer, - format: int32 - }, - description: Sumary for PhoneNumbers - }, - logLevel: { - $ref: #/components/schemas/LogLevel - }, - formFile: { - type: string, - description: Description for file, - format: binary - }, - dateTimeKind: { - $ref: #/components/schemas/DateTimeKind - } - } - }, - encoding: { - name: { - style: form - }, - phoneNumbers: { - style: form - }, - logLevel: { - style: form - }, - formFile: { - style: form - }, - dateTimeKind: { - style: form - } - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /country/validate: { - get: { - tags: [ - FromHeaderParams - ], - parameters: [ - { - name: country, - in: query, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /addresses/validate: { - get: { - tags: [ - FromQueryParams - ], - parameters: [ - { - name: country, - in: query, - description: 3-letter ISO country code, - required: true, - schema: { - type: string - } - }, - { - name: city, - in: query, - description: Name of city, - schema: { - type: string, - default: Seattle - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /zip-codes/validate: { - get: { - tags: [ - FromQueryParams - ], - parameters: [ - { - name: zipCodes, - in: query, - schema: { - type: array, - items: { - type: string - } - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - }, - /Issue3013/Get: { - get: { - tags: [ - Issue3013 - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/TestResponse - } - }, - application/json: { - schema: { - $ref: #/components/schemas/TestResponse - } - }, - text/json: { - schema: { - $ref: #/components/schemas/TestResponse - } - } - } - } - }, - x-purpose: test - } - }, - /promotions: { - get: { - tags: [ - JsonAnnotations - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: array, - items: { - $ref: #/components/schemas/Promotion - } - } - } - } - } - }, - x-purpose: test - } - }, - /shapes: { - post: { - tags: [ - PolymorphicTypes - ], - requestBody: { - content: { - application/json: { - schema: { - oneOf: [ - null, - null - ] - } - }, - text/json: { - schema: { - oneOf: [ - null, - null - ] - } - }, - application/*+json: { - schema: { - oneOf: [ - null, - null - ] - } - } - }, - x-purpose: test - }, - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - type: integer, - format: int32 - } - }, - application/json: { - schema: { - type: integer, - format: int32 - } - }, - text/json: { - schema: { - type: integer, - format: int32 - } - } - } - } - }, - x-purpose: test - } - }, - /orders: { - post: { - tags: [ - ResponseTypeAnnotations - ], - summary: Creates an order, - requestBody: { - description: , - content: { - application/xml: { - schema: { - $ref: #/components/schemas/Order - } - } - }, - required: true, - x-purpose: test - }, - responses: { - 201: { - description: Order created, - content: { - application/xml: { - schema: { - type: integer, - format: int32 - } - } - } - }, - 400: { - description: Order invalid, - content: { - application/xml: { - schema: { - $ref: #/components/schemas/ValidationProblemDetails - } - } - } - } - }, - x-purpose: test - } - }, - /carts: { - post: { - tags: [ - SwaggerAnnotations - ], - operationId: CreateCart, - requestBody: { - description: The cart request body, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/*+json: { - schema: { - $ref: #/components/schemas/Cart - } - } - }, - x-purpose: test - }, - responses: { - 201: { - description: The cart was created, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - } - } - }, - 400: { - description: The cart data is invalid - } - }, - x-purpose: test - } - }, - /carts/{id}: { - get: { - tags: [ - SwaggerAnnotations - ], - externalDocs: { - description: External docs for CartsByIdGet, - url: https://tempuri.org/carts-by-id-get - }, - operationId: GetCart, - parameters: [ - { - name: id, - in: path, - description: The cart identifier, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - } - } - } - }, - x-purpose: test - }, - delete: { - tags: [ - SwaggerAnnotations - ], - summary: Deletes a specific cart, - description: Requires admin privileges, - operationId: DeleteCart, - parameters: [ - { - name: id, - in: path, - description: The cart identifier, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK, - content: { - text/plain: { - schema: { - $ref: #/components/schemas/Cart - } - }, - application/json: { - schema: { - $ref: #/components/schemas/Cart - } - }, - text/json: { - schema: { - $ref: #/components/schemas/Cart - } - } - } - } - }, - x-purpose: test - } - }, - /stores: { - post: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: query, - schema: { - type: integer, - format: int32 - } - }, - { - name: location, - in: query, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: integer, - format: int32 - } - } - } - } - }, - x-purpose: test - }, - get: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: locations, - in: query, - schema: { - type: array, - items: { - type: string - } - } - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - type: array, - items: { - $ref: #/components/schemas/Store - } - } - } - } - } - }, - x-purpose: test - } - }, - /stores/{id}: { - get: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: path, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK, - content: { - application/json: { - schema: { - $ref: #/components/schemas/Store - } - } - } - } - }, - x-purpose: test - }, - put: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: query, - schema: { - type: integer, - format: int32 - } - }, - { - name: id, - in: path, - required: true, - schema: { - type: integer, - format: int32 - } - }, - { - name: location, - in: query, - schema: { - type: string - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - }, - delete: { - tags: [ - UnboundParams - ], - parameters: [ - { - name: id, - in: path, - required: true, - schema: { - type: integer, - format: int32 - } - } - ], - responses: { - 200: { - description: OK - } - }, - x-purpose: test - } - } - }, - components: { - schemas: { - Cart: { - required: [ - Id - ], - type: object, - properties: { - id: { - type: integer, - description: The cart identifier, - format: int32, - readOnly: true - }, - cartType: { - $ref: #/components/schemas/CartType - } - }, - additionalProperties: false - }, - CartType: { - enum: [ - 0, - 1 - ], - type: integer, - description: The cart type, - format: int32 - }, - Circle: { - allOf: [ - null, - { - type: object, - properties: { - radius: { - type: integer, - format: int32 - } - }, - additionalProperties: false - } - ] - }, - CreditCard: { - required: [ - cardNumber, - expMonth, - expYear - ], - type: object, - properties: { - cardNumber: { - minLength: 1, - pattern: ^[3-6]?\d{12,15}$, - type: string - }, - expMonth: { - maximum: 12, - minimum: 1, - type: integer, - format: int32 - }, - expYear: { - maximum: 99, - minimum: 14, - type: integer, - format: int32 - } - }, - additionalProperties: false - }, - DateTimeKind: { - enum: [ - 0, - 1, - 2 - ], - type: integer, - format: int32 - }, - DiscountType: { - enum: [ - Percentage, - Amount - ], - type: string - }, - LogLevel: { - enum: [ - 0, - 1, - 2, - 3, - 4, - 5, - 6 - ], - type: integer, - format: int32 - }, - Order: { - type: object, - properties: { - id: { - type: integer, - format: int32 - }, - description: { - type: string, - nullable: true - }, - total: { - type: number, - format: double - } - }, - additionalProperties: false - }, - PaymentRequest: { - required: [ - creditCard, - transaction - ], - type: object, - properties: { - transaction: { - $ref: #/components/schemas/Transaction - }, - creditCard: { - $ref: #/components/schemas/CreditCard - } - }, - additionalProperties: false - }, - Product: { - type: object, - properties: { - id: { - type: integer, - description: Uniquely identifies the product, - format: int32 - }, - description: { - type: string, - description: Describes the product, - nullable: true - }, - status: { - $ref: #/components/schemas/ProductStatus - }, - status2: { - $ref: #/components/schemas/ProductStatus - } - }, - additionalProperties: false, - description: Represents a product, - example: { - id: 123, - description: foobar, - price: 14.37 - } - }, - ProductStatus: { - enum: [ - 0, - 1, - 2 - ], - type: integer, - format: int32 - }, - Promotion: { - type: object, - properties: { - promo-code: { - type: string, - nullable: true - }, - discountType: { - $ref: #/components/schemas/DiscountType - } - }, - additionalProperties: false - }, - Rectangle: { - allOf: [ - null, - { - type: object, - properties: { - height: { - type: integer, - format: int32 - }, - width: { - type: integer, - format: int32 - } - }, - additionalProperties: false - } - ] - }, - Shape: { - required: [ - TypeName - ], - type: object, - properties: { - TypeName: { - type: string - }, - name: { - type: string, - nullable: true - } - }, - additionalProperties: false, - discriminator: { - propertyName: TypeName, - mapping: { - Rectangle: #/components/schemas/Rectangle, - Circle: #/components/schemas/Circle - } - } - }, - Store: { - type: object, - properties: { - id: { - type: integer, - format: int32 - }, - location: { - type: string, - nullable: true - } - }, - additionalProperties: false - }, - TestResponse: { - type: object, - properties: { - foo: { - $ref: #/components/schemas/TestStruct - } - }, - additionalProperties: false - }, - TestStruct: { - type: object, - properties: { - a: { - type: integer, - format: int32 - }, - b: { - type: integer, - format: int32 - } - }, - additionalProperties: false - }, - Transaction: { - required: [ - amount - ], - type: object, - properties: { - amount: { - type: number, - format: double - }, - note: { - type: string, - nullable: true - } - }, - additionalProperties: false - }, - ValidationProblemDetails: { - type: object, - properties: { - type: { - type: string, - nullable: true - }, - title: { - type: string, - nullable: true - }, - status: { - type: integer, - format: int32, - nullable: true - }, - detail: { - type: string, - nullable: true - }, - instance: { - type: string, - nullable: true - }, - errors: { - type: object, - additionalProperties: { - type: array, - items: { - type: string - } - }, - nullable: true - } - } - } - } - }, - tags: [ - { - name: SwaggerAnnotations, - description: Manipulate Carts to your heart's content, - externalDocs: { - url: http://www.tempuri.org - } - } - ] -} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt new file mode 100644 index 0000000000..02d5b65bb5 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt @@ -0,0 +1,1636 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Test API V1", + "description": "A sample API for testing Swashbuckle", + "termsOfService": "http://tempuri.org/terms", + "version": "v1" + }, + "paths": { + "/products": { + "post": { + "tags": [ + "CrudActions" + ], + "summary": "Creates a product", + "description": "## Heading 1\r\n\r\n POST /products\r\n {\r\n \"id\": \"123\",\r\n \"description\": \"Some product\"\r\n }", + "operationId": "CreateProduct", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + }, + "required": true, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + } + }, + "x-purpose": "test" + }, + "get": { + "tags": [ + "CrudActions" + ], + "summary": "Searches the collection of products by description key words", + "operationId": "SearchProducts", + "parameters": [ + { + "name": "kw", + "in": "query", + "description": "A list of search terms", + "schema": { + "type": "string", + "default": "foobar" + }, + "example": "hello" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product" + } + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/products/{id}": { + "get": { + "tags": [ + "CrudActions" + ], + "summary": "Returns a specific product", + "operationId": "GetProduct", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The product id", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "example": 111 + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + } + } + }, + "x-purpose": "test" + }, + "put": { + "tags": [ + "CrudActions" + ], + "summary": "Updates all properties of a specific product", + "operationId": "UpdateProduct", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "example": 222 + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Product" + } + } + }, + "required": true, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + }, + "patch": { + "tags": [ + "CrudActions" + ], + "summary": "Updates some properties of a specific product", + "operationId": "PatchProduct", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "example": 333 + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { } + } + }, + "text/json": { + "schema": { + "type": "object", + "additionalProperties": { } + } + }, + "application/*+json": { + "schema": { + "type": "object", + "additionalProperties": { } + } + } + }, + "required": true, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + }, + "delete": { + "tags": [ + "CrudActions" + ], + "summary": "Deletes a specific product", + "operationId": "DeleteProduct", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "example": 444 + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/payments/authorize": { + "post": { + "tags": [ + "DataAnnotations" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PaymentRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/PaymentRequest" + } + } + }, + "required": true, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/payments/{paymentId}/cancel": { + "put": { + "tags": [ + "DataAnnotations" + ], + "parameters": [ + { + "name": "paymentId", + "in": "path", + "required": true, + "schema": { + "minLength": 6, + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/kittens": { + "post": { + "tags": [ + "DynamicTypes" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { } + }, + "text/json": { + "schema": { } + }, + "application/*+json": { + "schema": { } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/unicorns": { + "get": { + "tags": [ + "DynamicTypes" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { } + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/dragons": { + "post": { + "tags": [ + "DynamicTypes" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { } + }, + "text/json": { + "schema": { } + }, + "application/*+json": { + "schema": { } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/files/single": { + "post": { + "tags": [ + "Files" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + }, + "encoding": { + "file": { + "style": "form" + } + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/files/multiple": { + "post": { + "tags": [ + "Files" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + } + } + }, + "encoding": { + "files": { + "style": "form" + } + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/files/form-with-file": { + "post": { + "tags": [ + "Files" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "file": { + "type": "string", + "format": "binary" + } + } + }, + "encoding": { + "name": { + "style": "form" + }, + "file": { + "style": "form" + } + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/files/{name}": { + "get": { + "tags": [ + "Files" + ], + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "oneOf": [ + { + "type": "string", + "format": "binary" + }, + { + "type": "string", + "format": "binary" + }, + { + "type": "string", + "format": "binary" + }, + { + "type": "string", + "format": "binary" + } + ] + } + }, + "application/zip": { + "schema": { + "oneOf": [ + { + "type": "string", + "format": "binary" + }, + { + "type": "string", + "format": "binary" + }, + { + "type": "string", + "format": "binary" + }, + { + "type": "string", + "format": "binary" + } + ] + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/registrations": { + "post": { + "tags": [ + "FromFormParams" + ], + "summary": "Form parameters with description", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Summary for Name", + "example": "MyName" + }, + "phoneNumbers": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "description": "Sumary for PhoneNumbers" + }, + "formFile": { + "type": "string", + "description": "Description for file", + "format": "binary" + }, + "text": { + "type": "string", + "description": "Description for Text" + } + } + }, + "encoding": { + "name": { + "style": "form" + }, + "phoneNumbers": { + "style": "form" + }, + "formFile": { + "style": "form" + }, + "text": { + "style": "form" + } + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/registrationsWithIgnoreProperties": { + "post": { + "tags": [ + "FromFormParams" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "phoneNumbers": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + } + }, + "encoding": { + "phoneNumbers": { + "style": "form" + } + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/registrationsWithEnumParameter": { + "post": { + "tags": [ + "FromFormParams" + ], + "summary": "Form parameters with description", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Summary for Name", + "example": "MyName" + }, + "phoneNumbers": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "description": "Sumary for PhoneNumbers" + }, + "logLevel": { + "$ref": "#/components/schemas/LogLevel" + }, + "formFile": { + "type": "string", + "description": "Description for file", + "format": "binary" + }, + "dateTimeKind": { + "$ref": "#/components/schemas/DateTimeKind" + } + } + }, + "encoding": { + "name": { + "style": "form" + }, + "phoneNumbers": { + "style": "form" + }, + "logLevel": { + "style": "form" + }, + "formFile": { + "style": "form" + }, + "dateTimeKind": { + "style": "form" + } + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/country/validate": { + "get": { + "tags": [ + "FromHeaderParams" + ], + "parameters": [ + { + "name": "country", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/addresses/validate": { + "get": { + "tags": [ + "FromQueryParams" + ], + "parameters": [ + { + "name": "country", + "in": "query", + "description": "3-letter ISO country code", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "city", + "in": "query", + "description": "Name of city", + "schema": { + "type": "string", + "default": "Seattle" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/zip-codes/validate": { + "get": { + "tags": [ + "FromQueryParams" + ], + "parameters": [ + { + "name": "zipCodes", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + }, + "/Issue3013/Get": { + "get": { + "tags": [ + "Issue3013" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/TestResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TestResponse" + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/promotions": { + "get": { + "tags": [ + "JsonAnnotations" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Promotion" + } + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/shapes": { + "post": { + "tags": [ + "PolymorphicTypes" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Rectangle" + }, + { + "$ref": "#/components/schemas/Circle" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Rectangle" + }, + { + "$ref": "#/components/schemas/Circle" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Rectangle" + }, + { + "$ref": "#/components/schemas/Circle" + } + ] + } + } + }, + "x-purpose": "test" + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "text/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/orders": { + "post": { + "tags": [ + "ResponseTypeAnnotations" + ], + "summary": "Creates an order", + "requestBody": { + "description": "", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "required": true, + "x-purpose": "test" + }, + "responses": { + "201": { + "description": "Order created", + "content": { + "application/xml": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + }, + "400": { + "description": "Order invalid", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ValidationProblemDetails" + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/carts": { + "post": { + "tags": [ + "SwaggerAnnotations" + ], + "operationId": "CreateCart", + "requestBody": { + "description": "The cart request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + } + }, + "x-purpose": "test" + }, + "responses": { + "201": { + "description": "The cart was created", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + } + } + }, + "400": { + "description": "The cart data is invalid" + } + }, + "x-purpose": "test" + } + }, + "/carts/{id}": { + "get": { + "tags": [ + "SwaggerAnnotations" + ], + "externalDocs": { + "description": "External docs for CartsByIdGet", + "url": "https://tempuri.org/carts-by-id-get" + }, + "operationId": "GetCart", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The cart identifier", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + } + } + } + }, + "x-purpose": "test" + }, + "delete": { + "tags": [ + "SwaggerAnnotations" + ], + "summary": "Deletes a specific cart", + "description": "Requires admin privileges", + "operationId": "DeleteCart", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The cart identifier", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Cart" + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/stores": { + "post": { + "tags": [ + "UnboundParams" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + }, + "x-purpose": "test" + }, + "get": { + "tags": [ + "UnboundParams" + ], + "parameters": [ + { + "name": "locations", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Store" + } + } + } + } + } + }, + "x-purpose": "test" + } + }, + "/stores/{id}": { + "get": { + "tags": [ + "UnboundParams" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Store" + } + } + } + } + }, + "x-purpose": "test" + }, + "put": { + "tags": [ + "UnboundParams" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "id", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + }, + "delete": { + "tags": [ + "UnboundParams" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "x-purpose": "test" + } + } + }, + "components": { + "schemas": { + "Cart": { + "required": [ + "Id" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The cart identifier", + "format": "int32", + "readOnly": true + }, + "cartType": { + "$ref": "#/components/schemas/CartType" + } + }, + "additionalProperties": false + }, + "CartType": { + "enum": [ + 0, + 1 + ], + "type": "integer", + "description": "The cart type", + "format": "int32" + }, + "Circle": { + "allOf": [ + { + "$ref": "#/components/schemas/Shape" + }, + { + "type": "object", + "properties": { + "radius": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + ] + }, + "CreditCard": { + "required": [ + "cardNumber", + "expMonth", + "expYear" + ], + "type": "object", + "properties": { + "cardNumber": { + "minLength": 1, + "pattern": "^[3-6]?\\d{12,15}$", + "type": "string" + }, + "expMonth": { + "maximum": 12, + "minimum": 1, + "type": "integer", + "format": "int32" + }, + "expYear": { + "maximum": 99, + "minimum": 14, + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "DateTimeKind": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer", + "format": "int32" + }, + "DiscountType": { + "enum": [ + "Percentage", + "Amount" + ], + "type": "string" + }, + "LogLevel": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ], + "type": "integer", + "format": "int32" + }, + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "description": { + "type": "string", + "nullable": true + }, + "total": { + "type": "number", + "format": "double" + } + }, + "additionalProperties": false + }, + "PaymentRequest": { + "required": [ + "creditCard", + "transaction" + ], + "type": "object", + "properties": { + "transaction": { + "$ref": "#/components/schemas/Transaction" + }, + "creditCard": { + "$ref": "#/components/schemas/CreditCard" + } + }, + "additionalProperties": false + }, + "Product": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Uniquely identifies the product", + "format": "int32" + }, + "description": { + "type": "string", + "description": "Describes the product", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/ProductStatus" + }, + "status2": { + "$ref": "#/components/schemas/ProductStatus" + } + }, + "additionalProperties": false, + "description": "Represents a product", + "example": { + "id": 123, + "description": "foobar", + "price": 14.37 + } + }, + "ProductStatus": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer", + "format": "int32" + }, + "Promotion": { + "type": "object", + "properties": { + "promo-code": { + "type": "string", + "nullable": true + }, + "discountType": { + "$ref": "#/components/schemas/DiscountType" + } + }, + "additionalProperties": false + }, + "Rectangle": { + "allOf": [ + { + "$ref": "#/components/schemas/Shape" + }, + { + "type": "object", + "properties": { + "height": { + "type": "integer", + "format": "int32" + }, + "width": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + ] + }, + "Shape": { + "required": [ + "TypeName" + ], + "type": "object", + "properties": { + "TypeName": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "TypeName", + "mapping": { + "Rectangle": "#/components/schemas/Rectangle", + "Circle": "#/components/schemas/Circle" + } + } + }, + "Store": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "location": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "TestResponse": { + "type": "object", + "properties": { + "foo": { + "$ref": "#/components/schemas/TestStruct" + } + }, + "additionalProperties": false + }, + "TestStruct": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "Transaction": { + "required": [ + "amount" + ], + "type": "object", + "properties": { + "amount": { + "type": "number", + "format": "double" + }, + "note": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "ValidationProblemDetails": { + "type": "object", + "properties": { + "type": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "status": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "detail": { + "type": "string", + "nullable": true + }, + "instance": { + "type": "string", + "nullable": true + }, + "errors": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": { } + } + } + }, + "tags": [ + { + "name": "SwaggerAnnotations", + "description": "Manipulate Carts to your heart's content", + "externalDocs": { + "url": "http://www.tempuri.org" + } + } + ] +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt new file mode 100644 index 0000000000..26bbe84ab9 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt @@ -0,0 +1,225 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "NswagClientExample", + "version": "1.0" + }, + "paths": { + "/Animals": { + "post": { + "tags": [ + "Animals" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "$ref": "#/components/schemas/Cat" + }, + { + "$ref": "#/components/schemas/Dog" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "$ref": "#/components/schemas/Cat" + }, + { + "$ref": "#/components/schemas/Dog" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "$ref": "#/components/schemas/Cat" + }, + { + "$ref": "#/components/schemas/Dog" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/SecondLevel": { + "post": { + "tags": [ + "SecondLevel" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SubSubType" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SubSubType" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SubSubType" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Animal": { + "required": [ + "animalType" + ], + "type": "object", + "properties": { + "animalType": { + "$ref": "#/components/schemas/AnimalType" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "animalType", + "mapping": { + "Cat": "#/components/schemas/Cat", + "Dog": "#/components/schemas/Dog" + } + } + }, + "AnimalType": { + "enum": [ + "Cat", + "Dog" + ], + "type": "string" + }, + "BaseType": { + "required": [ + "discriminator" + ], + "type": "object", + "properties": { + "discriminator": { + "type": "string" + }, + "property": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "discriminator", + "mapping": { + "SubSubType": "#/components/schemas/SubSubType" + } + } + }, + "Cat": { + "allOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "type": "object", + "properties": { + "catSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] + }, + "Dog": { + "allOf": [ + { + "$ref": "#/components/schemas/Animal" + }, + { + "type": "object", + "properties": { + "dogSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] + }, + "SubSubType": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseType" + }, + { + "type": "object", + "properties": { + "property2": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] + } + } + } +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt index ab9a22b898..7671c6817e 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=NSwagClientExample.Startup_swaggerRequestUri=v1.verified.txt @@ -107,28 +107,78 @@ "200": { "description": "OK", "content": { - "text/plain": { - "schema": { - "type": "integer", - "format": "int32" - } - }, "application/json": { "schema": { "type": "integer", "format": "int32" } - }, - "text/json": { - "schema": { - "type": "integer", - "format": "int32" - } } } } } } + }, + "/SystemTextJsonAnimals": { + "post": { + "tags": [ + "SystemTextJsonAnimals" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } } }, "components": { @@ -231,6 +281,60 @@ "additionalProperties": false } ] + }, + "SystemTextJsonAnimal": { + "required": [ + "animalType" + ], + "type": "object", + "properties": { + "animalType": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "animalType", + "mapping": { + "Cat": "#/components/schemas/SystemTextJsonCat", + "Dog": "#/components/schemas/SystemTextJsonDog" + } + } + }, + "SystemTextJsonCat": { + "allOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "type": "object", + "properties": { + "catSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] + }, + "SystemTextJsonDog": { + "allOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "type": "object", + "properties": { + "dogSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] } } } diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs index c99a907107..32d4e0f99b 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs @@ -14,6 +14,7 @@ public partial class SwaggerVerifyIntegrationTest [Theory] #if !NET6_0 [InlineData(typeof(Basic.Startup), "/swagger/v1/swagger.json")] + [InlineData(typeof(NSwagClientExample.Startup), "/swagger/v1/swagger.json")] #endif [InlineData(typeof(CliExample.Startup), "/swagger/v1/swagger_net8.0.json")] [InlineData(typeof(ConfigFromFile.Startup), "/swagger/v1/swagger.json")] @@ -23,7 +24,6 @@ public partial class SwaggerVerifyIntegrationTest [InlineData(typeof(GenericControllers.Startup), "/swagger/v1/swagger.json")] [InlineData(typeof(MultipleVersions.Startup), "/swagger/1.0/swagger.json")] [InlineData(typeof(MultipleVersions.Startup), "/swagger/2.0/swagger.json")] - [InlineData(typeof(NSwagClientExample.Startup), "/swagger/v1/swagger.json")] [InlineData(typeof(OAuth2Integration.Startup), "/resource-server/swagger/v1/swagger.json")] [InlineData(typeof(ReDocApp.Startup), "/swagger/v1/swagger.json")] [InlineData(typeof(TestFirst.Startup), "/swagger/v1-generated/openapi.json")] @@ -40,30 +40,23 @@ public async Task SwaggerEndpoint_ReturnsValidSwaggerJson( } #if NET6_0 - [Fact] - public async Task SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6() + [Theory] + [InlineData(typeof(Basic.Startup), "/swagger/v1/swagger.json")] + [InlineData(typeof(NSwagClientExample.Startup), "/swagger/v1/swagger.json")] + public async Task SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6( + Type startupType, + string swaggerRequestUri) { - var testSite = new TestSite(typeof(Basic.Startup)); + var testSite = new TestSite(startupType); using var client = testSite.BuildClient(); - using var swaggerResponse = await client.GetAsync("/swagger/v1/swagger.json"); + using var swaggerResponse = await client.GetAsync(swaggerRequestUri); var swagger = await swaggerResponse.Content.ReadAsStringAsync(); - await Verifier.VerifyJson(swagger); + await Verifier.Verify(swagger).UseParameters(startupType, GetVersion(swaggerRequestUri)); } #endif #if NET8_0_OR_GREATER - [Fact] - public async Task SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8() - { - var testSite = new TestSite(typeof(Basic.Startup)); - using var client = testSite.BuildClient(); - - using var swaggerResponse = await client.GetAsync("/swagger/v1/swagger.json"); - var swagger = await swaggerResponse.Content.ReadAsStringAsync(); - await Verifier.VerifyJson(swagger); - } - [Theory] [InlineData("/swagger/v1/swagger.json")] public async Task SwaggerEndpoint_ReturnsValidSwaggerJson_For_WebApi( diff --git a/test/WebSites/NswagClientExample/Controllers/AnimalsController.cs b/test/WebSites/NswagClientExample/Controllers/AnimalsController.cs index 8b59829e33..06a8226291 100644 --- a/test/WebSites/NswagClientExample/Controllers/AnimalsController.cs +++ b/test/WebSites/NswagClientExample/Controllers/AnimalsController.cs @@ -11,6 +11,7 @@ namespace NSwagClientExample.Controllers public class AnimalsController : ControllerBase { [HttpPost] + [Produces("application/json")] public void CreateAnimal([Required]Animal animal) { throw new NotImplementedException(); diff --git a/test/WebSites/NswagClientExample/Controllers/SecondLevelController.cs b/test/WebSites/NswagClientExample/Controllers/SecondLevelController.cs index d604787aed..16fab93422 100644 --- a/test/WebSites/NswagClientExample/Controllers/SecondLevelController.cs +++ b/test/WebSites/NswagClientExample/Controllers/SecondLevelController.cs @@ -9,6 +9,7 @@ namespace NswagClientExample.Controllers public class SecondLevelController : ControllerBase { [HttpPost] + [Produces("application/json")] public int Create([FromBody]BaseType input) { throw new NotImplementedException(); diff --git a/test/WebSites/NswagClientExample/Controllers/SystemTextJsonAnimalsController.cs b/test/WebSites/NswagClientExample/Controllers/SystemTextJsonAnimalsController.cs new file mode 100644 index 0000000000..8356daffb9 --- /dev/null +++ b/test/WebSites/NswagClientExample/Controllers/SystemTextJsonAnimalsController.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Mvc; + +namespace NSwagClientExample.Controllers +{ +#if NET7_0_OR_GREATER + [ApiController] + [Route("[controller]")] + public class SystemTextJsonAnimalsController : ControllerBase + { + [HttpPost] + [Produces("application/json")] + public void CreateAnimal([Required]SystemTextJsonAnimal animal) + { + throw new NotImplementedException(); + } + } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = "animalType")] + [JsonDerivedType(typeof(SystemTextJsonCat), "Cat")] + [JsonDerivedType(typeof(SystemTextJsonDog), "Dog")] + public class SystemTextJsonAnimal + { + public string AnimalType { get; set; } + } + + public class SystemTextJsonCat : SystemTextJsonAnimal + { + public string CatSpecificProperty { get; set; } + } + + public class SystemTextJsonDog : SystemTextJsonAnimal + { + public string DogSpecificProperty { get; set; } + } +#endif +} diff --git a/test/WebSites/NswagClientExample/swagger_net6.0.json b/test/WebSites/NswagClientExample/swagger_net6.0.json index 7bcd238c3f..611413b78a 100644 --- a/test/WebSites/NswagClientExample/swagger_net6.0.json +++ b/test/WebSites/NswagClientExample/swagger_net6.0.json @@ -112,23 +112,11 @@ "200": { "description": "OK", "content": { - "text/plain": { - "schema": { - "type": "integer", - "format": "int32" - } - }, "application/json": { "schema": { "type": "integer", "format": "int32" } - }, - "text/json": { - "schema": { - "type": "integer", - "format": "int32" - } } } } @@ -180,7 +168,10 @@ }, "additionalProperties": false, "discriminator": { - "propertyName": "discriminator" + "propertyName": "discriminator", + "mapping": { + "SubSubType": "#/components/schemas/SubSubType" + } } }, "Cat": { diff --git a/test/WebSites/NswagClientExample/swagger_net8.0.json b/test/WebSites/NswagClientExample/swagger_net8.0.json index 7bcd238c3f..1df2eaa803 100644 --- a/test/WebSites/NswagClientExample/swagger_net8.0.json +++ b/test/WebSites/NswagClientExample/swagger_net8.0.json @@ -112,28 +112,78 @@ "200": { "description": "OK", "content": { - "text/plain": { - "schema": { - "type": "integer", - "format": "int32" - } - }, "application/json": { "schema": { "type": "integer", "format": "int32" } - }, - "text/json": { - "schema": { - "type": "integer", - "format": "int32" - } } } } } } + }, + "/SystemTextJsonAnimals": { + "post": { + "tags": [ + "SystemTextJsonAnimals" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } } }, "components": { @@ -180,7 +230,10 @@ }, "additionalProperties": false, "discriminator": { - "propertyName": "discriminator" + "propertyName": "discriminator", + "mapping": { + "SubSubType": "#/components/schemas/SubSubType" + } } }, "Cat": { @@ -233,6 +286,60 @@ "additionalProperties": false } ] + }, + "SystemTextJsonAnimal": { + "required": [ + "animalType" + ], + "type": "object", + "properties": { + "animalType": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "animalType", + "mapping": { + "Cat": "#/components/schemas/SystemTextJsonCat", + "Dog": "#/components/schemas/SystemTextJsonDog" + } + } + }, + "SystemTextJsonCat": { + "allOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "type": "object", + "properties": { + "catSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] + }, + "SystemTextJsonDog": { + "allOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "type": "object", + "properties": { + "dogSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] } } } diff --git a/test/WebSites/NswagClientExample/swagger_net9.0.json b/test/WebSites/NswagClientExample/swagger_net9.0.json index 7bcd238c3f..1df2eaa803 100644 --- a/test/WebSites/NswagClientExample/swagger_net9.0.json +++ b/test/WebSites/NswagClientExample/swagger_net9.0.json @@ -112,28 +112,78 @@ "200": { "description": "OK", "content": { - "text/plain": { - "schema": { - "type": "integer", - "format": "int32" - } - }, "application/json": { "schema": { "type": "integer", "format": "int32" } - }, - "text/json": { - "schema": { - "type": "integer", - "format": "int32" - } } } } } } + }, + "/SystemTextJsonAnimals": { + "post": { + "tags": [ + "SystemTextJsonAnimals" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "$ref": "#/components/schemas/SystemTextJsonCat" + }, + { + "$ref": "#/components/schemas/SystemTextJsonDog" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } } }, "components": { @@ -180,7 +230,10 @@ }, "additionalProperties": false, "discriminator": { - "propertyName": "discriminator" + "propertyName": "discriminator", + "mapping": { + "SubSubType": "#/components/schemas/SubSubType" + } } }, "Cat": { @@ -233,6 +286,60 @@ "additionalProperties": false } ] + }, + "SystemTextJsonAnimal": { + "required": [ + "animalType" + ], + "type": "object", + "properties": { + "animalType": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "animalType", + "mapping": { + "Cat": "#/components/schemas/SystemTextJsonCat", + "Dog": "#/components/schemas/SystemTextJsonDog" + } + } + }, + "SystemTextJsonCat": { + "allOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "type": "object", + "properties": { + "catSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] + }, + "SystemTextJsonDog": { + "allOf": [ + { + "$ref": "#/components/schemas/SystemTextJsonAnimal" + }, + { + "type": "object", + "properties": { + "dogSpecificProperty": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + ] } } }