Skip to content

Commit 21ea2d3

Browse files
committed
Merge pull request #1163 from msimons/develop_2.0
[Develop_2.0] Properly resolve a inner model with a self/cyclic references
2 parents 42abc3d + 819bb20 commit 21ea2d3

File tree

7 files changed

+176
-119
lines changed

7 files changed

+176
-119
lines changed

modules/swagger-core/src/main/java/io/swagger/converter/ModelConverterContext.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ public interface ModelConverterContext {
1818
*/
1919
public void defineModel(String name, Model model);
2020

21+
/**
22+
* needs to be called whenever a Model is defined which can be referenced from another
23+
* Model or Property
24+
*
25+
* @param name the name of the model
26+
* @param model the Model
27+
* @param type the Type
28+
* @param prevName the (optional) previous name
29+
*/
30+
public void defineModel(String name, Model model, Type type, String prevName);
31+
2132
/**
2233
* @param type the property Class
2334
* @return a property representation of the Class. Any referenced models will be defined already.

modules/swagger-core/src/main/java/io/swagger/converter/ModelConverterContextImpl.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.swagger.models.Model;
44
import io.swagger.models.ModelImpl;
55
import io.swagger.models.properties.Property;
6+
import org.apache.commons.lang3.StringUtils;
67
import org.slf4j.Logger;
78
import org.slf4j.LoggerFactory;
89

@@ -44,10 +45,23 @@ public Iterator<ModelConverter> getConverters() {
4445

4546
@Override
4647
public void defineModel(String name, Model model) {
48+
defineModel(name,model,null,null);
49+
}
50+
51+
@Override
52+
public void defineModel(String name, Model model, Type type, String prevName) {
4753
if (LOGGER.isDebugEnabled()) {
4854
LOGGER.debug(String.format("defineModel %s %s", name, model));
4955
}
5056
modelByName.put(name, model);
57+
58+
if(StringUtils.isNotBlank(prevName)) {
59+
modelByName.remove(prevName);
60+
}
61+
62+
if(type != null) {
63+
modelByType.put(type,model);
64+
}
5165
}
5266

5367
public Map<String, Model> getDefinedModels() {

modules/swagger-core/src/main/java/io/swagger/jackson/ModelResolver.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,7 @@ public Model resolve(JavaType type, ModelConverterContext context, Iterator<Mode
179179
// We don't build models for primitive types
180180
return null;
181181
}
182-
if (type.isContainerType()) {
183-
// We treat collections as primitive types, just need to add models for values (if any)
184-
context.resolve(type.getContentType());
185-
return null;
186-
}
182+
187183
final BeanDescription beanDesc = _mapper.getSerializationConfig().introspect(type);
188184
// Couple of possibilities for defining
189185
String name = _typeName(type, beanDesc);
@@ -195,6 +191,16 @@ public Model resolve(JavaType type, ModelConverterContext context, Iterator<Mode
195191
final ModelImpl model = new ModelImpl().type(ModelImpl.OBJECT).name(name)
196192
.description(_description(beanDesc.getClassInfo()));
197193

194+
if(!type.isContainerType()) {
195+
// define the model here to support self/cyclic referencing of models
196+
context.defineModel(name, model, type, null);
197+
}
198+
199+
if (type.isContainerType()) {
200+
// We treat collections as primitive types, just need to add models for values (if any)
201+
context.resolve(type.getContentType());
202+
return null;
203+
}
198204
// if XmlRootElement annotation, construct an Xml object and attach it to the model
199205
XmlRootElement rootAnnotation = beanDesc.getClassAnnotations().get(XmlRootElement.class);
200206
if (rootAnnotation != null && !"".equals(rootAnnotation.name()) && !"##default".equals(rootAnnotation.name())) {

modules/swagger-core/src/test/scala/1_3/converter/SnakeCaseConverterTest.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ class SnakeCaseConverter extends ModelConverter {
9595
val name = (model.asInstanceOf[ModelImpl]).getName
9696
if (model.isInstanceOf[ModelImpl]) {
9797
val impl = model.asInstanceOf[ModelImpl]
98+
val prevName = impl.getName()
9899
impl.setName(toSnakeCase(impl.getName()))
100+
context.defineModel(impl.getName,impl,`type`,prevName)
99101
}
100102
return model
101103
}

modules/swagger-jaxrs/src/test/scala/ReferenceTest.scala

Lines changed: 120 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -28,135 +28,141 @@ class ReferenceTest extends FlatSpec with Matchers {
2828
val swagger = reader.read(classOf[ResourceWithReferences])
2929

3030
swagger should serializeToJson(
31-
"""{
32-
"swagger": "2.0",
33-
"tags": [
34-
{
35-
"name": "basic"
36-
}
37-
],
38-
"paths": {
39-
"/anotherModel": {
40-
"get": {
41-
"tags": [
42-
"basic"
43-
],
44-
"summary": "Get Another Model",
45-
"description": "",
46-
"operationId": "getAnotherModel",
47-
"parameters": [],
48-
"responses": {
49-
"200": {
50-
"description": "successful operation",
51-
"schema": {
52-
"$ref": "http://swagger.io/schemas.json#/Models"
53-
}
54-
}
55-
}
31+
"""{"swagger": "2.0", "tags": [
32+
{
33+
"name": "basic"
5634
}
35+
], "paths": {
36+
"/anotherModel": {
37+
"get": {
38+
"tags": ["basic"],
39+
"summary": "Get Another Model",
40+
"description": "",
41+
"operationId": "getAnotherModel",
42+
"parameters": [],
43+
"responses": {
44+
"200": {
45+
"description": "successful operation",
46+
"schema": {
47+
"$ref": "http://swagger.io/schemas.json#/Models"
48+
}
49+
}
50+
}
51+
}
5752
},
58-
"/model": {
53+
"/model": {
5954
"get": {
60-
"tags": [
61-
"basic"
62-
],
63-
"summary": "Get Model",
64-
"description": "",
65-
"operationId": "getModel",
66-
"parameters": [],
67-
"responses": {
68-
"200": {
69-
"description": "successful operation",
70-
"schema": {
71-
"$ref": "#/definitions/ModelContainingModelWithReference"
72-
}
73-
}
74-
}
75-
}
55+
"tags": ["basic"],
56+
"summary": "Get Model",
57+
"description": "",
58+
"operationId": "getModel",
59+
"parameters": [],
60+
"responses": {
61+
"200": {
62+
"description": "successful operation",
63+
"schema": {
64+
"$ref": "#/definitions/ModelContainingModelWithReference"
65+
}
66+
}
67+
}
68+
}
7669
},
77-
"/some": {
70+
"/some": {
7871
"get": {
79-
"tags": [
80-
"basic"
81-
],
82-
"summary": "Get Some",
83-
"description": "",
84-
"operationId": "getSome",
85-
"parameters": [],
86-
"responses": {
87-
"200": {
88-
"description": "successful operation",
89-
"schema": {
90-
"$ref": "http://swagger.io/schemas.json#/Models/SomeResponse"
91-
}
92-
}
93-
}
94-
}
72+
"tags": ["basic"],
73+
"summary": "Get Some",
74+
"description": "",
75+
"operationId": "getSome",
76+
"parameters": [],
77+
"responses": {
78+
"200": {
79+
"description": "successful operation",
80+
"schema": {
81+
"$ref": "http://swagger.io/schemas.json#/Models/SomeResponse"
82+
}
83+
}
84+
}
85+
}
9586
},
96-
"/test": {
87+
"/test": {
9788
"get": {
98-
"tags": [
99-
"basic"
100-
],
101-
"operationId": "getTest",
102-
"parameters": [],
103-
"responses": {
104-
"500": {
105-
"description": "Error",
106-
"schema": {
107-
"$ref": "http://swagger.io/schemas.json#/Models/ErrorResponse"
108-
}
109-
}
110-
}
111-
}
89+
"tags": ["basic"],
90+
"operationId": "getTest",
91+
"parameters": [],
92+
"responses": {
93+
"500": {
94+
"description": "Error",
95+
"schema": {
96+
"$ref": "http://swagger.io/schemas.json#/Models/ErrorResponse"
97+
}
98+
}
99+
}
100+
}
112101
},
113-
"/testSome": {
102+
"/testSome": {
114103
"get": {
115-
"tags": [
116-
"basic"
117-
],
118-
"summary": "Get Some",
119-
"description": "",
120-
"operationId": "getTestSome",
121-
"parameters": [],
122-
"responses": {
123-
"200": {
124-
"description": "successful operation",
125-
"schema": {
126-
"$ref": "http://swagger.io/schemas.json#/Models/SomeResponse"
127-
}
128-
},
129-
"500": {
130-
"description": "Error",
131-
"schema": {
132-
"$ref": "http://swagger.io/schemas.json#/Models/ErrorResponse"
133-
}
134-
}
135-
}
136-
}
104+
"tags": ["basic"],
105+
"summary": "Get Some",
106+
"description": "",
107+
"operationId": "getTestSome",
108+
"parameters": [],
109+
"responses": {
110+
"200": {
111+
"description": "successful operation",
112+
"schema": {
113+
"$ref": "http://swagger.io/schemas.json#/Models/SomeResponse"
137114
}
138-
},
139-
"definitions": {
140-
"ModelWithReference": {
115+
},
116+
"500": {
117+
"description": "Error",
118+
"schema": {
119+
"$ref": "http://swagger.io/schemas.json#/Models/ErrorResponse"
120+
}
121+
}
122+
}
123+
}
124+
}
125+
}, "definitions": {
126+
"ModelWithReference": {
141127
"type": "object",
142128
"properties": {
143-
"description": {
144-
"$ref": "http://swagger.io/schemas.json#/Models/Description"
145-
}
146-
}
129+
"nested": {
130+
"type": "array",
131+
"description": "SubModelWithSelfReference",
132+
"items": {
133+
"$ref": "#/definitions/SubModelWithSelfReference"
134+
}
135+
},
136+
"description": {
137+
"$ref": "http://swagger.io/schemas.json#/Models/Description"
138+
}
139+
}
147140
},
148-
"ModelContainingModelWithReference": {
141+
"ModelContainingModelWithReference": {
149142
"type": "object",
150143
"properties": {
151-
"model": {
152-
"$ref": "http://swagger.io/schemas.json#/Models"
153-
},
154-
"anotherModel": {
155-
"$ref": "http://swagger.io/schemas.json#/Models/AnotherModel"
156-
}
157-
}
144+
"model": {
145+
"$ref": "http://swagger.io/schemas.json#/Models"
146+
},
147+
"anotherModel": {
148+
"$ref": "http://swagger.io/schemas.json#/Models/AnotherModel"
158149
}
159-
}
160-
}""")
150+
}
151+
},
152+
"SubModelWithSelfReference": {
153+
"type": "object",
154+
"properties": {
155+
"references": {
156+
"type": "array",
157+
"description": "References",
158+
"items": {
159+
"$ref": "#/definitions/SubModelWithSelfReference"
160+
}
161+
}
162+
}
163+
}
164+
}}
165+
"""
166+
)
161167
}
162168
}

modules/swagger-jaxrs/src/test/scala/models/ModelWithReference.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
import io.swagger.annotations.ApiModel;
44
import io.swagger.annotations.ApiModelProperty;
55

6+
import java.util.List;
7+
68
@ApiModel(reference = "http://swagger.io/schemas.json#/Models")
79
public class ModelWithReference {
810

911
@ApiModelProperty(reference = "http://swagger.io/schemas.json#/Models/Description")
1012
public String getDescription() {
1113
return "Swagger";
1214
}
15+
16+
@ApiModelProperty(value="SubModelWithSelfReference")
17+
public List<SubModelWithSelfReference> nested;
1318
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package models;
2+
3+
import io.swagger.annotations.ApiModelProperty;
4+
5+
import java.util.List;
6+
7+
/**
8+
* Created by simon00t on 10-6-2015.
9+
*/
10+
public class SubModelWithSelfReference {
11+
@ApiModelProperty(value="References")
12+
public List<SubModelWithSelfReference> references;
13+
}

0 commit comments

Comments
 (0)