Skip to content

Commit 804d2a4

Browse files
authored
Merge pull request #59 from hauner/#56
resolves #56, #57
2 parents 9c3d539 + 87c34be commit 804d2a4

File tree

9 files changed

+377
-60
lines changed

9 files changed

+377
-60
lines changed

src/main/groovy/com/github/hauner/openapi/spring/converter/ApiConverter.groovy

Lines changed: 92 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original authors
2+
* Copyright 2019-2020 the original authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,10 +21,13 @@ import com.github.hauner.openapi.spring.converter.schema.RefResolver
2121
import com.github.hauner.openapi.spring.converter.schema.ResponseSchemaInfo
2222
import com.github.hauner.openapi.spring.converter.schema.SchemaInfo
2323
import com.github.hauner.openapi.spring.model.Api
24+
import com.github.hauner.openapi.spring.model.DataTypes
2425
import com.github.hauner.openapi.spring.model.Endpoint
25-
import com.github.hauner.openapi.spring.model.RequestBody
26+
import com.github.hauner.openapi.spring.model.RequestBody as ModelRequestBody
27+
import com.github.hauner.openapi.spring.model.datatypes.ObjectDataType
2628
import com.github.hauner.openapi.spring.model.parameters.CookieParameter
2729
import com.github.hauner.openapi.spring.model.parameters.HeaderParameter
30+
import com.github.hauner.openapi.spring.model.parameters.MultipartParameter
2831
import com.github.hauner.openapi.spring.model.parameters.Parameter as ModelParameter
2932
import com.github.hauner.openapi.spring.model.parameters.PathParameter
3033
import com.github.hauner.openapi.spring.model.parameters.QueryParameter
@@ -36,7 +39,9 @@ import io.swagger.v3.oas.models.OpenAPI
3639
import io.swagger.v3.oas.models.PathItem
3740
import io.swagger.v3.oas.models.media.MediaType
3841
import io.swagger.v3.oas.models.parameters.Parameter
42+
import io.swagger.v3.oas.models.parameters.RequestBody
3943
import io.swagger.v3.oas.models.responses.ApiResponse
44+
import io.swagger.v3.oas.models.responses.ApiResponses
4045

4146
/**
4247
* Converts the open api model to a new model that is better suited for generating source files
@@ -46,6 +51,7 @@ import io.swagger.v3.oas.models.responses.ApiResponse
4651
*/
4752
@Slf4j
4853
class ApiConverter {
54+
public static final String MULTIPART = "multipart/form-data"
4955

5056
private DataTypeConverter dataTypeConverter
5157
private ApiOptions options
@@ -76,7 +82,6 @@ class ApiConverter {
7682
}
7783

7884
private Map<String, PathItem> addEndpointsToInterfaces (OpenAPI api, Api target) {
79-
def resolver = new RefResolver (api.components)
8085

8186
api.paths.each { Map.Entry<String, PathItem> pathEntry ->
8287
String path = pathEntry.key
@@ -89,47 +94,11 @@ class ApiConverter {
8994
Endpoint ep = new Endpoint (path: path, method: httpOperation.httpMethod)
9095

9196
try {
92-
httpOperation.parameters.each { Parameter parameter ->
93-
ep.parameters.addAll (createParameter(path, parameter, target, resolver))
94-
}
95-
96-
if (httpOperation.requestBody != null) {
97-
def required = httpOperation.requestBody.required != null ?: false
98-
httpOperation.requestBody.content.each { Map.Entry<String, MediaType> requestBodyEntry ->
99-
def contentType = requestBodyEntry.key
100-
def requestBody = requestBodyEntry.value
101-
102-
def info = new SchemaInfo (path, requestBody.schema, getInlineTypeName (path))
103-
info.resolver = resolver
104-
105-
DataType dataType = dataTypeConverter.convert (info, target.models)
106-
107-
def body = new RequestBody(
108-
contentType: contentType,
109-
requestBodyType: dataType,
110-
required: required)
111-
112-
ep.requestBodies.add (body)
113-
}
114-
}
115-
116-
httpOperation.responses.each { Map.Entry<String, ApiResponse> responseEntry ->
117-
def httpStatus = responseEntry.key
118-
def httpResponse = responseEntry.value
119-
120-
if (!httpResponse.content) {
121-
ep.responses.add (createEmptyResponse ())
122-
} else {
123-
List<Response> responses = createResponses (
124-
path,
125-
httpResponse,
126-
getInlineResponseName (path, httpStatus),
127-
target,
128-
resolver)
129-
130-
ep.responses.addAll (responses)
131-
}
132-
}
97+
def resolver = new RefResolver (api.components)
98+
99+
collectParameters (httpOperation.parameters, ep, target.models, resolver)
100+
collectRequestBody (httpOperation.requestBody, ep, target.models, resolver)
101+
collectResponses (httpOperation.responses, ep, target.models, resolver)
133102

134103
itf.endpoints.add (ep)
135104

@@ -140,11 +109,59 @@ class ApiConverter {
140109
}
141110
}
142111

143-
private ModelParameter createParameter (String path, Parameter parameter, Api target, resolver) {
112+
private void collectParameters (List<Parameter> parameters, Endpoint ep, DataTypes dataTypes, RefResolver resolver) {
113+
parameters.each { Parameter parameter ->
114+
ep.parameters.add (createParameter (ep.path, parameter, dataTypes, resolver))
115+
}
116+
}
117+
118+
private void collectRequestBody (RequestBody requestBody, Endpoint ep, DataTypes dataTypes, RefResolver resolver) {
119+
if (requestBody == null) {
120+
return
121+
}
122+
123+
def required = requestBody.required != null ?: false
124+
125+
requestBody.content.each { Map.Entry<String, MediaType> requestBodyEntry ->
126+
def contentType = requestBodyEntry.key
127+
def reqBody = requestBodyEntry.value
128+
129+
def info = new SchemaInfo (ep.path, reqBody.schema, getInlineTypeName (ep.path))
130+
info.resolver = resolver
131+
132+
if (contentType == MULTIPART) {
133+
ep.parameters.addAll (createMultipartParameter (info, required))
134+
} else {
135+
ep.requestBodies.add (createRequestBody (contentType, info, required, dataTypes))
136+
}
137+
}
138+
}
139+
140+
private collectResponses (ApiResponses responses, Endpoint ep, DataTypes dataTypes, RefResolver resolver) {
141+
responses.each { Map.Entry<String, ApiResponse> responseEntry ->
142+
def httpStatus = responseEntry.key
143+
def httpResponse = responseEntry.value
144+
145+
if (!httpResponse.content) {
146+
ep.responses.add (createEmptyResponse ())
147+
} else {
148+
List<Response> results = createResponses (
149+
ep.path,
150+
httpResponse,
151+
getInlineResponseName (ep.path, httpStatus),
152+
dataTypes,
153+
resolver)
154+
155+
ep.responses.addAll (results)
156+
}
157+
}
158+
}
159+
160+
private ModelParameter createParameter (String path, Parameter parameter, DataTypes dataTypes, resolver) {
144161
def info = new ParameterSchemaInfo (path, parameter.schema, parameter.name)
145162
info.resolver = resolver
146163

147-
DataType dataType = dataTypeConverter.convert (info, target.models)
164+
DataType dataType = dataTypeConverter.convert (info, dataTypes)
148165

149166
switch (parameter.in) {
150167
case 'query':
@@ -161,19 +178,27 @@ class ApiConverter {
161178
}
162179
}
163180

164-
private String getInlineTypeName (String path) {
165-
Identifier.toClass (path) + 'RequestBody'
166-
}
181+
private ModelRequestBody createRequestBody (String contentType, SchemaInfo info, boolean required, DataTypes dataTypes) {
182+
DataType dataType = dataTypeConverter.convert (info, dataTypes)
167183

168-
private String getInlineResponseName (String path, String httpStatus) {
169-
Identifier.toClass (path) + 'Response' + httpStatus
184+
new ModelRequestBody(
185+
contentType: contentType,
186+
requestBodyType: dataType,
187+
required: required)
170188
}
171189

172-
private Response createEmptyResponse () {
173-
new Response (responseType: dataTypeConverter.none ())
190+
private Collection<ModelParameter> createMultipartParameter (SchemaInfo info, boolean required) {
191+
DataType dataType = dataTypeConverter.convert (info, new DataTypes())
192+
if (! (dataType instanceof ObjectDataType)) {
193+
throw new MultipartResponseBodyException(info.path)
194+
}
195+
196+
dataType.getObjectProperties ().collect {
197+
new MultipartParameter (name: it.key, required: required, dataType: it.value)
198+
}
174199
}
175200

176-
private List<Response> createResponses (String path, ApiResponse apiResponse, String inlineName, Api target, RefResolver resolver) {
201+
private List<Response> createResponses (String path, ApiResponse apiResponse, String inlineName, DataTypes dataTypes, RefResolver resolver) {
177202
def responses = []
178203

179204
apiResponse.content.each { Map.Entry<String, MediaType> contentEntry ->
@@ -188,9 +213,7 @@ class ApiConverter {
188213
inlineName)
189214
info.resolver = resolver
190215

191-
DataType dataType = dataTypeConverter.convert (
192-
info,
193-
target.models)
216+
DataType dataType = dataTypeConverter.convert (info, dataTypes)
194217

195218
def response = new Response (
196219
contentType: contentType,
@@ -202,6 +225,18 @@ class ApiConverter {
202225
responses
203226
}
204227

228+
private String getInlineTypeName (String path) {
229+
Identifier.toClass (path) + 'RequestBody'
230+
}
231+
232+
private String getInlineResponseName (String path, String httpStatus) {
233+
Identifier.toClass (path) + 'Response' + httpStatus
234+
}
235+
236+
private Response createEmptyResponse () {
237+
new Response (responseType: dataTypeConverter.none ())
238+
}
239+
205240
private void collectInterfaces (OpenAPI api, Api target) {
206241
target.interfaces = new InterfaceCollector (options)
207242
.collect (api.paths)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2020 the original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.github.hauner.openapi.spring.converter
18+
19+
/**
20+
* thrown when the ApiConverter hits a multipart/form-data response body where
21+
* the schema is not an object.
22+
*
23+
* @author Martin Hauner
24+
*/
25+
class MultipartResponseBodyException extends RuntimeException {
26+
27+
String path
28+
29+
MultipartResponseBodyException(String path) {
30+
super()
31+
this.path = path
32+
}
33+
34+
@Override
35+
String getMessage () {
36+
"the schema of the multipart response body of ${path} should be an object!"
37+
}
38+
39+
}

src/main/groovy/com/github/hauner/openapi/spring/model/datatypes/ObjectDataType.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original authors
2+
* Copyright 2019-2020 the original authors
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -61,4 +61,8 @@ class ObjectDataType implements DataType {
6161
properties.get (name)
6262
}
6363

64+
Map<String, DataType> getObjectProperties () {
65+
properties
66+
}
67+
6468
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2020 the original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.github.hauner.openapi.spring.model.parameters
18+
19+
/**
20+
* OpenAPI multipart request body is passed as {@code @RequestParam}
21+
*
22+
* @author Martin Hauner
23+
*/
24+
class MultipartParameter extends Parameter {
25+
26+
String getAnnotationName () {
27+
"RequestParam"
28+
}
29+
30+
boolean withAnnotation () {
31+
true
32+
}
33+
34+
boolean withParameters () {
35+
true
36+
}
37+
38+
}

0 commit comments

Comments
 (0)