Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref #294 - fix circular refs stack overflow #296

Merged
merged 1 commit into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@
<artifactId>swagger-jersey2-jaxrs</artifactId>
<version>${swagger-core-version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-core</artifactId>
<version>${swagger-core-version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-parser</artifactId>
Expand Down Expand Up @@ -429,9 +434,10 @@
</dependency>
</dependencies>
<properties>
<!--<swagger-core-version>1.5.22-SNAPSHOT</swagger-core-version>-->
<swagger-core-version>1.5.21</swagger-core-version>
<swagger-parser-version>1.0.38</swagger-parser-version>
<jackson.version>2.8.7</jackson.version>
<swagger-parser-version>1.0.42</swagger-parser-version>
<jackson.version>2.9.8</jackson.version>
<joda-time-version>2.9.1</joda-time-version>
<joda-version>1.8.1</joda-version>
<jetty-version>9.2.9.v20150224</jetty-version>
Expand Down
75 changes: 60 additions & 15 deletions src/main/java/io/swagger/inflector/utils/ResolverUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public class ResolverUtil {
private Map<String, Model> schemas;
private Map<String, Model> resolvedModels = new HashMap<>();
private Map<String, Property> resolvedProperties = new HashMap<>();
private Map<String, Model> processedModels = new HashMap<>();
private Map<String, Property> processedProperties = new HashMap<>();

/* set resolveCircularRefsAsObjectRefs to true to allow (in some cases) resolving circular refs in spec
as circular object references in models/properties, see issue #984 */
private boolean resolveCircularRefsAsObjectRefs = System.getProperty("resolveCircularRefsAsObjectRefs") == null ? false : Boolean.valueOf(System.getProperty("resolveCircularRefsAsObjectRefs"));

public Map<String, Model> getResolvedModels() {
return resolvedModels;
Expand Down Expand Up @@ -100,22 +106,33 @@ public void resolvePath(Path path) {
public Model resolveModel(Model schema) {
if (schema instanceof RefModel) {
String ref = ((RefModel) schema).getSimpleRef();
Model resolved = schemas.get(ref);
if (resolved == null) {
Model definitionsSchema = schemas.get(ref);
if (definitionsSchema == null) {
LOGGER.error("unresolved model " + ref);
return schema;
}
if (this.resolvedModels.containsKey(ref)) {
LOGGER.debug("avoiding infinite loop");
return this.resolvedModels.get(ref);
}
this.resolvedModels.put(ref, schema);
if (!resolveCircularRefsAsObjectRefs) {
if (this.processedModels.containsKey(ref)) {
return this.processedModels.get(ref);
}

Model model = resolveModel(resolved);
if (this.processedProperties.containsKey(ref)) {
PropertyModelConverter converter = new PropertyModelConverter();
return converter.propertyToModel(this.processedProperties.get(ref));
}

this.processedModels.put(ref, schema);
} else {
this.resolvedModels.put(ref, schema);
}
Model resolved = resolveModel(definitionsSchema);
// if we make it without a resolution loop, we can update the reference
this.resolvedModels.put(ref, model);
return model;
this.resolvedModels.put(ref, resolved);
return resolved;
}
if (schema instanceof ArrayModel) {
ArrayModel arrayModel = (ArrayModel) schema;
Expand Down Expand Up @@ -151,6 +168,7 @@ public Model resolveModel(Model schema) {
}
return model;
}
return schema;
}
if (schema instanceof ComposedModel) {
ComposedModel composedSchema = (ComposedModel) schema;
Expand All @@ -166,7 +184,11 @@ public Model resolveModel(Model schema) {
if (property.getRequired()) {
requiredProperties.add(key);
}
model.addProperty(key, resolveProperty(property));
if (!resolveCircularRefsAsObjectRefs) {
model.addProperty(key, property);
} else {
model.addProperty(key, resolveProperty(property));
}
}

}
Expand All @@ -190,8 +212,8 @@ public Model resolveModel(Model schema) {
private Property resolveProperty(Property property) {
if (property instanceof RefProperty) {
String ref = ((RefProperty) property).getSimpleRef();
Model resolved = schemas.get(ref);
if (resolved == null) {
Model definitionsSchema = schemas.get(ref);
if (definitionsSchema == null) {
LOGGER.error("unresolved model " + ref);
return property;
}
Expand All @@ -215,15 +237,38 @@ private Property resolveProperty(Property property) {

}

this.resolvedProperties.put(ref, property);
Model model = resolveModel(resolved);
if (!resolveCircularRefsAsObjectRefs) {
if (this.processedModels.containsKey(ref) || this.processedProperties.containsKey(ref)) {
LOGGER.debug("avoiding infinite loop");
Model modelResolved = this.processedModels.get(ref);
Property propertyResolved = this.processedProperties.get(ref);
if (modelResolved != null) {
PropertyModelConverter converter = new PropertyModelConverter();
Property convertedProperty = converter.modelToProperty(modelResolved);
if (convertedProperty instanceof UntypedProperty && modelResolved instanceof ModelImpl) {
Property property1 = createObjectProperty(modelResolved);
this.processedProperties.put(ref, property1);
return property1;
} else {
return convertedProperty;
}
} else if (propertyResolved != null) {
return propertyResolved;
}

}
this.processedProperties.put(ref, property);
} else {
this.resolvedProperties.put(ref, property);
}
Model resolved = resolveModel(definitionsSchema);

// if we make it without a resolution loop, we can update the reference
this.resolvedModels.put(ref, model);
this.resolvedModels.put(ref, resolved);
PropertyModelConverter converter = new PropertyModelConverter();
Property prop = converter.modelToProperty(model);
if (prop instanceof UntypedProperty && model instanceof ModelImpl) {
Property property1 = createObjectProperty(model);
Property prop = converter.modelToProperty(resolved);
if (prop instanceof UntypedProperty && resolved instanceof ModelImpl) {
Property property1 = createObjectProperty(resolved);
this.resolvedProperties.put(ref, property1);
return property1;
} else {
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/io/swagger/test/examples/ExampleBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,10 @@ public void testCircularRefSchema() throws Exception {
@Test
public void testCircularRefSchemaInResponse() throws Exception {
Swagger swagger = new SwaggerParser().read("./src/test/swagger/circuler-refs-SPLAT-56-2.yaml");
System.setProperty("resolveCircularRefsAsObjectRefs", "true");
ResolverUtil resolverUtil = new ResolverUtil();
resolverUtil.resolveFully(swagger);
String yaml = Yaml.pretty().writeValueAsString(swagger);
Response response = swagger.getPaths().get("/candidates").getOperationMap().get(HttpMethod.GET).getResponses().get("200");
Example example = ExampleBuilder.fromModel("", response.getResponseSchema(), swagger.getDefinitions(), new HashMap<String, Example>());
assertNotNull(example);
Expand Down Expand Up @@ -693,6 +695,14 @@ public void testCircularRefSchemaInResponse() throws Exception {
" \"selfObj\" : { }\n" +
" }\n" +
"}");

System.setProperty("resolveCircularRefsAsObjectRefs", "false");

swagger = new SwaggerParser().read("./src/test/swagger/circuler-refs-SPLAT-56-2.yaml");
resolverUtil = new ResolverUtil();
resolverUtil.resolveFully(swagger);
String yaml2 = Yaml.pretty().writeValueAsString(swagger);
assertEquals(yaml, yaml2);
}

@Test
Expand Down
17 changes: 15 additions & 2 deletions src/test/java/io/swagger/test/utils/ResolverUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.ObjectProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.UntypedProperty;
import io.swagger.parser.SwaggerParser;
import io.swagger.sample.models.Dog;
import io.swagger.util.Json;
Expand All @@ -25,6 +23,21 @@

public class ResolverUtilTest {


@Test
public void testIssue294() throws Exception {
Swagger swagger = new SwaggerParser().read("./src/test/swagger/issue-294/issue-294.yaml");
new ResolverUtil().resolveFully(swagger);
Yaml.prettyPrint(swagger);
try {
Json.mapper().writeValueAsString(swagger);

}
catch (Exception e) {
fail("Recursive loop found");
}
}

@Test
public void testRefs2() {
Swagger swagger = new SwaggerParser().read("./src/test/swagger/anotherSpec.yaml");
Expand Down
54 changes: 54 additions & 0 deletions src/test/swagger/issue-294/issue-294.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"swagger": "2.0",
"info": {
"description": "Data",
"version": "1.0",
"title": "Data"
},
"basePath": "/dcs/api",
"paths": {
"/concept": {
"get": {
"summary": "Retrieve settings",
"description": "settings",
"operationId": "getSettings",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Concept"
}
}
}
}
}
}
},
"definitions": {

"Concept": {
"type": "object",
"properties": {
"thing": {
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/definitions/Thing"
}
}
}
},
"Thing": {
"allOf": [
{
"$ref": "#/definitions/Concept"
}
]
}
}
}