Skip to content

Commit f31370e

Browse files
authored
Merge pull request swagger-api#422 from mkistler/issue-421
Fix handling of relative refs without leading dot
2 parents a7402b4 + 10ab96f commit f31370e

File tree

5 files changed

+252
-5
lines changed

5 files changed

+252
-5
lines changed

modules/swagger-parser/src/main/java/io/swagger/parser/util/SwaggerDeserializer.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.swagger.models.parameters.*;
88
import io.swagger.models.properties.Property;
99
import io.swagger.models.properties.PropertyBuilder;
10+
import io.swagger.models.properties.RefProperty;
1011
import io.swagger.util.Json;
1112

1213
import java.math.BigDecimal;
@@ -424,6 +425,22 @@ public List<Parameter> parameters(ArrayNode obj, String location, ParseResult re
424425
return output;
425426
}
426427

428+
// Refs may need to be massaged slightly to ensure that swagger-core (specifically GenericRef) recognizes
429+
// relative refs properly. This should be done anywhere refs could appear (properties, parameters, schema, etc),
430+
// so we have this function to ensure it is done consistently (and hopefully correctly) for all these occurrences.
431+
//
432+
// Returns a new ref string is change is needed, else returns null
433+
public String mungedRef(String refString) {
434+
// Ref: IETF RFC 3966, Section 5.2.2
435+
if (!refString.contains(":") && // No scheme
436+
!refString.startsWith("#") && // Path is not empty
437+
!refString.startsWith("/") && // Path is not absolute
438+
refString.indexOf(".") > 0) { // Path does not start with dot but contains "." (file extension)
439+
return "./" + refString;
440+
}
441+
return null;
442+
}
443+
427444
public Parameter parameter(ObjectNode obj, String location, ParseResult result) {
428445
if(obj == null) {
429446
return null;
@@ -433,6 +450,12 @@ public Parameter parameter(ObjectNode obj, String location, ParseResult result)
433450
JsonNode ref = obj.get("$ref");
434451
if(ref != null) {
435452
if(ref.getNodeType().equals(JsonNodeType.STRING)) {
453+
// work-around for https://github.com/swagger-api/swagger-core/issues/2138
454+
String mungedRef = mungedRef(ref.textValue());
455+
if (mungedRef != null) {
456+
obj.put("$ref", mungedRef);
457+
ref = obj.get("$ref");
458+
}
436459
return refParameter((TextNode) ref, location, result);
437460
}
438461
else {
@@ -979,10 +1002,9 @@ public Property property(ObjectNode node, String location, ParseResult result) {
9791002
// work-around for https://github.com/swagger-api/swagger-core/issues/1977
9801003
if(node.get("$ref") != null && node.get("$ref").isTextual()) {
9811004
// check if it's a relative ref
982-
String refString = node.get("$ref").textValue();
983-
if(refString.indexOf("/") == -1 && refString.indexOf(".") > 0) {
984-
refString = "./" + refString;
985-
node.put("$ref", refString);
1005+
String mungedRef = mungedRef(node.get("$ref").textValue());
1006+
if(mungedRef != null) {
1007+
node.put("$ref", mungedRef);
9861008
}
9871009
}
9881010
return Json.mapper().convertValue(node, Property.class);
@@ -1085,7 +1107,25 @@ public Response response(ObjectNode node, String location, ParseResult result) {
10851107

10861108
ObjectNode schema = getObject("schema", node, false, location, result);
10871109
if(schema != null) {
1088-
output.schema(Json.mapper().convertValue(schema, Property.class));
1110+
JsonNode schemaRef = schema.get("$ref");
1111+
if (schemaRef != null) {
1112+
if (schemaRef.getNodeType().equals(JsonNodeType.STRING)) {
1113+
1114+
// work-around for https://github.com/swagger-api/swagger-core/issues/2138
1115+
String mungedRef = mungedRef(schemaRef.textValue());
1116+
if (mungedRef != null) {
1117+
schema.put("$ref", mungedRef);
1118+
schemaRef = schema.get("$ref");
1119+
}
1120+
1121+
Property schemaProp = new RefProperty(schemaRef.textValue());
1122+
output.schema(schemaProp);
1123+
} else {
1124+
result.invalidType(location, "$ref", "string", node);
1125+
}
1126+
} else {
1127+
output.schema(Json.mapper().convertValue(schema, Property.class));
1128+
}
10891129
}
10901130
ObjectNode headersNode = getObject("headers", node, false, location, result);
10911131
if(headersNode != null) {

modules/swagger-parser/src/test/java/io/swagger/parser/FileReferenceTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
package io.swagger.parser;
22

33
import io.swagger.models.*;
4+
5+
import io.swagger.models.parameters.BodyParameter;
6+
import io.swagger.models.parameters.Parameter;
7+
import io.swagger.models.parameters.RefParameter;
48
import io.swagger.models.properties.ArrayProperty;
59
import io.swagger.models.properties.Property;
610
import io.swagger.models.properties.RefProperty;
11+
import io.swagger.models.refs.RefFormat;
712
import io.swagger.parser.util.SwaggerDeserializationResult;
813
import org.testng.annotations.Test;
914

15+
import java.util.List;
16+
1017
import static org.testng.Assert.*;
1118
import java.util.Arrays;
1219
import java.util.Map;
@@ -168,4 +175,38 @@ public void testAllOfFlatAndNested() {
168175
assertEquals(((RefProperty) ((ArrayProperty) props.get("siblings")).getItems()).getSimpleRef(), "pet");
169176
}
170177
}
178+
179+
@Test
180+
public void testIssue421() {
181+
SwaggerDeserializationResult result = new SwaggerParser().readWithInfo("./src/test/resources/nested-file-references/issue-421.yaml", null, true);
182+
assertNotNull(result.getSwagger());
183+
184+
Swagger swagger = result.getSwagger();
185+
assertNotNull(swagger.getPath("/pet/{petId}"));
186+
assertNotNull(swagger.getPath("/pet/{petId}").getGet());
187+
assertNotNull(swagger.getPath("/pet/{petId}").getGet().getParameters());
188+
assertTrue(swagger.getPath("/pet/{petId}").getGet().getParameters().size() == 1);
189+
assertTrue(swagger.getPath("/pet/{petId}").getGet().getParameters().get(0).getName().equals("petId"));
190+
assertTrue(swagger.getDefinitions().get("Pet") instanceof ModelImpl);
191+
assertTrue(swagger.getDefinitions().get("Pet").getProperties().size() == 6);
192+
193+
assertNotNull(swagger.getPath("/pet/{petId}").getPost());
194+
assertNotNull(swagger.getPath("/pet/{petId}").getPost().getParameters());
195+
assertTrue(swagger.getPath("/pet/{petId}").getPost().getParameters().size() == 3);
196+
assertTrue(swagger.getPath("/pet/{petId}").getPost().getParameters().get(1) instanceof RefParameter);
197+
assertTrue(((RefParameter)swagger.getPath("/pet/{petId}").getPost().getParameters().get(1)).getRefFormat() == RefFormat.INTERNAL);
198+
assertTrue(((RefParameter)swagger.getPath("/pet/{petId}").getPost().getParameters().get(1)).getSimpleRef().equals("name"));
199+
200+
assertNotNull(swagger.getPath("/store/order"));
201+
assertNotNull(swagger.getPath("/store/order").getPost());
202+
assertNotNull(swagger.getPath("/store/order").getPost().getParameters());
203+
assertTrue(swagger.getPath("/store/order").getPost().getParameters().size() == 1);
204+
assertTrue(swagger.getPath("/store/order").getPost().getParameters().get(0) instanceof BodyParameter);
205+
assertNotNull(((BodyParameter)swagger.getPath("/store/order").getPost().getParameters().get(0)).getSchema());
206+
assertTrue(((BodyParameter)swagger.getPath("/store/order").getPost().getParameters().get(0)).getSchema() instanceof RefModel);
207+
assertTrue(((RefModel)((BodyParameter)swagger.getPath("/store/order").getPost().getParameters().get(0)).getSchema()).getSimpleRef().equals("Order"));
208+
209+
assertTrue(swagger.getDefinitions().get("Order") instanceof ModelImpl);
210+
assertTrue(swagger.getDefinitions().get("Order").getProperties().size() == 6);
211+
}
171212
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
definitions:
2+
Pet:
3+
type: object
4+
required:
5+
- name
6+
- photoUrls
7+
properties:
8+
id:
9+
type: integer
10+
format: int64
11+
x-is-unique: true
12+
category:
13+
$ref: '#/definitions/Category'
14+
name:
15+
type: string
16+
example: doggie
17+
photoUrls:
18+
type: array
19+
xml:
20+
name: photoUrl
21+
wrapped: true
22+
items:
23+
type: string
24+
tags:
25+
type: array
26+
xml:
27+
name: tag
28+
wrapped: true
29+
items:
30+
$ref: '#/definitions/Tag'
31+
status:
32+
type: string
33+
description: pet status in the store
34+
enum:
35+
- available
36+
- pending
37+
- sold
38+
xml:
39+
name: Pet
40+
Category:
41+
type: object
42+
properties:
43+
id:
44+
type: integer
45+
format: int64
46+
name:
47+
type: string
48+
xml:
49+
name: Category
50+
Tag:
51+
type: object
52+
properties:
53+
id:
54+
type: integer
55+
format: int64
56+
name:
57+
type: string
58+
xml:
59+
name: Tag
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
petIdParam:
2+
name: petId
3+
in: path
4+
description: ID of pet to return
5+
required: true
6+
type: integer
7+
format: int64
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
swagger: '2.0'
2+
info:
3+
title: Test API
4+
version: '1'
5+
host: example.com
6+
basePath: /api/v1
7+
schemes:
8+
- https
9+
10+
paths:
11+
'/pet/{petId}':
12+
get:
13+
tags:
14+
- pet
15+
summary: Find pet by ID
16+
description: Returns a single pet
17+
operationId: getPetById
18+
produces:
19+
- application/json
20+
parameters:
21+
- $ref: 'common/issue-421-parms.yaml#/petIdParam'
22+
responses:
23+
'200':
24+
description: successful operation
25+
schema:
26+
$ref: 'common/issue-421-defns.yaml#/definitions/Pet'
27+
'400':
28+
description: Invalid ID supplied
29+
'404':
30+
description: Pet not found
31+
post:
32+
tags:
33+
- pet
34+
summary: Updates a pet in the store with form data
35+
description: ''
36+
operationId: updatePetWithForm
37+
consumes:
38+
- application/x-www-form-urlencoded
39+
produces:
40+
- application/xml
41+
- application/json
42+
parameters:
43+
- $ref: 'common/issue-421-parms.yaml#/petIdParam'
44+
- $ref: '#/parameters/name'
45+
- $ref: '#/parameters/status'
46+
responses:
47+
'200':
48+
description: successful operation
49+
schema:
50+
$ref: '#/definitions/ApiResponse'
51+
'405':
52+
description: Invalid input
53+
/store/order:
54+
post:
55+
tags:
56+
- store
57+
summary: Place an order for a pet
58+
description: ''
59+
operationId: placeOrder
60+
produces:
61+
- application/xml
62+
- application/json
63+
parameters:
64+
- in: body
65+
name: body
66+
description: order placed for purchasing the pet
67+
required: true
68+
schema:
69+
$ref: 'http://petstore.swagger.io/v2/swagger.json#/definitions/Order'
70+
responses:
71+
'200':
72+
description: successful operation
73+
schema:
74+
$ref: 'http://petstore.swagger.io/v2/swagger.json#/definitions/Order'
75+
'400':
76+
description: Invalid Order
77+
78+
parameters:
79+
- name: name
80+
in: formData
81+
description: Updated name of the pet
82+
required: false
83+
type: string
84+
- name: status
85+
in: formData
86+
description: Updated status of the pet
87+
required: false
88+
type: string
89+
90+
definitions:
91+
ApiResponse:
92+
type: object
93+
properties:
94+
code:
95+
type: integer
96+
format: int32
97+
type:
98+
type: string
99+
message:
100+
type: string

0 commit comments

Comments
 (0)