Skip to content
Merged

#24 #28

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, MediaType> 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<String, ApiResponse> responseEntry ->
def httpStatus = responseEntry.key
def httpResponse = responseEntry.value
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -189,4 +214,5 @@ class ApiConverter {
private boolean hasTags (op) {
op.tags && !op.tags.empty
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ class Endpoint {
String path
HttpMethod method

List<Response> responses = []
List<Parameter> parameters = []
List<RequestBody> requestBodies = []
List<Response> responses = []

RequestBody getRequestBody () {
requestBodies.first ()
}

Response getResponse () {
responses.first ()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> getImports () {
requestBodyType.imports
}

String getAnnotationName () {
"RequestBody"
}

String getAnnotationWithPackage () {
"org.springframework.web.bind.annotation.${annotationName}"
}

String getAnnotation () {
"@${annotationName}"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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) + '}'
Expand Down Expand Up @@ -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 () }
Expand All @@ -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 (', ')
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,5 @@ paths:
e.name == 'foo'
e.type == 'unknown'
}

}
Original file line number Diff line number Diff line change
@@ -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'
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<void> 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<void> postFoo(@RequestBody(required = false) FooRequestBody body);
"""
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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


/**
Expand All @@ -30,12 +30,14 @@ class GeneratrEndToEndTest extends GeneratrTestBase {
@Parameterized.Parameters(name = "{0}")
static Collection<TestSet> 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: 'response-array-data-type-mapping'),
new TestSet(name: 'params-simple-data-types'),
new TestSet(name: 'response-array-data-type-mapping')
new TestSet(name: 'params-path-simple-data-types'),
new TestSet(name: 'params-request-body')
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class GeneratrPendingTest extends GeneratrTestBase {
@Parameterized.Parameters(name = "{0}")
static Collection<TestSet> sources () {
return [
new TestSet(name: 'path-params-simple-data-types')
new TestSet(name: 'params-request-body')
]
}

Expand Down
17 changes: 17 additions & 0 deletions src/testInt/resources/params-request-body/generated/api/Api.java
Original file line number Diff line number Diff line change
@@ -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<Book> postBook(@RequestBody Book body);

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Loading