GithubHelp home page GithubHelp logo

epages-de / restdocs-api-spec Goto Github PK

View Code? Open in Web Editor NEW
375.0 20.0 100.0 991 KB

Adds API specification support to Spring REST Docs

License: MIT License

Kotlin 83.67% Shell 0.70% Java 15.63%
spring-rest-docs openapi rest-api-documentation spring openapi-documentation

restdocs-api-spec's Introduction

Spring REST Docs API specification Integration

oss lifecycle Coverage Maven Central Gitter

This is an extension that adds API specifications as an output format to Spring REST Docs. It currently supports:

Please note that this extension was developed for JSON-based APIs. We do not expect this extension to build usable API specification for non-JSON request or response bodies.

Motivation

Spring REST Docs is a great tool to produce documentation for your RESTful services that is accurate and readable.

We especially like its test-driven approach and this is the main reason why we chose it.

It offers support for AsciiDoc and Markdown. This is great for generating simple HTML-based documentation. But both are markup languages and thus it is hard to get any further than statically generated HTML.

API specifications like OpenAPI are a lot more flexible. With e.g. OpenAPI you get a machine-readable description of your API. There is a rich ecosystem around it that contains tools to:

  • generate a HTML representation of your API - ReDoc
  • generate an interactive API reference - e.g. using services like stoplight.io or readme.io

Also, API specifications like OpenAPI are supported by many REST clients like Postman and Paw. Thus having an API specification for a REST API is a great plus when starting to work with it.

The most common use case to generate an OpenAPI specification is code introspection and adding documentation related annotations to your code. We do not like enriching our production code with this information and clutter it with even more annotations. We agree with Spring REST Docs that the test-driven way to produce accurate API documentation is the way to go. This is why we came up with this project.

Getting started

Version compatibility

Spring Boot and Spring REST Docs 3.0.0 introduced breaking chances to how request parameters are documented: RequestParameterSnippet was split into QueryParameterSnippet and FormParameterSnippet.

Spring Boot version restdocs-api-spec version
3.x 0.17.1 or later
2.x 0.16.4

Project structure

The project consists of the following main components:

  • restdocs-api-spec - contains the actual Spring REST Docs extension. This is most importantly the ResourceDocumentation which is the entry point to use the extension in your tests. The ResourceSnippet is the snippet used to produce a json file resource.json containing all the details about the documented resource.
  • restdocs-api-spec-mockmvc - contains a wrapper for MockMvcRestDocumentation for easier migration to restdocs-api-spec from MockMvc tests that use plain spring-rest-docs-mockmvc.
  • restdocs-api-spec-restassured - contains a wrapper for RestAssuredRestDocumentation for easier migration to restdocs-api-spec from Rest Assured tests that use plain spring-rest-docs-restassured.
  • restdocs-api-spec-gradle-plugin - adds a gradle plugin that aggregates the resource.json files produced by ResourceSnippet into an API specification file for the whole project.

Build configuration

Gradle

  1. Add the plugin
    • Using the plugins DSL:
      plugins {
          id 'com.epages.restdocs-api-spec' version '0.18.2'
      }
      Examples with Kotlin are also available here
    • OR Using legacy plugin application:
      • 1.1 Use of buildscript requires you to add the https://plugins.gradle.org/m2/ repository.
      • 1.2 add the dependency to restdocs-api-spec-gradle-plugin
      • 1.3 apply restdocs-api-spec-gradle-plugin
      buildscript {
        repositories {
          maven {
            url "https://plugins.gradle.org/m2/" //1.1
          }
        }
        dependencies {
          classpath "com.epages:restdocs-api-spec-gradle-plugin:0.18.2" //1.2
        }
      }
      
      apply plugin: 'com.epages.restdocs-api-spec' //1.3
      
  2. Add required dependencies to your tests
    • 2.1 add the mavenCentral repository used to resolve the com.epages:restdocs-api-spec module of the project.
    • 2.2 add the actual restdocs-api-spec-mockmvc dependency to the test scope. Use restdocs-api-spec-restassured if you use RestAssured instead of MockMvc.
    • 2.3 add configuration options for restdocs-api-spec-gradle-plugin. See Gradle plugin configuration
    repositories { //2.1
        mavenCentral()
    }
    
    dependencies {
        //..
        testImplementation('com.epages:restdocs-api-spec-mockmvc:0.18.2') //2.2
    }
    
    openapi { //2.3
        host = 'localhost:8080'
        basePath = '/api'
        title = 'My API'
        description = 'My API description'
        tagDescriptionsPropertiesFile = 'src/docs/tag-descriptions.yaml'
        version = '1.0.0'
        format = 'json'
    }
    
    openapi3 {
        server = 'https://localhost:8080'
        title = 'My API'
        description = 'My API description'
        tagDescriptionsPropertiesFile = 'src/docs/tag-descriptions.yaml'
        version = '0.1.0'
        format = 'yaml'
    }
    
    postman {
        title = 'My API'
        version = '0.1.0'
        baseUrl = 'https://localhost:8080'
    }

See the build.gradle for the setup used in the sample project.

Maven

The root project does not provide a maven plugin. But you can find a plugin that works with restdocs-api-spec at BerkleyTechnologyServices/restdocs-spec.

Usage with Spring REST Docs

The class ResourceDocumentation contains the entry point for using the ResourceSnippet.

The most basic form does not take any parameters:

mockMvc
  .perform(post("/carts"))
  .andDo(document("carts-create", resource("Create a cart")));

This test will produce the resource.json file in the snippets directory. This file just contains all the information that we can collect about the resource. The format of this file is not specific to an API specification.

{
  "operationId" : "carts-create",
  "summary" : "Create a cart",
  "description" : "Create a cart",
  "privateResource" : false,
  "deprecated" : false,
  "request" : {
    "path" : "/carts",
    "method" : "POST",
    "contentType" : null,
    "headers" : [ ],
    "pathParameters" : [ ],
    "requestParameters" : [ ],
    "requestFields" : [ ],
    "example" : null,
    "securityRequirements" : null
  },
  "response" : {
    "status" : 201,
    "contentType" : "application/hal+json",
    "headers" : [ ],
    "responseFields" : [ ],
    "example" : "{\n  \"total\" : 0,\n  \"products\" : [ ],\n  \"_links\" : {\n    \"self\" : {\n      \"href\" : \"http://localhost:8080/carts/4\"\n    },\n    \"order\" : {\n      \"href\" : \"http://localhost:8080/carts/4/order\"\n    }\n  }\n}"
  }
}

Just like with Spring REST Docs we can also describe request fields, response fields, path variables, parameters, headers, and links. Furthermore you can add a text description and a summary for your resource. The extension also discovers JWT tokens in the Authorization header and will document the required scopes from it. Also basic auth headers are discovered and documented.

The following example uses ResourceSnippetParameters to document response fields, path parameters, and links. We paid close attention to keep the API as similar as possible to what you already know from Spring REST Docs. fieldWithPath and linkWithRel are actually still the static methods you would use in your using Spring REST Docs test.

mockMvc.perform(get("/carts/{id}", cartId)
  .accept(HAL_JSON))
  .andExpect(status().isOk())
  .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())));

Please see the CartIntegrationTest in the sample application for a detailed example.

⚠️ Use template URIs to refer to path variables in your request

Note how we use the urlTemplate to build the request with RestDocumentationRequestBuilders. This makes the urlTemplate available in the snippet and we can depend on the non-expanded template when generating the OpenAPI file.

mockMvc.perform(get("/carts/{id}", cartId)

Documenting Bean Validation constraints

Similar to the way Spring REST Docs allows to use bean validation constraints to enhance your documentation, you can also use the constraints from your model classes to let restdocs-api-spec enrich the generated JsonSchemas. restdocs-api-spec provides the class com.epages.restdocs.apispec.ConstrainedFields to generate FieldDescriptors that contain information about the constraints on this field.

Currently the following constraints are considered when generating JsonSchema from FieldDescriptors that have been created via com.epages.restdocs.apispec.ConstrainedFields

  • NotNull, NotEmpty, and NotBlank annotated fields become required fields in the JsonSchema
  • for String fields annotated with NotEmpty, and NotBlank the minLength constraint in JsonSchema is set to 1
  • for String fields annotated with Length the minLength and maxLength constraints in JsonSchema are set to the value of the corresponding attribute of the annotation
  • for String fields annotated with Pattern, the pattern constraint is propagated to JsonSchema
  • for Number fields annotated with Min, the minimum constraint is propagated to JsonSchema
  • for Number fields annotated with Max, the maximum constraint is propagated to JsonSchema
  • for Number fields annotated with Size the minimum and maximum constraints in JsonSchema are set to the value of the corresponding attribute of the annotation

If you already have your own ConstraintFields implementation you can also add the logic from com.epages.restdocs.apispec.ConstrainedFields to your own class. Here it is important to add the constraints under the key validationConstraints into the attributes map if the FieldDescriptor.

Migrate existing Spring REST Docs tests

MockMvc based tests

For convenience when applying restdocs-api-spec to an existing project that uses Spring REST Docs, we introduced com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.

In your tests you can just replace calls to MockMvcRestDocumentation.document with the corresponding variant of MockMvcRestDocumentationWrapper.document.

MockMvcRestDocumentationWrapper.document will execute the specified snippets and also add a ResourceSnippet equipped with the input from your snippets.

Here is an example:

resultActions
  .andDo(
    MockMvcRestDocumentationWrapper.document(operationName,
      requestFields(new FieldDescriptors().getFieldDescriptors()),
      responseFields(
        fieldWithPath("comment").description("the comment"),
        fieldWithPath("flag").description("the flag"),
        fieldWithPath("count").description("the count"),
        fieldWithPath("id").description("id"),
        fieldWithPath("_links").ignored()
      ),
      links(linkWithRel("self").description("some"))
  )
);

This will do exactly what MockMvcRestDocumentation.document does. Additionally it will add a ResourceSnippet with the descriptors you provided in the RequestFieldsSnippet, ResponseFieldsSnippet, and LinksSnippet.

REST Assured based tests

Also for REST Assured we offer a convenience wrapper similar to MockMvcRestDocumentationWrapper. The usage for REST Assured is also similar to MockMVC, except that com.epages.restdocs.apispec.RestAssuredRestDocumentationWrapper is used instead of com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.

To use the RestAssuredRestDocumentationWrapper, you have to add a dependency to restdocs-api-spec-restassured to your build.

RestAssured.given(this.spec)
        .filter(RestAssuredRestDocumentationWrapper.document("{method-name}",
                "The API description",
                requestParameters(
                        parameterWithName("param").description("the param")
                ),
                responseFields(
                        fieldWithPath("doc.timestamp").description("Creation timestamp")
                )
        ))
        .when()
        .queryParam("param", "foo")
        .get("/restAssuredExample")
        .then()
        .statusCode(200);

WebTestClient based tests

We also offer a convenience wrapper for WebTestClient which works similar to MockMvcRestDocumentationWrapper. The usage is similar to MockMVC, except that com.epages.restdocs.apispec.WebTestClientRestDocumentationWrapper is used instead of com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.

To use the WebTestClientRestDocumentationWrapper, you will have to add a dependency to restdocs-api-spec-webtestclient to your build.

webTestClient.get().uri("/sample/{id}?queryParam=something", "1024").exchange()
    .expectStatus().isOk().expectBody()
    .consumeWith(
        WebTestClientRestDocumentationWrapper
            .document("sample",
                RequestDocumentation.pathParameters(
                    parameterWithName("id").description(
                        "description of the path parameter")
                ),
                RequestDocumentation.requestParameters(
                    parameterWithName("queryParam").description(
                        "description of the query parameter")
                ),
                HeaderDocumentation.responseHeaders(
                    headerWithName(HttpHeaders.CONTENT_TYPE)
                        .description(MediaType.APPLICATION_JSON_UTF8_VALUE)
                ),
                responseFields(
                    PayloadDocumentation.fieldWithPath("field1").type(JsonFieldType.STRING)
                        .description("description of field1"),
                    PayloadDocumentation.fieldWithPath("field2").type(JsonFieldType.STRING)
                        .description("description of field2")
                )
            )
    );

Security Definitions in OpenAPI

The project has limited support for describing security requirements of an API. Currently we only support Oauth2 with JWT tokens and HTTP Basic Auth.

restdocs-api-spec inspects the AUTHORIZATION header of a request for a JWT token. Also the a HTTP basic authorization header is discovered and documented. If such a token is found the scopes are extracted and added to the resource.json snippet.

The restdocs-api-spec-gradle-plugin will consider this information if the oauth2SecuritySchemeDefinition configuration option is set (see Gradle plugin configuration). This will result in a top-level securityDefinitions in the OpenAPI definition. Additionally the required scopes will be added in the security section of an operation.

Running the gradle plugin

restdocs-api-spec-gradle-plugin is responsible for picking up the generated resource.json files and aggregate them into an API specification.

OpenAPI 2.0

In order to generate an OpenAPI 2.0 specification we use the openapi task:

./gradlew openapi

OpenAPI 3.0.1

In order to generate an OpenAPI 3.0.1 specification we use the openapi3 task:

./gradlew openapi3

For our sample project this creates a openapi3.yaml file in the output directory (build/api-spec).

Postman

In order to generate a Postman collection we use the postman task:

./gradlew postman

For our sample project this creates a postman-collection.json file in the output directory (build/api-spec).

Gradle plugin configuration

Common configuration for all formats

Name Description Default value
separatePublicApi Should the plugin generate additional API specification files which do not contain the resources marked as private false
outputDirectory The output directory for the API specification files build/api-spec
snippetsDirectory The directory Spring REST Docs generated the snippets to build/generated-snippets

Common OpenAPI configuration

The restdocs-api-spec-gradle-plugin takes the following configuration options for OpenAPI 2.0 and OpenAPI 3.0.1 - all are optional.

Name Description Default value
title The title of the application. Used for the title attribute in the Info object API documentation
description A description of the application. Used for the description attribute in the Info object empty
version The version of the api. Used for the version attribute in the Info object project version
format The format of the output OpenAPI file - supported values are json and yaml json
tagDescriptionsPropertiesFile A yaml file mapping tag names to descriptions. These are populated into the top level ` Tags attribute no default - if not provided no tags created.
oauth2SecuritySchemeDefinition Closure containing information to generate the securityDefinitions object in the OpenAPI specification. empty
oauth2SecuritySchemeDefinition.flows The Oauth2 flows the API supports. Use valid values from the securityDefinitions specification. no default - required if oauth2SecuritySchemeDefinition is set.
oauth2SecuritySchemeDefinition.tokenUrl The Oauth2 tokenUrl no default - required for the flows password, application, accessCode.
oauth2SecuritySchemeDefinition.authorizationUrl The Oauth2 authorizationUrl no default - required for the flows implicit, accessCode.
oauth2SecuritySchemeDefinition.scopeDescriptionsPropertiesFile A yaml file mapping scope names to descriptions. These are used in the securityDefinitions as the scope description no default - if not provided the scope descriptions default to No description.

The scopeDescriptionsPropertiesFile is supposed to be a yaml file:

scope-name: A description

OpenAPI 2.0

The restdocs-api-spec-gradle-plugin takes the following configuration options for OpenAPI 2.0 - all are optional.

Name Description Default value
host The host serving the API - corresponds to the attribute with the same name in the OpenAPI root object localhost
basePath The base path on which the API is served - corresponds to the attribute with the same name in the OpenAPI root object null
schemes The supported transfer protocols of the API - corresponds to the attribute with the same name in the OpenAPI root object ['http'"]
outputFileNamePrefix The file name prefix of the output file. openapi which results in e.g. openapi.json for the format json

Example configuration closure:

openapi {
    basePath = "/api"
    host = "api-shop.beyondshop.cloud"
    schemes = ["https"]
    format = "yaml"
    title = 'Beyond REST API'
    version = "1.0.0"
    separatePublicApi = true
    snippetsDirectory="src/docs/asciidoc/generated-snippets/"
    outputDirectory="openapi/"
    oauth2SecuritySchemeDefinition = {
        flows = ['accessCode', 'application']
        tokenUrl = 'https://api-shop.beyondshop.cloud/api/oauth/token'
        authorizationUrl = 'https://api-shop.beyondshop.cloud/api/auth/oauth-ext/authorize'
        scopeDescriptionsPropertiesFile = "src/docs/scope-descriptions.yaml"
    }
}

OpenAPI 3.0.1

The restdocs-api-spec-gradle-plugin takes the following configuration options for OpenAPI 3.0.1 - all are optional.

Name Description Default value
outputFileNamePrefix The file name prefix of the output file. openapi3 which results in e.g. openapi3.json for the format json
servers Specifies the servers the API is available from. Use this property to specify multiple server definitions. See example below. http://localhost
server Specifies the servers the API is available from. Use this property to specify just a single server definition. See example below http://localhost

Example configuration closure:

openapi3 {
    servers = [ { url = "http://some.api" } ]
    title = 'My API title'
    version = '1.0.1'
    format = 'yaml'
    contact = {
        name = 'John Doe'
        email = '[email protected]'
    }
    separatePublicApi = true
    outputFileNamePrefix = 'my-api-spec'
    oauth2SecuritySchemeDefinition = {
        flows = ['authorizationCode']
        tokenUrl = 'http://example.com/token'
        authorizationUrl = 'http://example.com/authorize'
        scopeDescriptionsPropertiesFile = "scopeDescriptions.yaml"
    }
}

Example build.gradle.kts configuration closure (by axkb, #112):

configure<com.epages.restdocs.apispec.gradle.OpenApi3Extension> {
    setServer("http://$apiHost:$apiPort")
    title = "Your title"
    description = "Your description"
    version = "0.1.0"
    format = "json"
    tagDescriptionsPropertiesFile = "src/test/resources/tags.yaml"
}

The servers and server property can also contain variables. Is this case the` property can be specified like this:

This configuration follows the same semantics as the 'Servers Object' in the OpenAPI specification

servers = [ {
    url = 'https://{host}/api'
    variables = [
        host: [
            default: 'api-shop.beyondshop.cloud/api',
            description: 'The hostname of your beyond shop',
            enum: ['api-shop', 'oz']
        ]
    ]
} ]

The same structure applies to server. A single server can also be specified using a plain string:

server = 'http://some.api/api'

Postman

The restdocs-api-spec-gradle-plugin takes the following configuration options for Postman collections - all are optional.

Name Description Default value
title The title of the application. Used for the name attribute of the Information object of the collection API documentation
version The version of the api. Used for the version attribute in the Information object project version if specified - otherwise 1.0.0
baseUrl The baseUrl of the application. e.g. https://myapi.example.com:8080/api http://localhost

Example configuration closure:

postman {
    title = 'Beyond REST API'
    version = '1.0.1'
    baseUrl = 'https://api-shop.beyondshop.cloud/api'
    separatePublicApi = true
    outputFileNamePrefix = 'my-postman-collection'
}

Generate an HTML-based API reference from OpenAPI

We can use redoc to generate an HTML API reference from our OpenAPI specification.

The redoc-cli can be used to bundle (and serve) this API reference:

# Install redoc-cli
npm install -g redoc-cli

# Bundle the documentation into a zero-dependency HTML-file
redoc-cli bundle build/api-spec/openapi.json

# Bundle and serve
redoc-cli serve build/api-spec/openapi.json

Maintenance

This section of the README is targeted at project maintainers.

Publish project

The project is published with the help of GitHub Actions. It's version number is determined by the Git tags (see allegro/axion-release-plugin). The Java dependencies are published to Sonatype with the help of the gradle-nexus/publish-plugin and the Maven Publish Plugin. The Gradle plugin is published to the Gradle plugin portal with the help of the 'plugin-publish' plugin (see docs.gradle.org).

Given that the master branch on the upstream repository is in the state from which you want to create a release, execute the following steps:

(1) Create release

Create release via the GitHub UI.

Use the intended version number as "Tag version", e.g. "0.18.2". This will automatically trigger a GitHub Action build which publishes the JAR files for this release to Sonatype.

(2) Login to Sonatype

Login to Sonatype and navigate to the staging repositories.

(3) Close the staging repository

Select the generated staging repository and close it. Check that there are no errors afterwards (e.g. missing signatures or Javadoc JARs).

(4) Release the repository

Select the generated staging repository and release it. After few minutes, the release should be available in the "Public Repositories" of ePages.

(5) Update documentation

Create a new commit which updates the version numbers in the README file.

restdocs-api-spec's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

restdocs-api-spec's Issues

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?

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?

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.

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.

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.

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 👍

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.

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"
  }
}

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

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?

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")
                        ))

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

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.

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

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"
  ],

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

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.

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.

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

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.

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'
}

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

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 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 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?

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.

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

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.

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?

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?

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'

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.

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的权限矩阵

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.

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.

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.