diff --git a/modules/swagger-parser/src/main/java/io/swagger/parser/util/SwaggerDeserializer.java b/modules/swagger-parser/src/main/java/io/swagger/parser/util/SwaggerDeserializer.java index d02608a6d9..91419943d3 100644 --- a/modules/swagger-parser/src/main/java/io/swagger/parser/util/SwaggerDeserializer.java +++ b/modules/swagger-parser/src/main/java/io/swagger/parser/util/SwaggerDeserializer.java @@ -7,6 +7,7 @@ import io.swagger.models.parameters.*; import io.swagger.models.properties.Property; import io.swagger.models.properties.PropertyBuilder; +import io.swagger.models.properties.RefProperty; import io.swagger.util.Json; import java.math.BigDecimal; @@ -424,6 +425,22 @@ public List parameters(ArrayNode obj, String location, ParseResult re return output; } + // Refs may need to be massaged slightly to ensure that swagger-core (specifically GenericRef) recognizes + // relative refs properly. This should be done anywhere refs could appear (properties, parameters, schema, etc), + // so we have this function to ensure it is done consistently (and hopefully correctly) for all these occurrences. + // + // Returns a new ref string is change is needed, else returns null + public String mungedRef(String refString) { + // Ref: IETF RFC 3966, Section 5.2.2 + if (!refString.contains(":") && // No scheme + !refString.startsWith("#") && // Path is not empty + !refString.startsWith("/") && // Path is not absolute + refString.indexOf(".") > 0) { // Path does not start with dot but contains "." (file extension) + return "./" + refString; + } + return null; + } + public Parameter parameter(ObjectNode obj, String location, ParseResult result) { if(obj == null) { return null; @@ -433,6 +450,12 @@ public Parameter parameter(ObjectNode obj, String location, ParseResult result) JsonNode ref = obj.get("$ref"); if(ref != null) { if(ref.getNodeType().equals(JsonNodeType.STRING)) { + // work-around for https://github.com/swagger-api/swagger-core/issues/2138 + String mungedRef = mungedRef(ref.textValue()); + if (mungedRef != null) { + obj.put("$ref", mungedRef); + ref = obj.get("$ref"); + } return refParameter((TextNode) ref, location, result); } else { @@ -979,10 +1002,9 @@ public Property property(ObjectNode node, String location, ParseResult result) { // work-around for https://github.com/swagger-api/swagger-core/issues/1977 if(node.get("$ref") != null && node.get("$ref").isTextual()) { // check if it's a relative ref - String refString = node.get("$ref").textValue(); - if(refString.indexOf("/") == -1 && refString.indexOf(".") > 0) { - refString = "./" + refString; - node.put("$ref", refString); + String mungedRef = mungedRef(node.get("$ref").textValue()); + if(mungedRef != null) { + node.put("$ref", mungedRef); } } return Json.mapper().convertValue(node, Property.class); @@ -1085,7 +1107,25 @@ public Response response(ObjectNode node, String location, ParseResult result) { ObjectNode schema = getObject("schema", node, false, location, result); if(schema != null) { - output.schema(Json.mapper().convertValue(schema, Property.class)); + JsonNode schemaRef = schema.get("$ref"); + if (schemaRef != null) { + if (schemaRef.getNodeType().equals(JsonNodeType.STRING)) { + + // work-around for https://github.com/swagger-api/swagger-core/issues/2138 + String mungedRef = mungedRef(schemaRef.textValue()); + if (mungedRef != null) { + schema.put("$ref", mungedRef); + schemaRef = schema.get("$ref"); + } + + Property schemaProp = new RefProperty(schemaRef.textValue()); + output.schema(schemaProp); + } else { + result.invalidType(location, "$ref", "string", node); + } + } else { + output.schema(Json.mapper().convertValue(schema, Property.class)); + } } ObjectNode headersNode = getObject("headers", node, false, location, result); if(headersNode != null) { diff --git a/modules/swagger-parser/src/test/java/io/swagger/parser/FileReferenceTests.java b/modules/swagger-parser/src/test/java/io/swagger/parser/FileReferenceTests.java index 33c27fae5b..19a056bdaa 100644 --- a/modules/swagger-parser/src/test/java/io/swagger/parser/FileReferenceTests.java +++ b/modules/swagger-parser/src/test/java/io/swagger/parser/FileReferenceTests.java @@ -1,12 +1,19 @@ package io.swagger.parser; import io.swagger.models.*; + +import io.swagger.models.parameters.BodyParameter; +import io.swagger.models.parameters.Parameter; +import io.swagger.models.parameters.RefParameter; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.RefProperty; +import io.swagger.models.refs.RefFormat; import io.swagger.parser.util.SwaggerDeserializationResult; import org.testng.annotations.Test; +import java.util.List; + import static org.testng.Assert.*; import java.util.Arrays; import java.util.Map; @@ -168,4 +175,38 @@ public void testAllOfFlatAndNested() { assertEquals(((RefProperty) ((ArrayProperty) props.get("siblings")).getItems()).getSimpleRef(), "pet"); } } + + @Test + public void testIssue421() { + SwaggerDeserializationResult result = new SwaggerParser().readWithInfo("./src/test/resources/nested-file-references/issue-421.yaml", null, true); + assertNotNull(result.getSwagger()); + + Swagger swagger = result.getSwagger(); + assertNotNull(swagger.getPath("/pet/{petId}")); + assertNotNull(swagger.getPath("/pet/{petId}").getGet()); + assertNotNull(swagger.getPath("/pet/{petId}").getGet().getParameters()); + assertTrue(swagger.getPath("/pet/{petId}").getGet().getParameters().size() == 1); + assertTrue(swagger.getPath("/pet/{petId}").getGet().getParameters().get(0).getName().equals("petId")); + assertTrue(swagger.getDefinitions().get("Pet") instanceof ModelImpl); + assertTrue(swagger.getDefinitions().get("Pet").getProperties().size() == 6); + + assertNotNull(swagger.getPath("/pet/{petId}").getPost()); + assertNotNull(swagger.getPath("/pet/{petId}").getPost().getParameters()); + assertTrue(swagger.getPath("/pet/{petId}").getPost().getParameters().size() == 3); + assertTrue(swagger.getPath("/pet/{petId}").getPost().getParameters().get(1) instanceof RefParameter); + assertTrue(((RefParameter)swagger.getPath("/pet/{petId}").getPost().getParameters().get(1)).getRefFormat() == RefFormat.INTERNAL); + assertTrue(((RefParameter)swagger.getPath("/pet/{petId}").getPost().getParameters().get(1)).getSimpleRef().equals("name")); + + assertNotNull(swagger.getPath("/store/order")); + assertNotNull(swagger.getPath("/store/order").getPost()); + assertNotNull(swagger.getPath("/store/order").getPost().getParameters()); + assertTrue(swagger.getPath("/store/order").getPost().getParameters().size() == 1); + assertTrue(swagger.getPath("/store/order").getPost().getParameters().get(0) instanceof BodyParameter); + assertNotNull(((BodyParameter)swagger.getPath("/store/order").getPost().getParameters().get(0)).getSchema()); + assertTrue(((BodyParameter)swagger.getPath("/store/order").getPost().getParameters().get(0)).getSchema() instanceof RefModel); + assertTrue(((RefModel)((BodyParameter)swagger.getPath("/store/order").getPost().getParameters().get(0)).getSchema()).getSimpleRef().equals("Order")); + + assertTrue(swagger.getDefinitions().get("Order") instanceof ModelImpl); + assertTrue(swagger.getDefinitions().get("Order").getProperties().size() == 6); + } } diff --git a/modules/swagger-parser/src/test/resources/nested-file-references/common/issue-421-defns.yaml b/modules/swagger-parser/src/test/resources/nested-file-references/common/issue-421-defns.yaml new file mode 100644 index 0000000000..e4e5c3e64e --- /dev/null +++ b/modules/swagger-parser/src/test/resources/nested-file-references/common/issue-421-defns.yaml @@ -0,0 +1,59 @@ +definitions: + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + x-is-unique: true + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag diff --git a/modules/swagger-parser/src/test/resources/nested-file-references/common/issue-421-parms.yaml b/modules/swagger-parser/src/test/resources/nested-file-references/common/issue-421-parms.yaml new file mode 100644 index 0000000000..a9dc9de09f --- /dev/null +++ b/modules/swagger-parser/src/test/resources/nested-file-references/common/issue-421-parms.yaml @@ -0,0 +1,7 @@ +petIdParam: + name: petId + in: path + description: ID of pet to return + required: true + type: integer + format: int64 diff --git a/modules/swagger-parser/src/test/resources/nested-file-references/issue-421.yaml b/modules/swagger-parser/src/test/resources/nested-file-references/issue-421.yaml new file mode 100644 index 0000000000..ccf0cbde16 --- /dev/null +++ b/modules/swagger-parser/src/test/resources/nested-file-references/issue-421.yaml @@ -0,0 +1,100 @@ +swagger: '2.0' +info: + title: Test API + version: '1' +host: example.com +basePath: /api/v1 +schemes: + - https + +paths: + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + produces: + - application/json + parameters: + - $ref: 'common/issue-421-parms.yaml#/petIdParam' + responses: + '200': + description: successful operation + schema: + $ref: 'common/issue-421-defns.yaml#/definitions/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + consumes: + - application/x-www-form-urlencoded + produces: + - application/xml + - application/json + parameters: + - $ref: 'common/issue-421-parms.yaml#/petIdParam' + - $ref: '#/parameters/name' + - $ref: '#/parameters/status' + responses: + '200': + description: successful operation + schema: + $ref: '#/definitions/ApiResponse' + '405': + description: Invalid input + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + produces: + - application/xml + - application/json + parameters: + - in: body + name: body + description: order placed for purchasing the pet + required: true + schema: + $ref: 'http://petstore.swagger.io/v2/swagger.json#/definitions/Order' + responses: + '200': + description: successful operation + schema: + $ref: 'http://petstore.swagger.io/v2/swagger.json#/definitions/Order' + '400': + description: Invalid Order + +parameters: + - name: name + in: formData + description: Updated name of the pet + required: false + type: string + - name: status + in: formData + description: Updated status of the pet + required: false + type: string + +definitions: + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string