diff --git a/modules/swagger-parser/src/main/java/io/swagger/parser/processors/DefinitionsProcessor.java b/modules/swagger-parser/src/main/java/io/swagger/parser/processors/DefinitionsProcessor.java index 369efc6fc8..537c7e43c5 100644 --- a/modules/swagger-parser/src/main/java/io/swagger/parser/processors/DefinitionsProcessor.java +++ b/modules/swagger-parser/src/main/java/io/swagger/parser/processors/DefinitionsProcessor.java @@ -30,9 +30,17 @@ public void processDefinitions() { } Set keySet = new HashSet<>(); - keySet.addAll(definitions.keySet()); - for (String modelName : keySet) { + // the definitions can grow as we resolve references + while(definitions.keySet().size() > keySet.size()) { + processDefinitions(keySet, definitions); + } + } + + public void processDefinitions(Set modelKeys, Map definitions) { + modelKeys.addAll(definitions.keySet()); + + for (String modelName : modelKeys) { final Model model = definitions.get(modelName); String originalRef = model instanceof RefModel ? ((RefModel) model).get$ref() : null; @@ -48,7 +56,6 @@ public void processDefinitions() { final Model resolvedModel = definitions.remove(renamedRef); definitions.put(modelName, resolvedModel); } - } } } diff --git a/modules/swagger-parser/src/main/java/io/swagger/parser/processors/ExternalRefProcessor.java b/modules/swagger-parser/src/main/java/io/swagger/parser/processors/ExternalRefProcessor.java index fe7a7fe6e1..dc74e89e14 100644 --- a/modules/swagger-parser/src/main/java/io/swagger/parser/processors/ExternalRefProcessor.java +++ b/modules/swagger-parser/src/main/java/io/swagger/parser/processors/ExternalRefProcessor.java @@ -12,6 +12,7 @@ import io.swagger.parser.ResolverCache; import org.slf4j.LoggerFactory; +import java.net.URI; import java.util.HashMap; import java.util.Map; @@ -95,9 +96,50 @@ public String processRefToExternalDefinition(String $ref, RefFormat refFormat) { private void processRefProperty(RefProperty subRef, String externalFile) { if (isAnExternalRefFormat(subRef.getRefFormat())) { - subRef.set$ref(processRefToExternalDefinition(subRef.get$ref(), subRef.getRefFormat())); + String $ref = constructRef(subRef, externalFile); + subRef.set$ref($ref); + if($ref.startsWith(".")) + processRefToExternalDefinition($ref, RefFormat.RELATIVE); + else { + processRefToExternalDefinition($ref, RefFormat.URL); + } + } else { processRefToExternalDefinition(externalFile + subRef.get$ref(), RefFormat.RELATIVE); } } + + protected String constructRef(RefProperty refProperty, String rootLocation) { + String ref = refProperty.get$ref(); + return join(rootLocation, ref); + } + + public static String join(String source, String fragment) { + try { + boolean isRelative = false; + if(source.startsWith("/") || source.startsWith(".")) { + isRelative = true; + } + URI uri = new URI(source); + + if(!source.endsWith("/") && (fragment.startsWith("./") && "".equals(uri.getPath()))) { + uri = new URI(source + "/"); + } + else if("".equals(uri.getPath()) && !fragment.startsWith("/")) { + uri = new URI(source + "/"); + } + URI f = new URI(fragment); + + URI resolved = uri.resolve(f); + + URI normalized = resolved.normalize(); + if(Character.isAlphabetic(normalized.toString().charAt(0)) && isRelative) { + return "./" + normalized.toString(); + } + return normalized.toString(); + } + catch(Exception e) { + return source; + } + } } 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 f3b0182ec5..c195c32d64 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,15 +1,15 @@ package io.swagger.parser; +import io.swagger.models.Model; import io.swagger.models.Operation; import io.swagger.models.Path; import io.swagger.models.Swagger; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; import io.swagger.parser.util.SwaggerDeserializationResult; -import io.swagger.util.Json; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; public class FileReferenceTests { @Test @@ -77,7 +77,6 @@ public void testIssue314() { Path path = swagger.getPath("/events"); assertNotNull(path.getGet()); - Json.prettyPrint(result); Operation get = path.getGet(); assertEquals(get.getOperationId(), "getEvents"); assertTrue(swagger.getDefinitions().size() == 3); @@ -85,4 +84,27 @@ public void testIssue314() { assertTrue(swagger.getDefinitions().get("StatusResponse").getProperties().size() == 1); assertTrue(swagger.getDefinitions().get("Paging").getProperties().size() == 1); } + + @Test + public void testIssue316() { + SwaggerDeserializationResult result = new SwaggerParser().readWithInfo("./src/test/resources/nested-file-references/issue-316.yaml", null, true); + assertNotNull(result.getSwagger()); + + Swagger swagger = result.getSwagger(); + assertNotNull(swagger.getPath("/events")); + Path path = swagger.getPath("/events"); + assertNotNull(path.getGet()); + Operation get = path.getGet(); + assertEquals(get.getOperationId(), "getEvents"); + assertTrue(swagger.getDefinitions().size() == 3); + assertTrue(swagger.getDefinitions().get("Foobar").getProperties().size() == 1); + assertTrue(swagger.getDefinitions().get("StatusResponse").getProperties().size() == 1); + assertTrue(swagger.getDefinitions().get("Paging2").getProperties().size() == 2); + Model model = swagger.getDefinitions().get("Paging2"); + + Property property = model.getProperties().get("foobar"); + assertTrue(property instanceof RefProperty); + RefProperty ref = (RefProperty) property; + assertEquals(ref.get$ref(), "#/definitions/Foobar"); + } } diff --git a/modules/swagger-parser/src/test/java/io/swagger/parser/SwaggerParserTest.java b/modules/swagger-parser/src/test/java/io/swagger/parser/SwaggerParserTest.java index b3576d4b69..b7c99486c8 100644 --- a/modules/swagger-parser/src/test/java/io/swagger/parser/SwaggerParserTest.java +++ b/modules/swagger-parser/src/test/java/io/swagger/parser/SwaggerParserTest.java @@ -228,7 +228,6 @@ public void testLoadRecursiveExternalDef() throws Exception { SwaggerParser parser = new SwaggerParser(); final Swagger swagger = parser.read("src/test/resources/file-reference-to-recursive-defs/b.yaml"); - Json.prettyPrint(swagger); Map definitions = swagger.getDefinitions(); assertEquals(((RefProperty) ((ArrayProperty) definitions.get("v").getProperties().get("children")).getItems()).get$ref(), "#/definitions/v"); assertTrue(!definitions.containsKey("y")); diff --git a/modules/swagger-parser/src/test/java/io/swagger/parser/processors/ExternalRefProcessorTest.java b/modules/swagger-parser/src/test/java/io/swagger/parser/processors/ExternalRefProcessorTest.java index 4d0df3f952..9e1e66015b 100644 --- a/modules/swagger-parser/src/test/java/io/swagger/parser/processors/ExternalRefProcessorTest.java +++ b/modules/swagger-parser/src/test/java/io/swagger/parser/processors/ExternalRefProcessorTest.java @@ -115,7 +115,7 @@ public void testNestedExternalRefs(@Injectable final Model mockedModel){ String actualRef = new ExternalRefProcessor(cache, testedSwagger).processRefToExternalDefinition(customerURL, refFormat); - assertTrue(testedSwagger.getDefinitions().get("Customer")!=null); + assertTrue(testedSwagger.getDefinitions().get("Customer")!=null); assertTrue(testedSwagger.getDefinitions().get("Contact")!=null); assertTrue(testedSwagger.getDefinitions().get("Address")!=null); } diff --git a/modules/swagger-parser/src/test/java/io/swagger/parser/util/RefUtilsTest.java b/modules/swagger-parser/src/test/java/io/swagger/parser/util/RefUtilsTest.java index ee63c7be75..c484ea392a 100644 --- a/modules/swagger-parser/src/test/java/io/swagger/parser/util/RefUtilsTest.java +++ b/modules/swagger-parser/src/test/java/io/swagger/parser/util/RefUtilsTest.java @@ -4,6 +4,7 @@ import io.swagger.models.ModelImpl; import io.swagger.models.auth.AuthorizationValue; import io.swagger.models.refs.RefFormat; +import io.swagger.parser.processors.ExternalRefProcessor; import mockit.Injectable; import mockit.Mocked; import mockit.StrictExpectations; @@ -251,4 +252,33 @@ public void testReadExternalRef_OnClasspath(@Mocked Files files, assertEquals(actualResult, expectedResult); } + + @Test + public void testPathJoin1() { + // simple + assertEquals(ExternalRefProcessor.join("http://foo.bar.com", "fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/", "fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/", "/fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com", "/fun"), "http://foo.bar.com/fun"); + + // relative to host + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/foo/bar", "/fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/foo/bar#/baz/bat", "/fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/foo/bar#/baz/bat", "/fun#for/all"), "http://foo.bar.com/fun#for/all"); + + // relative + assertEquals(ExternalRefProcessor.join("http://foo.bar.com", "./fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/veryFun", "./fun"), "http://foo.bar.com/fun"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/veryFun/", "../fun#nothing"), "http://foo.bar.com/fun#nothing"); + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/veryFun/notFun", "../fun#/it/is/fun"), "http://foo.bar.com/fun#/it/is/fun"); + + // with file extensions + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/baz/bat.yaml", "../fun/times.yaml"), "http://foo.bar.com/fun/times.yaml"); + + // hashes + assertEquals(ExternalRefProcessor.join("http://foo.bar.com/veryFun/", "../fun#/it/is/fun"), "http://foo.bar.com/fun#/it/is/fun"); + + // relative locations + assertEquals(ExternalRefProcessor.join("./foo#/definitions/Foo", "./bar#/definitions/Bar"), "./bar#/definitions/Bar"); + } } diff --git a/modules/swagger-parser/src/test/java/io/swagger/parser/util/SwaggerDeserializerTest.java b/modules/swagger-parser/src/test/java/io/swagger/parser/util/SwaggerDeserializerTest.java index 38487ca8f3..5f7c3d6f75 100644 --- a/modules/swagger-parser/src/test/java/io/swagger/parser/util/SwaggerDeserializerTest.java +++ b/modules/swagger-parser/src/test/java/io/swagger/parser/util/SwaggerDeserializerTest.java @@ -11,7 +11,6 @@ import io.swagger.parser.SwaggerParser; import io.swagger.parser.SwaggerResolver; import io.swagger.util.Json; -import io.swagger.util.Yaml; import org.testng.Assert; import org.testng.annotations.Test; @@ -1148,6 +1147,5 @@ public void testPR246() throws Exception { assertTrue(composed.getChild() instanceof ModelImpl); assertTrue(composed.getInterfaces().size() == 2); - Yaml.prettyPrint(swagger); } } diff --git a/modules/swagger-parser/src/test/resources/nested-file-references/common/paging2.yaml b/modules/swagger-parser/src/test/resources/nested-file-references/common/paging2.yaml new file mode 100644 index 0000000000..41d22e8ed5 --- /dev/null +++ b/modules/swagger-parser/src/test/resources/nested-file-references/common/paging2.yaml @@ -0,0 +1,6 @@ +Paging2: + properties: + total_items: + type: integer + foobar: + $ref: '../common2/bar.yaml#/Foobar' diff --git a/modules/swagger-parser/src/test/resources/nested-file-references/eventsWithPaging.yaml b/modules/swagger-parser/src/test/resources/nested-file-references/eventsWithPaging.yaml new file mode 100644 index 0000000000..f411e0c2bf --- /dev/null +++ b/modules/swagger-parser/src/test/resources/nested-file-references/eventsWithPaging.yaml @@ -0,0 +1,11 @@ +get: + description: A list of events + operationId: getEvents + responses: + 200: + description: OK + schema: + type: object + properties: + paging: + $ref: './common/paging2.yaml#/Paging2' diff --git a/modules/swagger-parser/src/test/resources/nested-file-references/issue-316.yaml b/modules/swagger-parser/src/test/resources/nested-file-references/issue-316.yaml new file mode 100644 index 0000000000..c5d1a4847a --- /dev/null +++ b/modules/swagger-parser/src/test/resources/nested-file-references/issue-316.yaml @@ -0,0 +1,22 @@ +swagger: '2.0' +info: + title: Test API + version: '1' +host: example.com +basePath: /api/v1 +schemes: + - https +consumes: + - application/json; charset=utf-8 +produces: + - application/json; charset=utf-8 + +paths: + /events: + $ref: './eventsWithPaging.yaml' + +definitions: + StatusResponse: + properties: + http_code: + type: integer