Skip to content

Fix handling of relative refs without leading dot #422

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

Merged
merged 4 commits into from
Jan 18, 2018
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -424,6 +425,22 @@ public List<Parameter> 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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
petIdParam:
name: petId
in: path
description: ID of pet to return
required: true
type: integer
format: int64
Original file line number Diff line number Diff line change
@@ -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