Skip to content

Commit 10362dd

Browse files
authored
Merge pull request #86 from hauner/#84
resolves #84
2 parents eb8be6f + 35dc4d1 commit 10362dd

File tree

9 files changed

+328
-7
lines changed

9 files changed

+328
-7
lines changed

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

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import com.github.hauner.openapi.spring.model.datatypes.IntegerDataType
4242
import com.github.hauner.openapi.spring.model.datatypes.LongDataType
4343
import com.github.hauner.openapi.spring.model.datatypes.NoneDataType
4444
import com.github.hauner.openapi.spring.model.datatypes.OffsetDateTimeDataType
45+
import com.github.hauner.openapi.spring.model.datatypes.LazyDataType
4546
import com.github.hauner.openapi.spring.model.datatypes.SetDataType
4647
import com.github.hauner.openapi.spring.model.datatypes.StringDataType
4748
import com.github.hauner.openapi.spring.model.datatypes.StringEnumDataType
@@ -55,9 +56,12 @@ import com.github.hauner.openapi.spring.model.datatypes.StringEnumDataType
5556
class DataTypeConverter {
5657

5758
private ApiOptions options
59+
private List<SchemaInfo> current
60+
5861

5962
DataTypeConverter(ApiOptions options) {
6063
this.options = options
64+
this.current = []
6165
}
6266

6367
DataType none() {
@@ -74,19 +78,28 @@ class DataTypeConverter {
7478
* @return the resulting java data type
7579
*/
7680
DataType convert (SchemaInfo dataTypeInfo, DataTypes dataTypes) {
81+
if (isLoop (dataTypeInfo)) {
82+
return new LazyDataType (info: dataTypeInfo, dataTypes: dataTypes)
83+
}
84+
85+
push (dataTypeInfo)
7786

87+
DataType result
7888
if (dataTypeInfo.isRefObject ()) {
79-
createRefDataType(dataTypeInfo, dataTypes)
89+
result = createRefDataType(dataTypeInfo, dataTypes)
8090

8191
} else if (dataTypeInfo.isArray ()) {
82-
createArrayDataType (dataTypeInfo, dataTypes)
92+
result = createArrayDataType (dataTypeInfo, dataTypes)
8393

8494
} else if (dataTypeInfo.isObject ()) {
85-
createObjectDataType (dataTypeInfo, dataTypes)
95+
result = createObjectDataType (dataTypeInfo, dataTypes)
8696

8797
} else {
88-
createSimpleDataType (dataTypeInfo, dataTypes)
98+
result = createSimpleDataType (dataTypeInfo, dataTypes)
8999
}
100+
101+
pop ()
102+
result
90103
}
91104

92105
private DataType createArrayDataType (SchemaInfo schemaInfo, DataTypes dataTypes) {
@@ -250,7 +263,7 @@ class DataTypeConverter {
250263
enumType
251264
}
252265

253-
TargetType getMappedDataType (SchemaType schemaType) {
266+
private TargetType getMappedDataType (SchemaType schemaType) {
254267
// check endpoint mappings
255268
List<Mapping> endpointMatches = schemaType.matchEndpointMapping (options.typeMappings)
256269
if (!endpointMatches.empty) {
@@ -293,4 +306,40 @@ class DataTypeConverter {
293306
return match.targetType
294307
}
295308

309+
/**
310+
* push the current schema info.
311+
*
312+
* Pushes the given {@code info} onto the in-progress data type stack. It is used to detect
313+
* $ref loops.
314+
*
315+
* @param info the schema info that is currently processed
316+
*/
317+
private void push (SchemaInfo info) {
318+
current.push (info)
319+
}
320+
321+
/**
322+
* pop the current schema info.
323+
*
324+
*/
325+
private void pop () {
326+
current.pop ()
327+
}
328+
329+
/**
330+
* detect $ref loop.
331+
*
332+
* returns true if the given {@code info} is currently processed, false otherwise. True indicates
333+
* a $ref loop.
334+
*
335+
* @param info the schema info that is currently processed
336+
* @return
337+
*/
338+
private boolean isLoop (SchemaInfo info) {
339+
def found = current.find {
340+
it.name == info.name
341+
}
342+
found != null
343+
}
344+
296345
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2019 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+
import com.github.hauner.openapi.spring.converter.schema.SchemaInfo
20+
import com.github.hauner.openapi.spring.model.DataTypes
21+
22+
/**
23+
* OpenAPI $ref type that is lazily evaluated. It is used to break loops in the schema definitions.
24+
*
25+
* @autor Martin Hauner
26+
*/
27+
class LazyDataType implements DataType {
28+
29+
private DataTypes dataTypes
30+
private SchemaInfo info
31+
32+
@Override
33+
String getName () {
34+
dataType.name
35+
}
36+
37+
@Override
38+
String getPackageName () {
39+
dataType.packageName
40+
}
41+
42+
@Override
43+
Set<String> getImports () {
44+
dataType.imports
45+
}
46+
47+
@Override
48+
Set<String> getReferencedImports () {
49+
dataType.referencedImports
50+
}
51+
52+
private DataType getDataType () {
53+
dataTypes.find (info.name)
54+
}
55+
56+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.model.datatypes.LazyDataType
20+
import com.github.hauner.openapi.spring.model.datatypes.ObjectDataType
21+
import spock.lang.Specification
22+
23+
import static com.github.hauner.openapi.spring.support.OpenApiParser.parse
24+
25+
26+
class DataTypeConverterLoopSpec extends Specification {
27+
28+
void "handles \$ref loops"() {
29+
def openApi = parse (
30+
"""\
31+
openapi: 3.0.2
32+
info:
33+
title: test \$ref loop
34+
version: 1.0.0
35+
36+
paths:
37+
38+
/self-reference:
39+
get:
40+
responses:
41+
'200':
42+
description: none
43+
content:
44+
application/json:
45+
schema:
46+
\$ref: '#/components/schemas/Self'
47+
48+
components:
49+
schemas:
50+
51+
Self:
52+
type: object
53+
properties:
54+
self:
55+
\$ref: '#/components/schemas/Self'
56+
""")
57+
58+
when:
59+
def api = new ApiConverter (new ApiOptions()).convert (openApi)
60+
61+
then:
62+
def itf = api.interfaces.first ()
63+
def ep = itf.endpoints.first ()
64+
def rp = ep.getFirstResponse ('200')
65+
def rt = rp.responseType
66+
def sf = rt.objectProperties.self
67+
rt instanceof ObjectDataType
68+
sf instanceof LazyDataType
69+
sf.name == 'Self'
70+
sf.packageName == 'generatr.model'
71+
sf.imports == ['generatr.model.Self'] as Set
72+
sf.referencedImports == ['generatr.model.Self'] as Set
73+
}
74+
75+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import org.junit.Ignore
2020
import org.junit.runner.RunWith
2121
import org.junit.runners.Parameterized
2222

23-
@Ignore
23+
//@Ignore
2424
@RunWith(Parameterized)
2525
class ProcessorPendingTest extends ProcessorTestBase {
2626

2727
@Parameterized.Parameters(name = "{0}")
2828
static Collection<TestSet> sources () {
2929
return [
30-
new TestSet(name: 'response-content-multiple')
30+
new TestSet(name: 'ref-loop')
3131
]
3232
}
3333

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
3+
* DO NOT EDIT.
4+
*/
5+
6+
package generated.api;
7+
8+
import generated.model.Foo;
9+
import generated.model.Self;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
13+
public interface Api {
14+
15+
@GetMapping(
16+
path = "/self-reference",
17+
produces = {"application/json"})
18+
ResponseEntity<Self> getSelfReference();
19+
20+
@GetMapping(
21+
path = "/nested-self-reference",
22+
produces = {"application/json"})
23+
ResponseEntity<Foo> getNestedSelfReference();
24+
25+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
3+
* DO NOT EDIT.
4+
*/
5+
6+
package generated.model;
7+
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
10+
public class Bar {
11+
12+
@JsonProperty("parent")
13+
private Foo parent;
14+
15+
public Foo getParent() {
16+
return parent;
17+
}
18+
19+
public void setParent(Foo parent) {
20+
this.parent = parent;
21+
}
22+
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
3+
* DO NOT EDIT.
4+
*/
5+
6+
package generated.model;
7+
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
10+
public class Foo {
11+
12+
@JsonProperty("child")
13+
private Bar child;
14+
15+
public Bar getChild() {
16+
return child;
17+
}
18+
19+
public void setChild(Bar child) {
20+
this.child = child;
21+
}
22+
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
3+
* DO NOT EDIT.
4+
*/
5+
6+
package generated.model;
7+
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
10+
public class Self {
11+
12+
@JsonProperty("self")
13+
private Self self;
14+
15+
public Self getSelf() {
16+
return self;
17+
}
18+
19+
public void setSelf(Self self) {
20+
this.self = self;
21+
}
22+
23+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
openapi: 3.0.2
2+
info:
3+
title: test $ref loop
4+
version: 1.0.0
5+
6+
paths:
7+
8+
/self-reference:
9+
get:
10+
responses:
11+
'200':
12+
description: none
13+
content:
14+
application/json:
15+
schema:
16+
$ref: '#/components/schemas/Self'
17+
18+
/nested-self-reference:
19+
get:
20+
responses:
21+
'200':
22+
description: none
23+
content:
24+
application/json:
25+
schema:
26+
$ref: '#/components/schemas/Foo'
27+
28+
components:
29+
schemas:
30+
31+
Self:
32+
type: object
33+
properties:
34+
self:
35+
$ref: '#/components/schemas/Self'
36+
37+
Foo:
38+
type: object
39+
properties:
40+
child:
41+
$ref: '#/components/schemas/Bar'
42+
43+
Bar:
44+
type: object
45+
properties:
46+
parent:
47+
$ref: '#/components/schemas/Foo'

0 commit comments

Comments
 (0)