epages-de / restdocs-api-spec Goto Github PK
View Code? Open in Web Editor NEWAdds API specification support to Spring REST Docs
License: MIT License
Adds API specification support to Spring REST Docs
License: MIT License
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
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 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.
Hello all,
do your software generates rest service doc automatically, because i checked your code and i seen that your rest docs written manually regarding your service; so if I change my rest service, the rest docs are not going to generate automatically. Is there anyway to get my rest service automatically for open api 3.0.
Why I can't get my final result like this example in swagger 2.0?
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:
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
products-create
in the name
field of the response object.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?
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"
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!
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.
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?
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?
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
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.
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.
It would be nice if we can also use Variables as the baseUrl for generated Postman collections, to use different environments easily.
See https://learning.getpostman.com/docs/postman/environments_and_globals/variables/ for documentation.
With the current version of restdocs-api-spec i get an Exception java.net.URISyntaxException: Illegal character in authority at index 8: https://{{url}}:8080
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")
))
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?
Can you please provide example how to configure openapi3 task in build.gradle.kts?
Following up on #73 and ScaCap/spring-auto-restdocs#281
for making restdocs-api-spec
and spring-auto-restdocs
work together I saw this interesting project that seems to have done it:
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.
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'
}
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
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.
It seems currently we only support Oauth2 and Basic authentication type. It would be nice to see more authentication types added:
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.
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: ""
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
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.
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
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.
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 possibility to add tags to an operation.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#tagObject
It would be also useful to default the tag to something useful
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"
],
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:
Please let me know if there's any question. I can add more details if anything above is not clear.
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.
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'
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"
}
}
Add OpenAPI Vendor Extensions to add more details to API docs such as logo, etc.
OpenAPI 3 Vendor Ext. Ref. and ReDoc Ref.
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?
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
Hey!
Maybe I missed something, but for Spring REST Docs in order to generate examples for application/x-www-urlencoded
you are using requestParameters
:
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?
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
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
I found this project that aims to automatically generate documentation for a Spring project: https://github.com/ScaCap/spring-auto-restdocs
Unfortunately, the output format is only to be used with AsciiDoctor and this library is not so flexible.
Would it then be possible to add a javadoc processor to restdocs-openapi to obtain and generate the beans documentation?
This is minor functionaility, but I wonder if we could add support for contactObject?
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的权限矩阵
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.
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.
The culprit of this is explained here: Redocly/redoc#205
To get the collapse/expand functionality there, the examples should be stored in YAML format.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.