Giter VIP home page Giter VIP logo

restdocs-api-spec's Issues

setting type for field descriptors broken with spring-restdocs 2.0.2

Since spring-restdocs 2.0.2 the type determined in AbstractFieldsSnippet is no longer applied to the list of descriptors in the input. Instead copies are created. This breaks our mechanism to use the spring-restdocs type defaulting.

Execution failed for task ':openapi'.
> com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.epages.restdocs.openapi.gradle.FieldDescriptor] value failed for JSON property type due to missing (therefore NULL) value for creator parameter type which is a non-nullable type

See spring-projects/spring-restdocs@a2a9a7c#diff-828f9d67e5886dcde0cb12c014b09531

Gradle task openapi3 fails when a field is not given a description

Not sure if this is intended behavior, but when I don't specify a description for a response field, the output snippet resource.json outputs "description": null, which causes the openapi3 gradle task to fail.

Example:

The controller returns an array of objects.

[
  {
    "example": "blah"
  }
]

resources.json shows a null value for description

 "responseFields" : [ {
      "attributes" : { },
      "description" : null,
      "ignored" : false,
      "path" : "[].example",
      "type" : "STRING",
      "optional" : false
    },

Test uses Spring @WebMvcTest and MockMvc with MockMvcRestDocumentationWrapper

this.mockMvc.perform(get("/example"))
        .andExpect(status().isOk())
        .andDo(document("example", resource(ResourceSnippetParameters.builder()
            .responseFields(
                fieldWithPath("[].example").type(JsonFieldType.STRING)
            )
            .build()
        )));

Error:

Execution failed for task ':example:openapi3'.
> com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.epages.restdocs.apispec.model.FieldDescriptor] value failed for JSON property description due to missing (therefore NULL) value for creator parameter description which is a non-nullable type
...

com.epages.restdocs.apispec.model.ResourceModel["response"]->com.epages.restdocs.apispec.model.ResponseModel["responseFields"]->java.util.ArrayList[0]->com.epages.restdocs.apispec.model.FieldDescriptor["description"])

If I add any description to the FieldDescriptor, the task succeeds.

Plugin not found when using the new Plugin DSL notation in build.gradle

Plugin configuration:

settings.gradle:

pluginManagement {
  repositories {
      gradlePluginPortal()
      maven { url = uri("https://jitpack.io") }
  }
}

build.gradle:

plugins {
    [...]
    id 'com.epages.restdocs-openapi' version '0.2.1'
}

Generated urls are different for classic configuration using buildscript and a new one with plugins:

https://jitpack.io/com/github/epages-de/restdocs-openapi/restdocs-openapi-gradle-plugin/0.2.1/restdocs-openapi-gradle-plugin-0.2.1.pom
https://jitpack.io/com/epages/restdocs-openapi/com.epages.restdocs-openapi.gradle.plugin/0.2.1/com.epages.restdocs-openapi.gradle.plugin-0.2.1.pom

The first one is from buildscript and works.

Postman Collections: Multiple examples for the same request are not labeled distinctively

If I provide multiple examples for the same request (same resource, HTTP method, HTTP status code and content-type) and I generate the Postman Collection, both examples are selectable, but share the same name:

postman-examples

Snippet to reproduce this:

{
    "id" : "products-create",
    "name" : "/products",
    "description" : "Creates a new product.",
    "variable" : [ ],
    "event" : [ ],
    "request" : {
      "url" : {
        "protocol" : "https",
        "host" : "api-shop.beyondshop.cloud",
        "path" : "/api/products"
      },
      "method" : "POST",
      "header" : [ {
        "key" : "Content-Type",
        "value" : "application/json",
        "disabled" : false
      } ],
      "body" : {
        "mode" : "raw",
        "raw" : "{\n  \"sku\" : \"123456789-001\",\n  \"name\" : \"Rioja Castillo de Puerto (2013)\",\n  \"description\" : \"Spain\\nRioja Tempranillo\",\n  \"manufacturer\" : \"Grape Vineyard\",\n  \"essentialFeatures\" : \"Dry. 12% alcohol. Best vine variety.\",\n  \"tags\" : [ \"Bestseller\", \"Red Wine\", \"Sale\" ],\n  \"productIdentifiers\" : [ {\n    \"type\" : \"EAN\",\n    \"value\" : \"9780134308135\"\n  } ],\n  \"salesPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"amount\" : 8.7,\n    \"currency\" : \"EUR\"\n  },\n  \"listPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"amount\" : 10.95,\n    \"currency\" : \"EUR\"\n  },\n  \"manufacturerPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"amount\" : 99.95,\n    \"currency\" : \"EUR\"\n  },\n  \"onSale\" : true,\n  \"visible\" : true,\n  \"taxClass\" : \"REGULAR\",\n  \"shippingWeight\" : 100,\n  \"maxOrderQuantity\" : 6,\n  \"shippingDimension\" : {\n    \"length\" : 1500,\n    \"width\" : 1000,\n    \"height\" : 2000\n  },\n  \"refPrice\" : {\n    \"refQuantity\" : 1,\n    \"unit\" : \"LITER\",\n    \"quantity\" : 0.75,\n    \"price\" : {\n      \"taxModel\" : \"NET\",\n      \"amount\" : 11.6,\n      \"currency\" : \"EUR\"\n    }\n  },\n  \"shippingPeriod\" : {\n    \"minDays\" : 2,\n    \"maxDays\" : 4,\n    \"displayUnit\" : \"WEEKS\"\n  }\n}",
        "urlencoded" : [ ]
      }
    },
    "response" : [ {
      "id" : "products-create",
      "name" : "201-application/hal+json",
      "originalRequest" : {
        "url" : {
          "protocol" : "https",
          "host" : "api-shop.beyondshop.cloud",
          "path" : "/api/products"
        },
        "method" : "POST",
        "header" : [ {
          "key" : "Content-Type",
          "value" : "application/json",
          "disabled" : false
        } ],
        "body" : {
          "mode" : "raw",
          "raw" : "{\n  \"sku\" : \"123456789-001\",\n  \"name\" : \"Rioja Castillo de Puerto (2013)\",\n  \"description\" : \"Spain\\nRioja Tempranillo\",\n  \"manufacturer\" : \"Grape Vineyard\",\n  \"essentialFeatures\" : \"Dry. 12% alcohol. Best vine variety.\",\n  \"tags\" : [ \"Bestseller\", \"Red Wine\", \"Sale\" ],\n  \"productIdentifiers\" : [ {\n    \"type\" : \"EAN\",\n    \"value\" : \"9780134308135\"\n  } ],\n  \"salesPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"amount\" : 8.7,\n    \"currency\" : \"EUR\"\n  },\n  \"listPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"amount\" : 10.95,\n    \"currency\" : \"EUR\"\n  },\n  \"manufacturerPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"amount\" : 99.95,\n    \"currency\" : \"EUR\"\n  },\n  \"onSale\" : true,\n  \"visible\" : true,\n  \"taxClass\" : \"REGULAR\",\n  \"shippingWeight\" : 100,\n  \"maxOrderQuantity\" : 6,\n  \"shippingDimension\" : {\n    \"length\" : 1500,\n    \"width\" : 1000,\n    \"height\" : 2000\n  },\n  \"refPrice\" : {\n    \"refQuantity\" : 1,\n    \"unit\" : \"LITER\",\n    \"quantity\" : 0.75,\n    \"price\" : {\n      \"taxModel\" : \"NET\",\n      \"amount\" : 11.6,\n      \"currency\" : \"EUR\"\n    }\n  },\n  \"shippingPeriod\" : {\n    \"minDays\" : 2,\n    \"maxDays\" : 4,\n    \"displayUnit\" : \"WEEKS\"\n  }\n}",
          "urlencoded" : [ ]
        }
      },
      "header" : [ {
        "key" : "Content-Type",
        "value" : "application/hal+json",
        "disabled" : false
      } ],
      "cookie" : [ ],
      "body" : "{\n  \"_embedded\" : {\n    \"availability\" : {\n      \"availabilityState\" : \"IN_STOCK\",\n      \"availableStock\" : null,\n      \"stockThreshold\" : null,\n      \"purchasable\" : true,\n      \"_links\" : {\n        \"self\" : {\n          \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73/availability\"\n        }\n      }\n    }\n  },\n  \"lastModifiedAt\" : \"2019-06-11T12:49:17.573\",\n  \"sku\" : \"123456789-001\",\n  \"salesPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 8.7,\n    \"derivedPrice\" : {\n      \"taxModel\" : \"GROSS\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 9.57,\n      \"taxRate\" : 0.1\n    }\n  },\n  \"listPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 10.95,\n    \"derivedPrice\" : {\n      \"taxModel\" : \"GROSS\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 12.045,\n      \"taxRate\" : 0.1\n    }\n  },\n  \"manufacturerPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 99.95,\n    \"derivedPrice\" : {\n      \"taxModel\" : \"GROSS\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 109.945,\n      \"taxRate\" : 0.1\n    }\n  },\n  \"onSale\" : true,\n  \"tags\" : [ \"Bestseller\", \"Red Wine\", \"Sale\" ],\n  \"productIdentifiers\" : [ {\n    \"type\" : \"EAN\",\n    \"value\" : \"9780134308135\"\n  } ],\n  \"visible\" : true,\n  \"taxClass\" : \"REGULAR\",\n  \"shippingWeight\" : 100,\n  \"maxOrderQuantity\" : 6,\n  \"shippingDimension\" : {\n    \"length\" : 1500,\n    \"width\" : 1000,\n    \"height\" : 2000\n  },\n  \"refPrice\" : {\n    \"refQuantity\" : 1,\n    \"unit\" : \"LITER\",\n    \"quantity\" : 0.75,\n    \"price\" : {\n      \"taxModel\" : \"NET\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 11.6,\n      \"derivedPrice\" : {\n        \"taxModel\" : \"GROSS\",\n        \"currency\" : \"EUR\",\n        \"amount\" : 12.76,\n        \"taxRate\" : 0.1\n      }\n    }\n  },\n  \"shippingPeriod\" : {\n    \"minDays\" : 2,\n    \"maxDays\" : 4,\n    \"displayUnit\" : \"WEEKS\"\n  },\n  \"name\" : \"Rioja Castillo de Puerto (2013)\",\n  \"description\" : \"Spain\\nRioja Tempranillo\",\n  \"manufacturer\" : \"Grape Vineyard\",\n  \"essentialFeatures\" : \"Dry. 12% alcohol. Best vine variety.\",\n  \"variationAttributes\" : [ ],\n  \"_id\" : \"11f16b3a-7f3b-4ad5-bba9-283b1669ab73\",\n  \"_links\" : {\n    \"self\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73\"\n    },\n    \"product\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73\"\n    },\n    \"availability\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73/availability\"\n    },\n    \"attributes\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73/attributes\"\n    },\n    \"attachments\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73/attachments\"\n    },\n    \"images\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73/images\"\n    },\n    \"default-image\" : {\n      \"href\" : \"https://yourshop.api.urn/products/11f16b3a-7f3b-4ad5-bba9-283b1669ab73/default-image\"\n    }\n  }\n}",
      "code" : 201
    }, {
      "id" : "variation-products-create",
      "name" : "201-application/hal+json",
      "originalRequest" : {
        "url" : {
          "protocol" : "https",
          "host" : "api-shop.beyondshop.cloud",
          "path" : "/api/products"
        },
        "method" : "POST",
        "header" : [ {
          "key" : "Content-Type",
          "value" : "application/json",
          "disabled" : false
        } ],
        "body" : {
          "mode" : "raw",
          "raw" : "{\n  \"salesPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 31.19\n  },\n  \"listPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 46.13\n  },\n  \"manufacturerPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 48.6\n  },\n  \"onSale\" : true,\n  \"tags\" : [ \"Shirt\", \"Summer\", \"Sale\" ],\n  \"productIdentifiers\" : [ ],\n  \"visible\" : true,\n  \"taxClass\" : \"REGULAR\",\n  \"shippingWeight\" : null,\n  \"maxOrderQuantity\" : null,\n  \"shippingDimension\" : {\n    \"length\" : 2000,\n    \"width\" : 750,\n    \"height\" : 500\n  },\n  \"refPrice\" : null,\n  \"shippingPeriod\" : {\n    \"minDays\" : 3,\n    \"maxDays\" : 5,\n    \"displayUnit\" : \"DAYS\"\n  },\n  \"name\" : \"Tony Highfinger, Poloshirt, Men\",\n  \"description\" : \"100% cotton, regular fit, needs cold washing (max. 30°C), Fair Trade certified.\",\n  \"manufacturer\" : \"Tony Highfinger\",\n  \"essentialFeatures\" : null,\n  \"variationAttributes\" : [ {\n    \"displayName\" : \"size\",\n    \"values\" : [ \"S\", \"M\", \"L\", \"XL\" ]\n  }, {\n    \"displayName\" : \"color\",\n    \"values\" : [ \"Black\", \"White\", \"Grey\" ]\n  } ]\n}",
          "urlencoded" : [ ]
        }
      },
      "header" : [ {
        "key" : "Content-Type",
        "value" : "application/hal+json",
        "disabled" : false
      } ],
      "cookie" : [ ],
      "body" : "{\n  \"_embedded\" : {\n    \"availability\" : {\n      \"availabilityState\" : \"NOT_AVAILABLE\",\n      \"availableStock\" : null,\n      \"stockThreshold\" : null,\n      \"purchasable\" : false,\n      \"_links\" : {\n        \"self\" : {\n          \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/availability\"\n        }\n      }\n    }\n  },\n  \"lastModifiedAt\" : \"2019-06-11T12:48:18.989\",\n  \"sku\" : null,\n  \"salesPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 31.19,\n    \"derivedPrice\" : {\n      \"taxModel\" : \"GROSS\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 34.309,\n      \"taxRate\" : 0.1\n    }\n  },\n  \"listPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 46.13,\n    \"derivedPrice\" : {\n      \"taxModel\" : \"GROSS\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 50.743,\n      \"taxRate\" : 0.1\n    }\n  },\n  \"manufacturerPrice\" : {\n    \"taxModel\" : \"NET\",\n    \"currency\" : \"EUR\",\n    \"amount\" : 48.6,\n    \"derivedPrice\" : {\n      \"taxModel\" : \"GROSS\",\n      \"currency\" : \"EUR\",\n      \"amount\" : 53.46,\n      \"taxRate\" : 0.1\n    }\n  },\n  \"onSale\" : true,\n  \"tags\" : [ \"Shirt\", \"Summer\", \"Sale\" ],\n  \"productIdentifiers\" : [ ],\n  \"visible\" : true,\n  \"taxClass\" : \"REGULAR\",\n  \"shippingWeight\" : null,\n  \"maxOrderQuantity\" : null,\n  \"shippingDimension\" : {\n    \"length\" : 2000,\n    \"width\" : 750,\n    \"height\" : 500\n  },\n  \"refPrice\" : null,\n  \"shippingPeriod\" : {\n    \"minDays\" : 3,\n    \"maxDays\" : 5,\n    \"displayUnit\" : \"DAYS\"\n  },\n  \"name\" : \"Tony Highfinger, Poloshirt, Men\",\n  \"description\" : \"100% cotton, regular fit, needs cold washing (max. 30°C), Fair Trade certified.\",\n  \"manufacturer\" : \"Tony Highfinger\",\n  \"essentialFeatures\" : null,\n  \"variationAttributes\" : [ {\n    \"displayName\" : \"size\",\n    \"values\" : [ \"S\", \"M\", \"L\", \"XL\" ]\n  }, {\n    \"displayName\" : \"color\",\n    \"values\" : [ \"Black\", \"White\", \"Grey\" ]\n  } ],\n  \"_id\" : \"fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9\",\n  \"_links\" : {\n    \"self\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9\"\n    },\n    \"product\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9\"\n    },\n    \"availability\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/availability\"\n    },\n    \"attributes\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/attributes\"\n    },\n    \"attachments\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/attachments\"\n    },\n    \"images\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/images\"\n    },\n    \"default-image\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/default-image\"\n    },\n    \"variations\" : {\n      \"href\" : \"https://yourshop.api.urn/products/fd6b7fc2-5c6f-4aed-b5ae-5b6717f83fe9/variations\"\n    }\n  }\n}",
      "code" : 201
    } ]
  }

(notice how there are 2 responses with the IDs products-create and variation-products-create and both have the field:

"name" : "201-application/hal+json",

which results in the identical display in Postman)

Suggestion

  • Include the operation ID (as provides by Spring restocs), e.g. products-create in the name field of the response object.

How to specify format of the field?

In the specification it is possible to add to the field of type string, that its format it date or date+time. For example

                   entryTime:
                      type: string
                      format: date-time
                      description: time of entry
 

How to achieve that with this plugin?

How to set response status description?

I couldn't seem to find a way to set response status description. It is currently always using the status code by default:

responses:
        200:
          description: "200"
        400:
          description: "400"

[Info] Similar project

Hey!
I think we started the exact same project in the same time, so in my view it would be interesting for both of us to look at each other's code: restdocs-openapi
I only had a little to do before with Java, Kotlin Maven etc, so I think what I did is a little lame and is a bit of an amateur project (like the commit messages…).
But if you want to grab ideas from it (like the Maven plugin), you're of course free to go!

How to control the name of the schema?

Is there any option to enforce the name of the schema in the generated contract?
Currently the name is generated from the URL of the service and hash code of the schema content.

It would be great to have a control of the name of the schema for the request and response.

Is there a known workaround for handling an array of string/enum?

I have an array of simple enum called weathers. Although I can get to the array itself:

fieldWithPath("weathers").type(JsonFieldType.ARRAY).description("an array of weathers")

I can't get to the element inside it currently due to the exact same limitation as: spring-projects/spring-restdocs#505. As such, I got the following openapi3 spec:

weathers:
          type: array
          description: an array of weathers
          items:
            oneOf:
            - type: object
            - type: boolean
            - type: string
            - type: number

It seems the relevant logic is added in this PR: #62 as "default behavior".

Here is a minified demo: https://github.com/lzhoucs/spring-restdocs/tree/87-demo/samples/junit5

What I would like to see is as follows:

weathers:
          type: array
          description: an array of weathers
          items:
            type: string
            enum: [SPRING, SUMMER...]

Is there any workaround to get close to what I expected above?

Is there any handling of link descriptors?

I executed the supplied sample app and the .links() part in com.epages.restdocs.apispec.sample.CartIntegrationTest#should_get_cart

        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("products", hasSize(1)))
                .andExpect(jsonPath("products[0].quantity", is(1)))
                .andExpect(jsonPath("products[0].product.name", notNullValue()))
                .andExpect(jsonPath("total", notNullValue()))
                .andDo(document("cart-get",
                        resource(
                                ResourceSnippetParameters.builder()
                                        .description("Get a cart by id")
                                        .pathParameters(
                                                parameterWithName("id").description("the cart id"))
                                        .responseFields(
                                                fieldWithPath("total").description("Total amount of the cart."),
                                                fieldWithPath("products").description("The product line item of the cart."),
                                                subsectionWithPath("products[]._links.product").description("Link to the product."),
                                                fieldWithPath("products[].quantity").description("The quantity of the line item."),
                                                subsectionWithPath("products[].product").description("The product the line item relates to."),
                                                subsectionWithPath("_links").description("Links section."))
                                        .links(
                                                linkWithRel("self").ignored(),
                                                linkWithRel("order").description("Link to order the cart."))
                                        .build()
                        )
                ));

ends up being just

      properties:
        _links:
          type: object
          description: Links section.

I don't actually know what it could be in openapi 3. Are there any plans to add more information from the link hypermedia documentation beyond what is there today?

Remove pretty print in example

Hello!

I would like to have a human-readable documentation with asciidoctor and a machine-readable documentation with this project. Is there a possibility to disable or un-pretty-print the JSON only for the resource.json?

I know of the possibility of Preprocessor but with this, I can only control both versions of the documentation.

For now, the example in the resource.json and the resulting .yml is riddled with escape and whitespace characters and when imported in postman, it's also shown there. It is impossible to use postman with the imported documentation, without removing these control elements beforehand. This is also shown in the README.md

Security Scheme for BasicAuth should be "basic" instead of "Basic"

The Security Scheme generated by SecuritySchemeGenerator.kt line 56 for BasicAuth should be "basic" rather than "Basic"
expected openapi document:
components: securitySchemes: basic: type: http scheme: basic
generated entry:
components: securitySchemes: basic: type: http scheme: Basic
The later one does not conform to openapi specification, so that it can not be interpreted by swagger ui & editor correctly, which leads to no BasicAuth header is added in the http-request.

Aggregate parameters & descriptions from multiple snippets

If the request path and method are equal, parameters used during different requests in tests should be aggregated into the same endpoint description.
Example:

Testcase1: GET /foo?param=1
Testcase2: GET /foo?otherParam="test"

should result in both params being documented.

Currently this is not the case, the ApiGenerator extracts the parameters from the first resource.json it finds. See OpenApi3Generator.kt

Aggregating the parameters would be useful, if valid requests to an endpoint as well es the error responses should be documented in the OpenAPI specification. In this case, making a request with missing parameters would make sense, to test and document the expected error code from the service.

Mutlipart file upload support

Trying to document a multipart file upload does not seem to generate any request part related documentation. For instance,

mockMvc.perform(RestDocumentationRequestBuilders.fileUpload("/assets)
                .file("file",
                        getSystemResourceAsStream("fixtures/assets/asset.jpg").readAllBytes()))
                .andExpect(status().isOk()).andDo(document("asset-post",
                        resourceDetails().description("Post an asset"),
                        requestParts(
                                partWithName("file").description("File to be uploaded")
                        ))

Help with WebTestClient and spring-auto-restdocs

It appears that webflux tests with WebTestClient is not supported. Is this something in radar and can a sample code be provided on top of the current adapters for rest assured and springmvc test?

Map responseBody examples to responseBody model.

Maybe related to #58

In my case, The generated openapi3.yaml looks like this:

openapi: 3.0.1
info:
  title: Grails-rest-seed API
  description: Grails-rest-seed API文档
  version: "1.0"
servers:
- url: http://localhost:8080
tags: []
paths:
  /api/getUploadAuthority:
    get:
      tags:
      - operation
      summary: OSS接口
      description: 阿里云OSS获取上传权限
      operationId: AliyunOSS
      parameters:
      - name: Authorization
        in: header
        description: JWT
        required: true
        schema:
          type: string
      responses:
        200:
          description: "200"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api-getUploadAuthority2026114897'
              examples:
                AliyunOSS:
                  value: "{\r\n  \"accessKeyId\" : \"mock\",\r\n  \"policy\" : \"\
                    eyJleHBpcmF0aW9uIjoiMjAxOS0wMy0yNlQxNjo1NToyOS41MDNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIxMzUwMDAwMDAwMSJdXX0=\"\
                    ,\r\n  \"signature\" : \"1slIn7u/Ruoptc+Hn6xBDOjNLWo=\",\r\n \
                    \ \"dir\" : \"13500000001\",\r\n  \"host\" : \"https://mock.oss-cn-hangzhou.aliyuncs.com\"\
                    ,\r\n  \"expire\" : 1553619329503,\r\n  \"cdnUrl\" : \"mock\"\r\
                    \n}"
components:
  schemas:
    api-getUploadAuthority2026114897:
      type: object
      required:
        - accessKeyId
        - signature
        - expire
        - host
        - dir
        - policy
      properties:
        accessKeyId:
          type: string
          description: OSS的access key id
        signature:
          type: string
          description: OSS认证成功后的签名
        cdnUrl:
          type: string
          description: 用于外部访问的CDN URL(可空)
        expire:
          type: number
          description: 授权过期时间
        host:
          type: string
          description: OSS访问主机
        dir:
          type: string
          description: 有权限上传的目录
        policy:
          type: string
          description: OSS的权限矩阵

But I want to see like this:

openapi: 3.0.1
info:
  title: Grails-rest-seed API
  description: Grails-rest-seed API文档
  version: "1.0"
servers:
- url: http://localhost:8080
tags: []
paths:
  /api/getUploadAuthority:
    get:
      tags:
      - operation
      summary: OSS接口
      description: 阿里云OSS获取上传权限
      operationId: AliyunOSS
      parameters:
      - name: Authorization
        in: header
        description: JWT
        required: true
        schema:
          type: string
      responses:
        200:
          description: "200"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api-getUploadAuthority2026114897'
components:
  schemas:
    api-getUploadAuthority2026114897:
      type: object
      required:
        - accessKeyId
        - signature
        - expire
        - host
        - dir
        - policy
      properties:
        accessKeyId:
          type: string
          description: OSS的access key id
          example: mock
        signature:
          type: string
          description: OSS认证成功后的签名
          example: 1slIn7u/Ruoptc+Hn6xBDOjNLWo=
        cdnUrl:
          type: string
          description: 用于外部访问的CDN URL(可空)
          example: mock
        expire:
          type: number
          description: 授权过期时间
          example: 1553619329503
        host:
          type: string
          description: OSS访问主机
          example: https://mock.oss-cn-hangzhou.aliyuncs.com
        dir:
          type: string
          description: 有权限上传的目录
          example: 1553619329503
        policy:
          type: string
          description: OSS的权限矩阵
          example: "eyJleHBpcmF0aW9uIjoiMjAxOS0wMy0yNlQxNjo1NToyOS41MDNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIxMzUwMDAwMDAwMSJdXX0="

Map the response examples to schemas.

Add titleDescription as an optional plugin attribute

I would like to add a title description as an optional attribute to the plugin so that I can set the description of the API in the Info object

e.g.

    host = 'localhost:8080'
    basePath = '/api'
    title = 'My API'
    titleDescription = 'My API description'
    version = '1.0.0'
    format = 'json'
}

openapi3 {
	server = 'https://localhost:8080'
	title = 'My API'
	titleDescription = 'My API description'
	version = '0.1.0'
	format = 'yaml'
}

MockMvcRestDocumentationWrapper doesn't seem to deal with "relaxed" fields

This is probably a feature request more than anything.

When the MockMvcRestDocumentationWrapper.document drop in replacement is used in snippets like below, the tests start generating errors despite the relaxed* factories for snippets used

        this.getMockMvc().perform(
                RestDocumentationRequestBuilders.post(copyHref).headers(getMockMvcHeaders(super.getTestToken()))
                        .content(locatorjson).accept(APPLICATION_HAL_JSON)
                        .contentType(APPLICATION_HAL_JSON))
                .andDo(
                        document("file/copy_file", REQUEST_PREPROCESSOR, RESPONSE_PREPROCESSOR,
                                requestHeaders(
                                        headerWithName(HttpHeaders.AUTHORIZATION).description(""),
                                        headerWithName(HttpHeaders.ACCEPT).description("")
                                ),
                                responseHeaders(
                                        headerWithName(HttpHeaders.CONTENT_TYPE)
                                                .description(""),
                                        headerWithName(HttpHeaders.LOCATION).description("")
                                ),
                                relaxedRequestFields(
                                        fieldWithPath("href").description(""),
                                        fieldWithPath("name").optional().description("")
                                )
                        )
                )
                .andExpect(status().isCreated()).andReturn();

then with the request

Accept: application/hal+json
{
  "href" : "value1",
  "id" : null,
  "name" : "value2",
  "allRenditions" : false
}

I see the following error

The following parts of the payload were not documented:
{
  "id" : null,
  "allRenditions" : false
}

when undocumented fields that are in request in this case should be ignored (we have some defaults in the input beans that we don't care about and want omitted in this case).

The same happens for other factories like

org.springframework.restdocs.hypermedia.HypermediaDocumentation#relaxedLinks(org.springframework.restdocs.hypermedia.LinkDescriptor...)
org.springframework.restdocs.payload.PayloadDocumentation#relaxedResponseFields(org.springframework.restdocs.payload.FieldDescriptor...)

etc

Default summary to path when empty

Many tools use the summary as the display name of a resource. We could default the summary to the path when the summary is not given.

Option to exclude examples

Gradle plugin generates following

openapi: 3.0.1
info:
  title: Testing ReDoc
  version: 1.0.0
servers:
- url: http://localhost
paths:
  /public/v1/integrations/recommended:
    get:
      tags:
      - public
      summary: Get Recommended Connectors - Public
      description: Returns list of connectors that are currently not installed, but
        are recommended based on the CSM and third party data.
      operationId: public-recommended-integrations
      responses:
        200:
          description: "200"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/public-v1-integrations-recommended47899855'
              examples:
                public-recommended-integrations:
                  value: '[{"providerId":"2d889796-8174-4c5d-84fb-50c8a87942fa","providerName":"slack","confidenceScore":0.97}]'
components:
  schemas:
    public-v1-integrations-recommended47899855:
      type: array
      items:
        type: object
        properties:
          confidenceScore:
            type: number
            description: ""
          providerId:
            type: string
            description: ""
          providerName:
            type: string
            description: ""

This causes ReDoc to NOT format the sample response.
image

When examples section is removed, it is formatted correctly and "Expand All" and "Collapse All" also work.

openapi: 3.0.1
info:
  title: Testing ReDoc
  version: 1.0.0
servers:
- url: http://localhost
paths:
  /public/v1/integrations/recommended:
    get:
      tags:
      - public
      summary: Get Recommended Connectors - Public
      description: Returns list of connectors that are currently not installed, but
        are recommended based on the CSM and third party data.
      operationId: public-recommended-integrations
      responses:
        200:
          description: "200"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/public-v1-integrations-recommended47899855'
#              examples:
#                public-recommended-integrations:
#                  value: '[{"providerId":"2d889796-8174-4c5d-84fb-50c8a87942fa","providerName":"slack","confidenceScore":0.97}]'
components:
  schemas:
    public-v1-integrations-recommended47899855:
      type: array
      items:
        type: object
        properties:
          confidenceScore:
            type: number
            description: ""
          providerId:
            type: string
            description: ""
          providerName:
            type: string
            description: ""

image

Further more, if the schema is updated with example, the sample data is populated.

openapi: 3.0.1
info:
  title: Testing ReDoc
  version: 1.0.0
servers:
- url: http://localhost
paths:
  /public/v1/integrations/recommended:
    get:
      tags:
      - public
      summary: Get Recommended Connectors - Public
      description: Returns list of connectors that are currently not installed, but
        are recommended based on the CSM and third party data.
      operationId: public-recommended-integrations
      responses:
        200:
          description: "200"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/public-v1-integrations-recommended47899855'
#              examples:
#                public-recommended-integrations:
#                  value: '[{"providerId":"2d889796-8174-4c5d-84fb-50c8a87942fa","providerName":"slack","confidenceScore":0.97}]'
components:
  schemas:
    public-v1-integrations-recommended47899855:
      type: array
      items:
        type: object
        properties:
          confidenceScore:
            type: number
            description: ""
            example: 0.97
          providerId:
            type: string
            description: ""
            example: 2d889796-8174-4c5d-84fb-50c8a87942fa
          providerName:
            type: string
            description: ""
            example: slack

image

Please let me know if I'm missing something here. Or if there's an easy way to achieve the same formatting.

If not, it would be great if this was the default behavior, or at least an option to exclude examples section, so ReDoc will format the sample data correctly.

Add description annotation

I would like to ask if it possible to add the functionality of reading the fields description from the the models if the fields annotated for example with @description annotation

parameter types

The schema type for path and request parameters is being set to string even for numeric values. For instance, for a path parameter that is defined as a Long, the generated document sets the schema type as a string when it should be an integer.

Unrecognized field "X"

In my CustomFieldDescriptor I also have an additional attribute for the internal restdocs document.

Sadly the object mapper does not like that

Unable to parse snippet file: /.../target/generated-snippets/.../resource.json: Unrecognized field "X"

Would be nice if unknown fields could be ignored here 👍

Add top level tags in generated open api spec

I would like the ability to configure top level tags through the plugin. As per example below tags can be configured with a name and description. When I import the spec into swagger ui I get my operations grouped by these tags.

  "swagger": "2.0",
  "info": {
    "description": "The API to for all things cart.",
    "version": "1.0.0",
    "title": "Cart API"
  },
  "host": "localhost:8080",
  "basePath": "/",
  "tags": [
    {
      "name": "Cart Services",
      "description": "All things related to cart."
    }
  ],
  "schemes": [
    "https"
  ],

Add `oneOf` composite schema support?

Currently we seems to always aggregate FieldDescriptors for different requests with the same path + method + mediaType, which is fine in most cases. However, in our case, we have an API that supports multiple schemas (within the same path + method + mediaType) by using a discriminator type polymorphism approach.

I created a simple pet demo branch for demo.

Currently the generated api spec looks like:

paths:
  /pet-demo:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pet-demo-1939693445'
components:
  schemas:
    pet-demo-1939693445:
      type: object
      properties:
        dogProp:
          type: string
          description: A dog specific property
        petType:
          type: string
          description: cat
        name:
          type: string
          description: Name of cat
        catProp:
          type: string
          description: A cat specific property

Expected api spec:

paths:
  /pet-demo:
    post:
      requestBody:
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/cat'
                - $ref: '#/components/schemas/dog'
              discriminator:
                propertyName: petType
components:
  schemas:
    cat:
      type: object
      properties:
        petType:
          type: string
          description: cat
        name:
          type: string
          description: Name of cat
        catProp:
          type: string
          description: A cat specific property
    dog:
      type: object
      properties:
        petType:
          type: string
          description: dog
        name:
          type: string
          description: Name of dog
        dogProp:
          type: string
          description: A dog specific property

Swagger UI recognizes it and render the oneOf composite schemas as follows:
image

Please let me know if there's any question. I can add more details if anything above is not clear.

Limited supported constraints in generated contract

According to the README document, it is possible to write custom ConstraintFields implementation.

However, when we look at the constraint resolver class, only limited subset of contraints can be emitted to the generated contract file.

For example, we have a pattern definition in JSON schema file, but we do not have it in generated contract file.

According to Spring REST Docs manual, there are way more contstraints which can be used.

I would like to ask if it is possible to extend the constraint resolver to cover all possibilities.

Multi-parameter URL with dash in between parameters not recognizing each parameter

While trying to use the openapi gradle task, I am running across an issue with the path parameters. My URL has two parameters separated by a dash and they are not being recognized as two separate parameters, but instead as one which includes the inner brackets and the dash (Ex. .../{param1}-{param2}/... gives me one single parameter "param1}-{param2").

My test is as follows:

@Test
  public void getPartiesByAbo_200success() throws Exception {
    this.mockMvc
            .perform(get("/v4/parties/{salesPlanAff}-{aboNum}/party", 1, 1L))
            .andExpect(status().isOk())
            .andDo(
                    document(
                            "getPartyByABO",
                            resource(
                                    ResourceSnippetParameters.builder()
                                            .description("Get all parties associated with a given aff-abo combination")
                                            .summary(
                                                    "This operation will return a list of partyIds and globalPartyIds associated with a given aff-abo.")
                                            .pathParameters(
                                                    parameterWithName("salesPlanAff")
                                                            .description(
                                                                    "The Sales Plan Affiliate portion of the aff-abo being searched for"),
                                                    parameterWithName("aboNum")
                                                            .description(
                                                                    "The ABO portion of the aff-abo being searched for"))
                                            .responseFields(
                                                    fieldWithPath("affAbo")
                                                            .description("The aff-abo whose parties are being searched for"),
                                                    fieldWithPath("partyIds")
                                                            .description(
                                                                    "The list of partyIds associated with the given aff-abo"),
                                                    fieldWithPath("globalPartyIds")
                                                            .description(
                                                                    "The list of globalPartyIds associated with the given aff-abo"))
                                            .build())));
  }

Asciidoctor is generating the snippet correctly with the individual parameters, but the openapi.yaml document that is generated when the openapi task is run gives the combined parameter.

Asciidoctor snippet:

{
  "operationId" : "getPartyByABO",
  "summary" : "This operation will return a list of partyIds and globalPartyIds associated with a given aff-abo.",
  "description" : "Get all parties associated with a given aff-abo combination",
  "privateResource" : false,
  "deprecated" : false,
  "request" : {
    "path" : "/v4/parties/{salesPlanAff}-{aboNum}/party",
    "method" : "GET",
    "contentType" : null,
    "headers" : [ ],
    "pathParameters" : [ {
      "name" : "salesPlanAff",
      "attributes" : { },
      "description" : "The Sales Plan Affiliate portion of the aff-abo being searched for",
      "ignored" : false,
      "type" : "STRING",
      "optional" : false
    }, {
      "name" : "aboNum",
      "attributes" : { },
      "description" : "The ABO portion of the aff-abo being searched for",
      "ignored" : false,
      "type" : "STRING",
      "optional" : false
    } ],
    "requestParameters" : [ ],
    "requestFields" : [ ],
    "example" : null,
    "securityRequirements" : null
  },
  "response" : {
    "status" : 200,
    "contentType" : "application/json",
    "headers" : [ ],
    "responseFields" : [ {
      "attributes" : { },
      "description" : "The aff-abo whose parties are being searched for",
      "ignored" : false,
      "path" : "affAbo",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "The list of partyIds associated with the given aff-abo",
      "ignored" : false,
      "path" : "partyIds",
      "type" : "ARRAY",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "The list of globalPartyIds associated with the given aff-abo",
      "ignored" : false,
      "path" : "globalPartyIds",
      "type" : "ARRAY",
      "optional" : false
    } ],
    "example" : "{\r\n  \"affAbo\" : \"1-1\",\r\n  \"partyIds\" : [ 1, 2 ],\r\n  \"globalPartyIds\" : [ 1, 2 ]\r\n}"
  },
  "tags" : [ "v4" ]
}

openapi.yaml:

/v4/parties/{salesPlanAff}-{aboNum}/party:
    get:
      tags:
      - v4
      summary: This operation will return a list of partyIds and globalPartyIds associated
        with a given aff-abo.
      description: Get all parties associated with a given aff-abo combination
      operationId: getPartyByABO
      produces:
      - application/json
      parameters:
      - name: salesPlanAff}-{aboNum
        in: path
        description: ""
        required: true
        type: string
      responses:
        200:
          description: ""
          examples:
            application/json: "{\r\n  \"affAbo\" : \"1-1\",\r\n  \"partyIds\" : [\
              \ 1, 2 ],\r\n  \"globalPartyIds\" : [ 1, 2 ]\r\n}"
          schema:
            $ref: '#/definitions/v4_parties_salesPlanAff-aboNum_party809631298'

Problematic json schema for unspecified arrays

If one does just document an array without documenting the item type we generate a json schema like this:

"serviceableCountries": {
  "description": "The list of target countries this payment method can be used in.",
  "type": "array"
}

This seems to be valid json schema but I have seen tools complaining about this. In such a case we should generate a schema like this:

"serviceableCountries": {
  "description": "The list of target countries this payment method can be used in.",
  "type": "array",
  "items": {
     "type": "object"
  }
}

OpenAPI 3 lost header example ?

Here is my resource.json:

$ cat build/generated-snippets/AliyunOSS/resource.json
{
  "operationId" : "AliyunOSS",
  "summary" : "OSS接口",
  "description" : "阿里云OSS获取上传权限",
  "privateResource" : false,
  "deprecated" : false,
  "request" : {
    "path" : "/api/getUploadAuthority",
    "method" : "GET",
    "contentType" : null,
    "headers" : [ {
      "name" : "Authorization",
      "attributes" : { },
      "description" : "JWT",
      "type" : "STRING",
      "optional" : false,
      "example" : "Bearer eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSldTdjI4VE1SVEhmYUZWS2lKQml3UVNRMWtvRWtMSW9WUk1tZm9MQkxxbWlKQ2xTQ0RuXC9IcDE2N01QMjlmbUZwUUpoZzVCUUNVa1Joajdsd0FMZndDQ2diVnpKeVNlTDJrdm1TcTgzTmwrN1wvTjk3K3QzZUVRbXJTRzNuRTRwZDhBU0NzeTRMZHBoMFE0b1RqTUxocTdsYmZ5c2dHTkNXdVArM3Z4NjNQOWNJVUZJYWx6WVZMSzh5Ukp3NUZLNHpYWlpYVElWMTF2T0NCVTN1b1lzeE1ibjBWUm1zVkRVcHY3Q1FwUVo0ZkpDZ0FcL0k5RUVSNkxYSVlBV0ZTRVh3Y2ZaNlp4c2k1OWwzdFltSHhFMkROZXhwczBOUDJaRTJNQ1pRb29NdkZWTGRJRE1zaW5TbVhGT3IxVzRxRFBBTk1sMmVoUnBkd0tQTEVkNkFjb0pKT3hwYUJjVTZFamdhd1RLM3BWRlZnSFhrNHFEWXpBbFpiNEZyaEdRcVpkWmlkZndsZWVWN212SmxLYXk0MkhmVEFCZSt3dzJmUjMwZVhkWlNZcGRDS3p2WFZvbm1ZbE40TWVUMVp0XC85NkhcL3F0U3VFb0FlM3o4NHB6Njh1a2Q2MzU4ZlhDbU9EeUpFckk2V1dZWTF1aXRYTWxPU25Ccnp5ejQrUDN4OGN2WGwyRHBWOXhQM1wvOTM5dWNlaFV2cXlUbEJubTlNaWJJSFp2d3Y4amZPbHMrSW5yT1cySkpKV0FFNlFjOEZPSkVvenRUaGd0VFwveDI1UHlUOVhEMXhlTEsyc09tMzlibUYrN2RHYXg1MUw1UXRPNm5qWVlhNTNqXC96OXZ2XC9ldVwva1BPSVRPNHltUUU2UDEwR05iT2tBK2IxNGNGczdjUHZcL2FLUDRReFh4OW5cL0FQckFjVUZ4QXdBQSIsInN1YiI6IjEzNTAwMDAwMDAxIiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJpc3MiOiJTcHJpbmcgU2VjdXJpdHkgUkVTVCBHcmFpbHMgUGx1Z2luIiwiZXhwIjoxNTUzNjE5NzQ4LCJpYXQiOjE1NTM2MTYxNDh9.X_8dSmjqBxkRVAD6sdth0XFJs7_uNWzgbnWWVWt2aR0"
    } ],
    "pathParameters" : [ ],
    "requestParameters" : [ ],
    "requestFields" : [ ],
    "example" : null,
    "securityRequirements" : null
  },
  "response" : {
    "status" : 200,
    "contentType" : "application/json",
    "headers" : [ ],
    "responseFields" : [ {
      "attributes" : { },
      "description" : "OSS的access key id",
      "ignored" : false,
      "path" : "accessKeyId",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "OSS的权限矩阵",
      "ignored" : false,
      "path" : "policy",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "OSS认证成功后的签名",
      "ignored" : false,
      "path" : "signature",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "有权限上传的目录",
      "ignored" : false,
      "path" : "dir",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "OSS访问主机",
      "ignored" : false,
      "path" : "host",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "授权过期时间",
      "ignored" : false,
      "path" : "expire",
      "type" : "NUMBER",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "用于外部访问的CDN URL(可空)",
      "ignored" : false,
      "path" : "cdnUrl",
      "type" : "STRING",
      "optional" : true
    } ],
    "example" : "{\r\n  \"accessKeyId\" : \"mock\",\r\n  \"policy\" : \"eyJleHBpcmF0aW9uIjoiMjAxOS0wMy0yNlQxNjowNzoyOS4zNjBaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIxMzUwMDAwMDAwMSJdXX0=\",\r\n  \"signature\" : \"B9T3N97XTWH9yereUvpn91M2A4w=\",\r\n  \"dir\" : \"13500000001\",\r\n  \"host\" : \"https://mock.oss-cn-hangzhou.aliyuncs.com\",\r\n  \"expire\" : 1553616449360,\r\n  \"cdnUrl\" : \"mock\"\r\n}"
  },
  "tags" : [ "operation" ]
}

You can see the request.headers field contains example field. But lost when convert to openapi3.yaml.

$ cat build/api-spec/openapi3.yaml
openapi: 3.0.1
info:
  title: Grails-rest-seed API
  description: Grails-rest-seed API文档
  version: "1.0"
servers:
- url: http://localhost:8080
tags: []
paths:
  /api/getUploadAuthority:
    get:
      tags:
      - operation
      summary: OSS接口
      description: 阿里云OSS获取上传权限
      operationId: AliyunOSS
      parameters:
      - name: Authorization
        in: header
        description: JWT
        required: true
        schema:
          type: string
      responses:
        200:
          description: "200"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api-getUploadAuthority2026114897'
              examples:
                AliyunOSS:
                  value: "{\r\n  \"accessKeyId\" : \"mock\",\r\n  \"policy\" : \"\
                    eyJleHBpcmF0aW9uIjoiMjAxOS0wMy0yNlQxNjowNzoyOS4zNjBaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIxMzUwMDAwMDAwMSJdXX0=\"\
                    ,\r\n  \"signature\" : \"B9T3N97XTWH9yereUvpn91M2A4w=\",\r\n \
                    \ \"dir\" : \"13500000001\",\r\n  \"host\" : \"https://mock.oss-cn-hangzhou.aliyuncs.com\"\
                    ,\r\n  \"expire\" : 1553616449360,\r\n  \"cdnUrl\" : \"mock\"\r\
                    \n}"
components:
  schemas:
    api-getUploadAuthority2026114897:
      type: object
      properties:
        accessKeyId:
          type: string
          description: OSS的access key id
        signature:
          type: string
          description: OSS认证成功后的签名
        cdnUrl:
          type: string
          description: 用于外部访问的CDN URL(可空)
        expire:
          type: number
          description: 授权过期时间
        host:
          type: string
          description: OSS访问主机
        dir:
          type: string
          description: 有权限上传的目录
        policy:
          type: string
          description: OSS的权限矩阵

How to convert to the openapi3 spec with all examples?

conflict with https://github.com/ajoberstar/reckon gradle plugin

I've added restdocs-openapi plugin to my build.gradle and now building fails with:

> Failed to apply plugin [id 'com.epages.restdocs-openapi']
   > Could not create an instance of type com.epages.restdocs.openapi.gradle.RestdocsOpenApiPluginExtension_Decorated.
      > org.ajoberstar.reckon.gradle.ReckonPlugin$DelayedVersion cannot be cast to java.lang.String

Empty schema in requestBody for application/x-www-form-urlencoded POST endpoints when using requestParameters as Spring REST Docs suggests

Hey!

Maybe I missed something, but for Spring REST Docs in order to generate examples for application/x-www-urlencoded you are using requestParameters:

https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#documenting-your-api-request-parameters

For POST methods it generates nice requests with url encoded format:

POST /oauth/token HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Authorization: Basic ....REDACTED...
Content-Length: 77
Host: localhost:8080

grant_type=password&scope=admin&username=...REDACTED...

However, in API specs we see this:

  /oauth/token:
    post:
    ...
      parameters:
      - name: grant_type
        in: query
        description: Type of the OAuth2 flow.
        required: true
        schema:
          type: string
      ...
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/oauth-token486549215'
            examples:
              oauth-token-200:
                value: 
                  ...
...
components:
  schemas:
    oauth-token486549215:
      type: object

Is there a way to move those fields from parameters section to the corresponding schema?

Investigate swagger warning `no property from null`

We see this warning when running the plugin in some situations:

no property from null, null, {ENUM=null, TITLE=null, DESCRIPTION=Optional field. The URL to use for registering shops to the app later., DEFAULT=null, PATTERN=null, DESCRIMINATOR=null, MIN_ITEMS=null, MAX_ITEMS=null, MIN_PROPERTIES=null, MAX_PROPERTIES=null, MIN_LENGTH=null, MAX_LENGTH=null, MINIMUM=null, MAXIMUM=null, EXCLUSIVE_MINIMUM=null, EXCLUSIVE_MAXIMUM=null, UNIQUE_ITEMS=null, EXAMPLE=null, TYPE=null, FORMAT=null, READ_ONLY=null, VENDOR_EXTENSIONS={}, MULTIPLE_OF=null}

It is coming from the swagger model:
https://github.com/swagger-api/swagger-core/blob/1.5/modules/swagger-core/src/main/java/io/swagger/util/PropertyDeserializer.java#L333

Attributes for HeaderDescriptor and ParameterDescriptor are ignored.

The restdocs type ParameterDescriptor contains an inherited map of attributes that may be used to define constraints.
But this attributes map is ignored when ParameterDescriptorWithType is created in ResourceSnippetParameters.kt:128.
Due to this problem constraints are ignored for path variables.

Edit @ozscheyge :
Same applies to HeaderDescriptorWithType

OpenAPI 3 lost schema properties required list.

Here is the snippet from require.json:

  "response" : {
    "status" : 200,
    "contentType" : "application/json",
    "headers" : [ ],
    "responseFields" : [ {
      "attributes" : { },
      "description" : "OSS的access key id",
      "ignored" : false,
      "path" : "accessKeyId",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "OSS的权限矩阵",
      "ignored" : false,
      "path" : "policy",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "OSS认证成功后的签名",
      "ignored" : false,
      "path" : "signature",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "有权限上传的目录",
      "ignored" : false,
      "path" : "dir",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "OSS访问主机",
      "ignored" : false,
      "path" : "host",
      "type" : "STRING",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "授权过期时间",
      "ignored" : false,
      "path" : "expire",
      "type" : "NUMBER",
      "optional" : false
    }, {
      "attributes" : { },
      "description" : "用于外部访问的CDN URL(可空)",
      "ignored" : false,
      "path" : "cdnUrl",
      "type" : "STRING",
      "optional" : true
    } ],

But convert to openapi3.yaml lost the optional field:

components:
  schemas:
    api-getUploadAuthority2026114897:
      type: object
      properties:
        accessKeyId:
          type: string
          description: OSS的access key id
        signature:
          type: string
          description: OSS认证成功后的签名
        cdnUrl:
          type: string
          description: 用于外部访问的CDN URL(可空)
        expire:
          type: number
          description: 授权过期时间
        host:
          type: string
          description: OSS访问主机
        dir:
          type: string
          description: 有权限上传的目录
        policy:
          type: string
          description: OSS的权限矩阵

OpenAPI 3 has required property list. See: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema-object

I hope optional from resource.json could render to required to openapi3.yaml.

components:
  schemas:
    api-getUploadAuthority2026114897:
      type: object
      required:                  # <======== Add required list except isOptioanl() is true
        - accessKeyId
        - signature
        - expire
        - host
        - dir
        - policy
      properties:
        accessKeyId:
          type: string
          description: OSS的access key id
        signature:
          type: string
          description: OSS认证成功后的签名
        cdnUrl:
          type: string
          description: 用于外部访问的CDN URL(可空)
        expire:
          type: number
          description: 授权过期时间
        host:
          type: string
          description: OSS访问主机
        dir:
          type: string
          description: 有权限上传的目录
        policy:
          type: string
          description: OSS的权限矩阵

Fix coverage report generation

Currently the coverage report is missing. The aggregation of the jacoco test reports from the submodules is not working any longer.

The merged executionData is still there - restdocs-openapi/build/jacoco/jacocoMerge.exec
But the resulting report of task jacocoRootReport is just empty.

Do not aggregate FieldDescriptors for different content types

When aggregating FieldDescriptors for different requests with the same path and method we now always aggregate the FieldDescriptors from all requests and create a JsonSchema. In case we have requests/response for different content types we should not do this.

A good example is PATCH /products/{id} in the sample app - here we have two requests with application/json-patch+json and application/json and we currently generate this schema:

type: "object"
    required:
    - "name"
    - "price"
    properties:
      '[]':
        type: "object"
        properties:
          path:
            type: "string"
            description: "The path of the field."
          op:
            type: "string"
            description: "Patch operation."
          value:
            type: "string"
            description: "The value to assign."
      price:
        type: "number"
        description: "The price of the product."
      name:
        type: "string"
        description: "The name of the product."
        minLength: 1

This is obviously wrong.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.