Skip to content

Commit d68d039

Browse files
committed
'single' wrapper mapping
1 parent 3bd02fd commit d68d039

10 files changed

Lines changed: 392 additions & 6 deletions

File tree

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class ApiConverter {
6161

6262
private DataTypeConverter dataTypeConverter
6363
private ResultDataTypeWrapper dataTypeWrapper
64+
private SingleDataTypeWrapper singleDataTypeWrapper
6465
private MappingFinder mappingFinder
6566
private ApiOptions options
6667

@@ -73,6 +74,7 @@ class ApiConverter {
7374

7475
dataTypeConverter = new DataTypeConverter(this.options)
7576
dataTypeWrapper = new ResultDataTypeWrapper(this.options)
77+
singleDataTypeWrapper = new SingleDataTypeWrapper(this.options)
7678
mappingFinder = new MappingFinder (typeMappings: this.options.typeMappings)
7779
}
7880

@@ -233,10 +235,11 @@ class ApiConverter {
233235

234236
private ModelRequestBody createRequestBody (String contentType, SchemaInfo info, boolean required, DataTypes dataTypes) {
235237
DataType dataType = dataTypeConverter.convert (info, dataTypes)
238+
DataType singleDataType = singleDataTypeWrapper.wrap (dataType, info)
236239

237240
new ModelRequestBody(
238241
contentType: contentType,
239-
requestBodyType: dataType,
242+
requestBodyType: singleDataType,
240243
required: required)
241244
}
242245

@@ -256,7 +259,8 @@ class ApiConverter {
256259
def info = new SchemaInfo (path: path)
257260

258261
DataType dataType = new NoneDataType()
259-
DataType resultDataType = dataTypeWrapper.wrap (dataType, info)
262+
DataType singleDataType = singleDataTypeWrapper.wrap (dataType, info)
263+
DataType resultDataType = dataTypeWrapper.wrap (singleDataType, info)
260264

261265
def resp = new ModelResponse (
262266
responseType: resultDataType)
@@ -278,7 +282,8 @@ class ApiConverter {
278282
resolver: resolver)
279283

280284
DataType dataType = dataTypeConverter.convert (info, dataTypes)
281-
DataType resultDataType = dataTypeWrapper.wrap (dataType, info)
285+
DataType singleDataType = singleDataTypeWrapper.wrap (dataType, info)
286+
DataType resultDataType = dataTypeWrapper.wrap (singleDataType, info)
282287

283288
def resp = new ModelResponse (
284289
contentType: contentType,

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@ class MappingFinder {
121121

122122
}
123123

124+
class SingleTypeMatcher extends BaseVisitor {
125+
126+
@Override
127+
boolean match (TypeMapping mapping) {
128+
mapping.sourceTypeName == 'single'
129+
}
130+
131+
}
132+
124133
class AddParameterMatcher extends BaseVisitor {
125134

126135
@Override
@@ -255,6 +264,44 @@ class MappingFinder {
255264

256265
[]
257266
}
267+
268+
/**
269+
* find endpoint single type mapping.
270+
*
271+
* @param info schema info of the OpenAPI schema.
272+
* @return the single type mapping.
273+
*/
274+
List<Mapping> findEndpointSingleMapping (SchemaInfo info) {
275+
List<Mapping> ep = filterMappings (new EndpointMatcher (schema: info), typeMappings)
276+
277+
def matcher = new SingleTypeMatcher (schema: info)
278+
def result = ep.findAll {
279+
it.matches (matcher)
280+
}
281+
282+
if (!result.empty) {
283+
return result
284+
}
285+
286+
[]
287+
}
288+
289+
/**
290+
* find (global) single type mapping.
291+
*
292+
* @param info schema info of the OpenAPI schema.
293+
* @return the single type mapping.
294+
*/
295+
List<Mapping> findSingleMapping (SchemaInfo info) {
296+
List<Mapping> ep = filterMappings (new SingleTypeMatcher (schema: info), typeMappings)
297+
298+
299+
if (!ep.empty) {
300+
return ep
301+
}
302+
303+
[]
304+
}
258305

259306
/**
260307
* check if the given endpoint should b excluded.

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ class SchemaInfo implements MappingSchema {
239239
}
240240

241241
boolean isArray () {
242-
schema.type == 'array'
242+
schema?.type == 'array'
243243
}
244244

245245
boolean isObject () {
@@ -254,6 +254,10 @@ class SchemaInfo implements MappingSchema {
254254
schema.ref != null
255255
}
256256

257+
boolean isEmpty () {
258+
!schema
259+
}
260+
257261
boolean isEnum () {
258262
schema.enum != null
259263
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
import com.github.hauner.openapi.spring.converter.mapping.AmbiguousTypeMappingException
20+
import com.github.hauner.openapi.spring.converter.mapping.Mapping
21+
import com.github.hauner.openapi.spring.converter.mapping.TargetType
22+
import com.github.hauner.openapi.spring.converter.mapping.TargetTypeMapping
23+
import com.github.hauner.openapi.spring.model.datatypes.DataType
24+
import com.github.hauner.openapi.spring.model.datatypes.NoneDataType
25+
import com.github.hauner.openapi.spring.model.datatypes.SingleDataType
26+
27+
/**
28+
* wraps the data type with the 'singe' data mapping.
29+
*
30+
* Used to wrap Responses or RequestBody's with {@code Mono<>} or similar types.
31+
*
32+
* @author Martin Hauner
33+
*/
34+
class SingleDataTypeWrapper {
35+
36+
private ApiOptions options
37+
private MappingFinder finder
38+
39+
SingleDataTypeWrapper (ApiOptions options) {
40+
this.options = options
41+
this.finder = new MappingFinder(typeMappings: options.typeMappings)
42+
}
43+
44+
/**
45+
* wraps a (converted) on-array data type with the configured single data type like
46+
* {@code Mono<>} ec.
47+
*
48+
* If the configuration for the result type is 'plain' or not defined the source data type
49+
* is not wrapped.
50+
*
51+
* @param dataType the data type to wrap
52+
* @param schemaInfo the open api type with context information
53+
* @return the resulting java data type
54+
*/
55+
DataType wrap (DataType dataType, SchemaInfo schemaInfo) {
56+
def targetType = getSingleResultDataType (schemaInfo)
57+
if (!targetType || schemaInfo.isArray ()) {
58+
return dataType
59+
}
60+
61+
if (targetType.typeName == 'plain') {
62+
return dataType
63+
}
64+
65+
def wrappedType = new SingleDataType (
66+
type: targetType.name,
67+
pkg: targetType.pkg,
68+
dataType: checkNone (dataType)
69+
)
70+
71+
return wrappedType
72+
}
73+
74+
private TargetType getSingleResultDataType (SchemaInfo info) {
75+
// check endpoint single mapping
76+
List<Mapping> endpointMatches = finder.findEndpointSingleMapping (info)
77+
78+
if (!endpointMatches.empty) {
79+
80+
if (endpointMatches.size () != 1) {
81+
throw new AmbiguousTypeMappingException (endpointMatches)
82+
}
83+
84+
TargetType target = (endpointMatches.first() as TargetTypeMapping).targetType
85+
if (target) {
86+
return target
87+
}
88+
}
89+
90+
// find global single mapping
91+
List<Mapping> typeMatches = finder.findSingleMapping (info)
92+
if (typeMatches.isEmpty ()) {
93+
return null
94+
}
95+
96+
if (typeMatches.size () != 1) {
97+
throw new AmbiguousTypeMappingException (typeMatches)
98+
}
99+
100+
def match = typeMatches.first () as TargetTypeMapping
101+
return match.targetType
102+
}
103+
104+
private DataType checkNone (DataType dataType) {
105+
if (dataType instanceof NoneDataType) {
106+
return dataType.wrappedInResult ()
107+
}
108+
109+
dataType
110+
}
111+
112+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.datatypes
18+
19+
/**
20+
* Single data type wrapper. Assumes a single generic parameter.
21+
*
22+
* @author Martin Hauner
23+
*/
24+
class SingleDataType implements DataType {
25+
26+
String type
27+
String pkg = 'unknown'
28+
private DataType dataType
29+
30+
@Override
31+
String getName () {
32+
"$type<${dataType.name}>"
33+
}
34+
35+
@Override
36+
String getPackageName () {
37+
pkg
38+
}
39+
40+
@Override
41+
Set<String> getImports () {
42+
[[packageName, type].join ('.')] + dataType.imports
43+
}
44+
45+
@Override
46+
Set<String> getReferencedImports () {
47+
[]
48+
}
49+
50+
@Override
51+
DataTypeConstraints getConstraints () {
52+
null
53+
}
54+
55+
}

src/testInt/groovy/com/github/hauner/openapi/processor/ProcessorPendingTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class ProcessorPendingTest extends ProcessorTestBase {
2828
@Parameterized.Parameters(name = "{0}")
2929
static Collection<TestSet> sources () {
3030
return [
31-
new TestSet(name: 'response-result-mapping', parser: ParserType.SWAGGER),
32-
new TestSet(name: 'response-result-mapping', parser: ParserType.OPENAPI4J)
31+
new TestSet(name: 'response-single', parser: ParserType.SWAGGER),
32+
new TestSet(name: 'response-single', parser: ParserType.OPENAPI4J)
3333
]
3434
}
3535

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* This class is auto generated by https://github.com/hauner/openapi-processor-spring.
3+
* DO NOT EDIT.
4+
*/
5+
6+
package generated.api;
7+
8+
import generated.model.Book;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PathVariable;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.PutMapping;
13+
import org.springframework.web.bind.annotation.RequestBody;
14+
import reactor.core.publisher.Flux;
15+
import reactor.core.publisher.Mono;
16+
17+
public interface Api {
18+
19+
@GetMapping(
20+
path = "/books",
21+
produces = {"application/json"})
22+
Flux<Book> getBooks();
23+
24+
@PostMapping(
25+
path = "/books",
26+
consumes = {"application/json"})
27+
Mono<Void> postBooks(@RequestBody Flux<Book> body);
28+
29+
@GetMapping(
30+
path = "/books/{isbn}",
31+
produces = {"application/json"})
32+
Mono<Book> getBooksIsbn(@PathVariable(name = "isbn") String isbn);
33+
34+
@PutMapping(
35+
path = "/books/{isbn}",
36+
consumes = {"application/json"})
37+
Mono<Void> putBooksIsbn(@PathVariable(name = "isbn") String isbn, @RequestBody Mono<Book> body);
38+
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This class is auto generated by https://github.com/hauner/openapi-processor-spring.
3+
* DO NOT EDIT.
4+
*/
5+
6+
package generated.model;
7+
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
10+
public class Book {
11+
12+
@JsonProperty("isbn")
13+
private String isbn;
14+
15+
@JsonProperty("title")
16+
private String title;
17+
18+
public String getIsbn() {
19+
return isbn;
20+
}
21+
22+
public void setIsbn(String isbn) {
23+
this.isbn = isbn;
24+
}
25+
26+
public String getTitle() {
27+
return title;
28+
}
29+
30+
public void setTitle(String title) {
31+
this.title = title;
32+
}
33+
34+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
options:
2+
package-name: generated
3+
4+
map:
5+
6+
types:
7+
- from: single
8+
to: reactor.core.publisher.Mono
9+
10+
- from: array
11+
to: reactor.core.publisher.Flux

0 commit comments

Comments
 (0)