From 45c304b453dcbd305a46b71a0bb9773cb586a756 Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 8 Aug 2024 14:07:44 +0100 Subject: [PATCH 1/5] Fix verification tests Fix missing verification tests for the Basic website. --- ...lidSwaggerJson_Basic_DotNet_6.verified.txt | 1449 +++++++++++++++++ ...lidSwaggerJson_Basic_DotNet_8.verified.txt | 1448 ++++++++++++++++ .../SwaggerVerifyIntegrationTest.cs | 38 +- 3 files changed, 2932 insertions(+), 3 deletions(-) create mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6.verified.txt create mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8.verified.txt 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 new file mode 100644 index 0000000000..83ff3be810 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6.verified.txt @@ -0,0 +1,1449 @@ +{ + 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 + ], + requestBody: { + content: { + application/x-www-form-urlencoded: { + schema: { + type: object, + properties: { + name: { + type: string + }, + phoneNumbers: { + type: array, + items: { + type: integer, + format: int32 + } + } + } + }, + encoding: { + name: { + style: form + }, + phoneNumbers: { + 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 + } + }, + /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 + } + }, + /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 + }, + DiscountType: { + enum: [ + Percentage, + Amount + ], + type: string + }, + 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 + }, + 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 new file mode 100644 index 0000000000..07dc53bbcf --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_8.verified.txt @@ -0,0 +1,1448 @@ +{ + 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 + ], + requestBody: { + content: { + application/x-www-form-urlencoded: { + schema: { + type: object, + properties: { + name: { + type: string + }, + phoneNumbers: { + type: array, + items: { + type: integer, + format: int32 + } + } + } + }, + encoding: { + name: { + style: form + }, + phoneNumbers: { + 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 + } + }, + /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 + } + }, + /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 + }, + DiscountType: { + enum: [ + Percentage, + Amount + ], + type: string + }, + 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 + }, + 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.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs index 142969bff3..ca92a7e86c 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.cs @@ -9,7 +9,7 @@ namespace Swashbuckle.AspNetCore.IntegrationTests { [Collection("TestSite")] - public class SwaggerVerifyIntegrationTest + public partial class SwaggerVerifyIntegrationTest { [Theory] [InlineData(typeof(CliExample.Startup), "/swagger/v1/swagger_net8.0.json")] @@ -34,7 +34,31 @@ public async Task SwaggerEndpoint_ReturnsValidSwaggerJson( await Verifier.Verify(swagger).UseParameters(startupType, GetVersion(swaggerRequestUri)); } +#if NET6_0 + [Fact] + public async Task SwaggerEndpoint_ReturnsValidSwaggerJson_Basic_DotNet_6() + { + 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); + } +#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( @@ -71,13 +95,21 @@ private static async Task SwaggerEndpointReturnsValidSwaggerJson SwaggerResponse(HttpClient client, string swaggerRequestUri) { using var swaggerResponse = await client.GetAsync(swaggerRequestUri); var contentStream = await swaggerResponse.Content.ReadAsStringAsync(); return contentStream; } - private static string GetVersion(string swaggerUi) => Regex.Match(swaggerUi, "/\\w+/([\\w+\\d+.-]+)/").Groups[1].Value; +#endif + private static string GetVersion(string swaggerUi) => +#if NET6_0 + Regex.Match(swaggerUi, "/\\w+/([\\w+\\d+.-]+)/").Groups[1].Value; +#else + VersionRegex().Match(swaggerUi).Groups[1].Value; + + [GeneratedRegex("/\\w+/([\\w+\\d+.-]+)/")] + private static partial Regex VersionRegex(); +#endif } } From fb4aee49775a1b47bc10a27955ef4073ab9c2f86 Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 8 Aug 2024 14:09:48 +0100 Subject: [PATCH 2/5] Add broken code Add example controller code from #3013. --- .../Basic/Controllers/Issue3013Controller.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 test/WebSites/Basic/Controllers/Issue3013Controller.cs diff --git a/test/WebSites/Basic/Controllers/Issue3013Controller.cs b/test/WebSites/Basic/Controllers/Issue3013Controller.cs new file mode 100644 index 0000000000..ab6cbe7aab --- /dev/null +++ b/test/WebSites/Basic/Controllers/Issue3013Controller.cs @@ -0,0 +1,26 @@ +#if NET6_0_OR_GREATER +using Microsoft.AspNetCore.Mvc; + +namespace Basic.Controllers; + +[ApiController] +[Route("[controller]/[action]")] +public class Issue3013Controller : ControllerBase +{ + [HttpGet] + public TestResponse Get() + { + return new() + { + Foo = new(1, 2), + }; + } + + public record TestResponse + { + public TestStruct? Foo { get; init; } + } + + public record struct TestStruct(int A, int B); +} +#endif From b7161e6e5b739f17c5f87551ec93b4fd60e8f3ab Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 8 Aug 2024 14:12:01 +0100 Subject: [PATCH 3/5] Remove unused file Remove unused verify snapshot. --- ....Startup_swaggerRequestUri=v1.verified.txt | 1497 ----------------- 1 file changed, 1497 deletions(-) delete mode 100644 test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt deleted file mode 100644 index 4d9e556dc5..0000000000 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt +++ /dev/null @@ -1,1497 +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\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" - ], - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "phoneNumbers": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - } - } - } - }, - "encoding": { - "name": { - "style": "form" - }, - "phoneNumbers": { - "style": "form" - } - } - } - }, - "x-purpose": "test" - }, - "responses": { - "200": { - "description": "OK" - } - }, - "x-purpose": "test" - } - }, - "/registrationsWithIgnoreProperties": { - "post": { - "tags": [ - "FromFormParams" - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "required": [ - "country" - ], - "type": "object", - "properties": { - "phoneNumbers": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - } - }, - "country": { - "type": "string", - "description": "3-letter ISO country code" - }, - "city": { - "type": "string", - "description": "Name of city", - "default": "Seattle" - } - } - }, - "encoding": { - "phoneNumbers": { - "style": "form" - }, - "country": { - "style": "form" - }, - "city": { - "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" - } - }, - "/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" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Store" - } - }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/Store" - } - }, - "application/*+json": { - "schema": { - "$ref": "#/components/schemas/Store" - } - } - }, - "x-purpose": "test" - }, - "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 - }, - "DiscountType": { - "enum": [ - "Percentage", - "Amount" - ], - "type": "string" - }, - "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 - }, - "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 - } - }, - "additionalProperties": { } - } - } - }, - "tags": [ - { - "name": "SwaggerAnnotations", - "description": "Manipulate Carts to your heart's content", - "externalDocs": { - "url": "http://www.tempuri.org" - } - } - ] -} \ No newline at end of file From 581ac159c1a511f24a4b5d832d8fed7624cee449 Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 8 Aug 2024 14:36:35 +0100 Subject: [PATCH 4/5] Fix schemas for nullable properties Fix incorrect handling of schemas for nullable properties. Resolves #3013. --- .../JsonSerializerDataContractResolver.cs | 2 +- ...lidSwaggerJson_Basic_DotNet_6.verified.txt | 53 +++++++++++++++++++ ...lidSwaggerJson_Basic_DotNet_8.verified.txt | 53 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs index c048d970ed..3e2a6404aa 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs @@ -96,7 +96,7 @@ public DataContract GetDataContractForType(Type type) return DataContract.ForObject( underlyingType: effectiveType, - properties: GetDataPropertiesFor(type, out Type extensionDataType), + properties: GetDataPropertiesFor(effectiveType, out Type extensionDataType), extensionDataType: extensionDataType, jsonConverter: (value) => JsonConverterFunc(value, effectiveType)); } 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 index 83ff3be810..ce88e0c243 100644 --- 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 @@ -726,6 +726,36 @@ 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: [ @@ -1381,6 +1411,29 @@ }, 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 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 index 07dc53bbcf..fbae1d65df 100644 --- 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 @@ -726,6 +726,36 @@ 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: [ @@ -1381,6 +1411,29 @@ }, 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 From 0c1bfbf9a82971e4791780111021c89a183450a9 Mon Sep 17 00:00:00 2001 From: martincostello Date: Thu, 8 Aug 2024 14:55:33 +0100 Subject: [PATCH 5/5] Bump NuGet packages Update various NuGet packages to their latest versions. --- Directory.Packages.props | 24 +++++++++---------- .../NswagClientExample.csproj | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 9e100ec035..a3b73e2eb2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,35 +2,35 @@ - + - - + + - + - + - + - - + + - - - - + + + + diff --git a/test/WebSites/NswagClientExample/NswagClientExample.csproj b/test/WebSites/NswagClientExample/NswagClientExample.csproj index 4ee15c17f8..6b3282425d 100644 --- a/test/WebSites/NswagClientExample/NswagClientExample.csproj +++ b/test/WebSites/NswagClientExample/NswagClientExample.csproj @@ -9,7 +9,7 @@ - +