From e6eab6a315cae593b4dddc1efb3c403055ab688f Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 09:12:06 +0100 Subject: [PATCH 1/9] moved working test case --- .../hauner/openapi/generatr/GeneratrEndToEndTest.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy index 2849832e..52767761 100644 --- a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy +++ b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy @@ -17,7 +17,7 @@ package com.github.hauner.openapi.generatr import org.junit.runner.RunWith -import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized /** @@ -35,7 +35,8 @@ class GeneratrEndToEndTest extends GeneratrTestBase { new TestSet(name: 'response-complex-data-types'), new TestSet(name: 'ref-into-another-file'), new TestSet(name: 'params-simple-data-types'), - new TestSet(name: 'response-array-data-type-mapping') + new TestSet(name: 'response-array-data-type-mapping'), + new TestSet(name: 'path-params-simple-data-types') ] } From f5e46ad6a88a3c119b76904f9516621001888d32 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 09:14:50 +0100 Subject: [PATCH 2/9] improved layout/order --- .../hauner/openapi/generatr/GeneratrEndToEndTest.groovy | 6 +++--- .../generated/api/EndpointApi.java | 0 .../openapi.yaml | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename src/testInt/resources/{path-params-simple-data-types => params-path-simple-data-types}/generated/api/EndpointApi.java (100%) rename src/testInt/resources/{path-params-simple-data-types => params-path-simple-data-types}/openapi.yaml (100%) diff --git a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy index 52767761..8e25d6c6 100644 --- a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy +++ b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy @@ -30,13 +30,13 @@ class GeneratrEndToEndTest extends GeneratrTestBase { @Parameterized.Parameters(name = "{0}") static Collection sources () { return [ + new TestSet(name: 'ref-into-another-file'), new TestSet(name: 'no-response-content'), new TestSet(name: 'response-simple-data-types'), new TestSet(name: 'response-complex-data-types'), - new TestSet(name: 'ref-into-another-file'), - new TestSet(name: 'params-simple-data-types'), new TestSet(name: 'response-array-data-type-mapping'), - new TestSet(name: 'path-params-simple-data-types') + new TestSet(name: 'params-simple-data-types'), + new TestSet(name: 'params-path-simple-data-types') ] } diff --git a/src/testInt/resources/path-params-simple-data-types/generated/api/EndpointApi.java b/src/testInt/resources/params-path-simple-data-types/generated/api/EndpointApi.java similarity index 100% rename from src/testInt/resources/path-params-simple-data-types/generated/api/EndpointApi.java rename to src/testInt/resources/params-path-simple-data-types/generated/api/EndpointApi.java diff --git a/src/testInt/resources/path-params-simple-data-types/openapi.yaml b/src/testInt/resources/params-path-simple-data-types/openapi.yaml similarity index 100% rename from src/testInt/resources/path-params-simple-data-types/openapi.yaml rename to src/testInt/resources/params-path-simple-data-types/openapi.yaml From 64bb8e3aac8ce0273fe4296556eeaa47adc740a8 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 09:15:54 +0100 Subject: [PATCH 3/9] request body test case --- .../generatr/GeneratrPendingTest.groovy | 2 +- .../generated/api/Api.java | 17 ++++++++++ .../generated/model/Book.java | 34 +++++++++++++++++++ .../params-request-body/openapi.yaml | 30 ++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/testInt/resources/params-request-body/generated/api/Api.java create mode 100644 src/testInt/resources/params-request-body/generated/model/Book.java create mode 100644 src/testInt/resources/params-request-body/openapi.yaml diff --git a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrPendingTest.groovy b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrPendingTest.groovy index c406a223..9d15e406 100644 --- a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrPendingTest.groovy +++ b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrPendingTest.groovy @@ -27,7 +27,7 @@ class GeneratrPendingTest extends GeneratrTestBase { @Parameterized.Parameters(name = "{0}") static Collection sources () { return [ - new TestSet(name: 'path-params-simple-data-types') + new TestSet(name: 'params-request-body') ] } diff --git a/src/testInt/resources/params-request-body/generated/api/Api.java b/src/testInt/resources/params-request-body/generated/api/Api.java new file mode 100644 index 00000000..a697cf81 --- /dev/null +++ b/src/testInt/resources/params-request-body/generated/api/Api.java @@ -0,0 +1,17 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-generatr-spring. + * DO NOT EDIT. + */ + +package generated.api; + +import generated.model.Book; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; + +public interface Api { + + @PostMapping(path = "/book", consumes = {"application/json"}, produces = {"application/json"}) + ResponseEntity postBook(@RequestBody Book body); + +} diff --git a/src/testInt/resources/params-request-body/generated/model/Book.java b/src/testInt/resources/params-request-body/generated/model/Book.java new file mode 100644 index 00000000..82ea3638 --- /dev/null +++ b/src/testInt/resources/params-request-body/generated/model/Book.java @@ -0,0 +1,34 @@ +/* + * This class is auto generated by https://github.com/hauner/openapi-generatr-spring. + * DO NOT EDIT. + */ + +package generated.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Book { + + @JsonProperty("isbn") + private String isbn; + + @JsonProperty("title") + private String title; + + public String getIsbn () { + return isbn; + } + + public void setIsbn (String isbn) { + this.isbn = isbn; + } + + public String getTitle () { + return title; + } + + public void setTitle (String title) { + this.title = title; + } + +} diff --git a/src/testInt/resources/params-request-body/openapi.yaml b/src/testInt/resources/params-request-body/openapi.yaml new file mode 100644 index 00000000..8675d464 --- /dev/null +++ b/src/testInt/resources/params-request-body/openapi.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.2 +info: + title: test request body parameters + version: 1.0.0 + +paths: + /book: + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + responses: + '201': + description: created book + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + +components: + schemas: + Book: + type: object + properties: + isbn: + type: string + title: + type: string From 79e5fce9dd53f43d7a7e5cc0708e8fc9bed533b0 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 15:09:36 +0100 Subject: [PATCH 4/9] typo --- .../com/github/hauner/openapi/spring/model/Response.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/com/github/hauner/openapi/spring/model/Response.groovy b/src/main/groovy/com/github/hauner/openapi/spring/model/Response.groovy index a0491472..22f42b25 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/model/Response.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/model/Response.groovy @@ -20,7 +20,7 @@ import com.github.hauner.openapi.spring.model.datatypes.DataType import com.github.hauner.openapi.spring.model.datatypes.NoneDataType /** - * Endpoint response properties, + * Endpoint response properties. * * @author Martin Hauner */ From 6f3cd50f636717614298bb0f00bec4b53b6521ed Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 15:14:15 +0100 Subject: [PATCH 5/9] handle request body --- .../spring/converter/ApiConverter.groovy | 26 ++++++++ .../openapi/spring/model/Endpoint.groovy | 7 +- .../openapi/spring/model/RequestBody.groovy | 48 ++++++++++++++ .../ApiConverterRequestBodySpec.groovy | 66 +++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/main/groovy/com/github/hauner/openapi/spring/model/RequestBody.groovy create mode 100644 src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterRequestBodySpec.groovy diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiConverter.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiConverter.groovy index 017c8e68..70af384b 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiConverter.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiConverter.groovy @@ -18,6 +18,7 @@ package com.github.hauner.openapi.spring.converter import com.github.hauner.openapi.spring.model.Api import com.github.hauner.openapi.spring.model.Endpoint +import com.github.hauner.openapi.spring.model.RequestBody import com.github.hauner.openapi.spring.model.parameters.CookieParameter import com.github.hauner.openapi.spring.model.parameters.HeaderParameter import com.github.hauner.openapi.spring.model.parameters.Parameter as ModelParameter @@ -88,6 +89,26 @@ class ApiConverter { ep.parameters.addAll (createParameter(parameter, target, resolver)) } + if (httpOperation.requestBody != null) { + def required = httpOperation.requestBody.required != null ?: false + httpOperation.requestBody.content.each { Map.Entry requestBodyEntry -> + def contentType = requestBodyEntry.key + def requestBody = requestBodyEntry.value + + def info = new SchemaInfo (requestBody.schema, getInlineTypeName (path)) + info.resolver = resolver + + DataType dataType = dataTypeConverter.convert (info, target.models) + + def body = new RequestBody( + contentType: contentType, + requestBodyType: dataType, + required: required) + + ep.requestBodies.add (body) + } + } + httpOperation.responses.each { Map.Entry responseEntry -> def httpStatus = responseEntry.key def httpResponse = responseEntry.value @@ -136,6 +157,10 @@ class ApiConverter { } } + private String getInlineTypeName (String path) { + StringUtil.toCamelCase (path.substring (1)) + 'RequestBody' + } + private String getInlineResponseName (String path, String httpStatus) { StringUtil.toCamelCase (path.substring (1)) + 'Response' + httpStatus } @@ -189,4 +214,5 @@ class ApiConverter { private boolean hasTags (op) { op.tags && !op.tags.empty } + } diff --git a/src/main/groovy/com/github/hauner/openapi/spring/model/Endpoint.groovy b/src/main/groovy/com/github/hauner/openapi/spring/model/Endpoint.groovy index 7443fa6a..8a283e80 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/model/Endpoint.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/model/Endpoint.groovy @@ -27,8 +27,13 @@ class Endpoint { String path HttpMethod method - List responses = [] List parameters = [] + List requestBodies = [] + List responses = [] + + RequestBody getRequestBody () { + requestBodies.first () + } Response getResponse () { responses.first () diff --git a/src/main/groovy/com/github/hauner/openapi/spring/model/RequestBody.groovy b/src/main/groovy/com/github/hauner/openapi/spring/model/RequestBody.groovy new file mode 100644 index 00000000..5d68e787 --- /dev/null +++ b/src/main/groovy/com/github/hauner/openapi/spring/model/RequestBody.groovy @@ -0,0 +1,48 @@ +/* + * Copyright 2019 the original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.hauner.openapi.spring.model + +import com.github.hauner.openapi.spring.model.datatypes.DataType + +/** + * Endpoint request body properties. + * + * @author Martin Hauner + */ +class RequestBody { + + String contentType + DataType requestBodyType + boolean required + + Set getImports () { + requestBodyType.imports + } + + String getAnnotationName () { + "RequestBody" + } + + String getAnnotationWithPackage () { + "org.springframework.web.bind.annotation.${annotationName}" + } + + String getAnnotation () { + "@${annotationName}" + } + +} diff --git a/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterRequestBodySpec.groovy b/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterRequestBodySpec.groovy new file mode 100644 index 00000000..f8fe9330 --- /dev/null +++ b/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterRequestBodySpec.groovy @@ -0,0 +1,66 @@ +/* + * Copyright 2019 the original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.hauner.openapi.spring.converter + +import spock.lang.Specification + +import static com.github.hauner.openapi.spring.support.OpenApiParser.parse + +class ApiConverterRequestBodySpec extends Specification { + + void "converts request body parameter"() { + def openApi = parse ( +"""\ +openapi: 3.0.2 +info: + title: test request body parameter + version: 1.0.0 + +paths: + /endpoint: + + get: + tags: + - endpoint + requestBody: + content: + application/json: + schema: + type: object + properties: + foo: + type: string + responses: + '204': + description: empty +""") + + when: + def api = new ApiConverter ().convert (openApi) + + then: + def itf = api.interfaces.first () + def ep = itf.endpoints.first () + def body = ep.requestBodies.first () + body.contentType == 'application/json' + body.requestBodyType.type == 'EndpointRequestBody' + !body.required + body.annotation == '@RequestBody' + body.annotationWithPackage == 'org.springframework.web.bind.annotation.RequestBody' + } + +} From 4d8f9121ce37fa80b5bf6bbd082c354bbb6b9ace Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 15:14:54 +0100 Subject: [PATCH 6/9] formatting --- .../openapi/spring/converter/ApiConverterParameterSpec.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterParameterSpec.groovy b/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterParameterSpec.groovy index f49b7248..f7636cd7 100644 --- a/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterParameterSpec.groovy +++ b/src/test/groovy/com/github/hauner/openapi/spring/converter/ApiConverterParameterSpec.groovy @@ -217,4 +217,5 @@ paths: e.name == 'foo' e.type == 'unknown' } + } From 9f8e2f6840fb3e230cb3995723a882f47f65984b Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 18:02:59 +0100 Subject: [PATCH 7/9] handle request body --- .../openapi/spring/writer/MethodWriter.groovy | 23 ++++++++++ .../spring/writer/MethodWriterSpec.groovy | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/main/groovy/com/github/hauner/openapi/spring/writer/MethodWriter.groovy b/src/main/groovy/com/github/hauner/openapi/spring/writer/MethodWriter.groovy index 73782e78..0f49b077 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/writer/MethodWriter.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/writer/MethodWriter.groovy @@ -17,6 +17,7 @@ package com.github.hauner.openapi.spring.writer import com.github.hauner.openapi.spring.model.Endpoint +import com.github.hauner.openapi.spring.model.RequestBody import com.github.hauner.openapi.spring.model.parameters.Parameter import com.github.hauner.openapi.support.Identifier @@ -39,6 +40,11 @@ class MethodWriter { mapping += "(" mapping += 'path = ' + quote(endpoint.path) + if (!endpoint.requestBodies.empty) { + mapping += ", " + mapping += 'consumes = {' + quote(endpoint.requestBody.contentType) + '}' + } + if (!endpoint.response.empty) { mapping += ", " mapping += 'produces = {' + quote(endpoint.response.contentType) + '}' @@ -68,6 +74,17 @@ class MethodWriter { param } + private String createRequestBodyAnnotation (RequestBody requestBody) { + String param = "${requestBody.annotation}" + + // required is default, so add required only if the parameter is not required + if (!requestBody.required) { + param += '(required = false)' + } + + param + } + private String createMethodName (Endpoint endpoint) { def tokens = endpoint.path.tokenize ('/') tokens = tokens.collect { Identifier.fromJson (it).capitalize () } @@ -86,6 +103,12 @@ class MethodWriter { } + if (!endpoint.requestBodies.empty) { + def body = endpoint.requestBody + def param = "${createRequestBodyAnnotation(body)} ${body.requestBodyType.name} body" + ps.add (param) + } + ps.join (', ') } diff --git a/src/test/groovy/com/github/hauner/openapi/spring/writer/MethodWriterSpec.groovy b/src/test/groovy/com/github/hauner/openapi/spring/writer/MethodWriterSpec.groovy index 8d8599e7..1a65bb65 100644 --- a/src/test/groovy/com/github/hauner/openapi/spring/writer/MethodWriterSpec.groovy +++ b/src/test/groovy/com/github/hauner/openapi/spring/writer/MethodWriterSpec.groovy @@ -18,6 +18,7 @@ package com.github.hauner.openapi.spring.writer import com.github.hauner.openapi.spring.model.Endpoint import com.github.hauner.openapi.spring.model.HttpMethod +import com.github.hauner.openapi.spring.model.RequestBody import com.github.hauner.openapi.spring.model.Response import com.github.hauner.openapi.spring.model.datatypes.BooleanDataType import com.github.hauner.openapi.spring.model.datatypes.CollectionDataType @@ -322,4 +323,47 @@ class MethodWriterSpec extends Specification { """ } + void "writes required request body parameter" () { + def endpoint = new Endpoint (path: '/foo', method: HttpMethod.POST, responses: [ + new Response (contentType: 'application/json', responseType: new NoneDataType()) + ], requestBodies: [ + new RequestBody( + contentType: 'application/json', + requestBodyType: new ObjectDataType (type: 'FooRequestBody', + properties: ['foo': new StringDataType ()] as LinkedHashMap), + required: true) + ]) + + when: + writer.write (target, endpoint) + + then: + target.toString () == """\ + @PostMapping(path = "${endpoint.path}", consumes = {"application/json"}) + ResponseEntity postFoo(@RequestBody FooRequestBody body); +""" + } + + void "writes optional request body parameter" () { + def endpoint = new Endpoint (path: '/foo', method: HttpMethod.POST, responses: [ + new Response (contentType: 'application/json', responseType: new NoneDataType()) + ], requestBodies: [ + new RequestBody( + contentType: 'application/json', + requestBodyType: new ObjectDataType ( + type: 'FooRequestBody', + properties: ['foo': new StringDataType ()] as LinkedHashMap), + required: false) + ]) + + when: + writer.write (target, endpoint) + + then: + target.toString () == """\ + @PostMapping(path = "${endpoint.path}", consumes = {"application/json"}) + ResponseEntity postFoo(@RequestBody(required = false) FooRequestBody body); +""" + } + } From 8ee91525cefa77fec43e4f0c988a2bf824c10e88 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 18:03:20 +0100 Subject: [PATCH 8/9] fixed test case --- src/testInt/resources/params-request-body/openapi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/testInt/resources/params-request-body/openapi.yaml b/src/testInt/resources/params-request-body/openapi.yaml index 8675d464..10d94a02 100644 --- a/src/testInt/resources/params-request-body/openapi.yaml +++ b/src/testInt/resources/params-request-body/openapi.yaml @@ -11,6 +11,7 @@ paths: application/json: schema: $ref: '#/components/schemas/Book' + required: true responses: '201': description: created book From 994fcbcdab1b4a84d0f300de1e04abc294a0f0a0 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sat, 23 Nov 2019 18:04:25 +0100 Subject: [PATCH 9/9] no longer pending --- .../github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy index 8e25d6c6..6f5b64f9 100644 --- a/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy +++ b/src/testInt/groovy/com/github/hauner/openapi/generatr/GeneratrEndToEndTest.groovy @@ -36,7 +36,8 @@ class GeneratrEndToEndTest extends GeneratrTestBase { new TestSet(name: 'response-complex-data-types'), new TestSet(name: 'response-array-data-type-mapping'), new TestSet(name: 'params-simple-data-types'), - new TestSet(name: 'params-path-simple-data-types') + new TestSet(name: 'params-path-simple-data-types'), + new TestSet(name: 'params-request-body') ] }