Repository: swagger-api/swagger-parser
Pull request #806 (2018-08-20 10:07:27)

fix relative ref with http ref bug
This patch fixes https://github.com/swagger-api/swagger-parser/issues/805.


Patch:
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ExternalRefProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ExternalRefProcessor.java
index b5de807d..12c7d253 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ExternalRefProcessor.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ExternalRefProcessor.java
@@ -665,21 +665,19 @@ public final class ExternalRefProcessor {
         return newRef;
     }
 
-
     private void processRefSchema(Schema subRef, String externalFile) {
         RefFormat format = computeRefFormat(subRef.get$ref());
-        if (isAnExternalRefFormat(format)) {
-            String $ref = constructRef(subRef, externalFile);
-            subRef.set$ref($ref);
-            if($ref.startsWith("."))
-                processRefToExternalSchema($ref, RefFormat.RELATIVE);
-            else {
-                processRefToExternalSchema($ref, RefFormat.URL);
-            }
 
-        } else {
+        if (!isAnExternalRefFormat(format)) {
             processRefToExternalSchema(externalFile + subRef.get$ref(), RefFormat.RELATIVE);
+            return;
+        }
+        String $ref = subRef.get$ref();
+        if (format.equals(RefFormat.RELATIVE)) {
+            $ref = constructRef(subRef, externalFile);
+            subRef.set$ref($ref);
         }
+        processRefToExternalSchema($ref, computeRefFormat(subRef.get$ref()));
     }
 
 
@@ -688,6 +686,7 @@ public final class ExternalRefProcessor {
         return join(rootLocation, ref);
     }
 
+    // visible for testing
     public static String join(String source, String fragment) {
         try {
             boolean isRelative = false;


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java
index 3d808eca..4134e025 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ExternalRefProcessorTest.java
@@ -6,9 +6,12 @@ import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.media.Schema;
 import io.swagger.v3.oas.models.media.StringSchema;
 import io.swagger.v3.parser.ResolverCache;
+import io.swagger.v3.parser.core.models.AuthorizationValue;
 import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RemoteUrl;
 import mockit.Expectations;
 import mockit.Injectable;
+import mockit.Mocked;
 import mockit.StrictExpectations;
 import org.testng.annotations.Test;
 
@@ -17,6 +20,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
 import static org.testng.Assert.assertEquals;
 import static org.testng.AssertJUnit.assertTrue;
 
@@ -70,33 +75,33 @@ public class ExternalRefProcessorTest {
     @Test
     public void testNestedExternalRefs(@Injectable final Schema mockedModel){
     	final RefFormat refFormat = RefFormat.URL;
-  	
+
     	//Swagger test instance
     	OpenAPI testedOpenAPI = new OpenAPI();
-    	
+
     	//Start with customer, add address property to it
     	final String customerURL = "http://my.company.com/path/to/customer.json#/definitions/Customer";
-    	
+
     	final Schema customerModel = new Schema();
     	Map<String,Schema> custProps = new HashMap<>();
     	Schema address = new Schema();
     	final String addressURL = "http://my.company.com/path/to/address.json#/definitions/Address";
     	address.set$ref(addressURL);
     	custProps.put("Address", address);
-    	
+
     	//Create a 'local' reference to something in #/definitions, this should be ignored a no longer result in a null pointer exception
     	final String loyaltyURL = "#/definitions/LoyaltyScheme";
-    	
+
     	Schema loyaltyProp = new Schema();
     	loyaltyProp.set$ref(loyaltyURL);
     	loyaltyProp.setName("LoyaltyCardNumber");
     	List<String> required = new ArrayList<>();
     	required.add("LoyaltyCardNumber");
     	loyaltyProp.setRequired(required);
-    	
+
     	custProps.put("Loyalty", loyaltyProp);
     	customerModel.setProperties(custProps);
-    	
+
     	//create address model, add Contact Ref Property to it
     	final Schema addressModel = new Schema();
     	Map<String, Schema> addressProps = new HashMap<>();
@@ -105,8 +110,8 @@ public class ExternalRefProcessorTest {
     	contact.set$ref(contactURL);
     	addressProps.put("Contact", contact);
     	addressModel.setProperties(addressProps);
-    	
-    	
+
+
     	//Create contact model, with basic type property
     	final Schema contactModel = new Schema();
     	Schema contactProp = new StringSchema();
@@ -117,25 +122,87 @@ public class ExternalRefProcessorTest {
     	Map<String, Schema> contactProps = new HashMap<>();
     	contactProps.put("PhoneNumber", contactProp);
     	contactModel.setProperties(contactProps);
-    	
+
     	new Expectations(){{
     		cache.loadRef(customerURL, refFormat, Schema.class);
     		result = customerModel;
     		times = 1;
-    				
+
     		cache.loadRef(addressURL, refFormat, Schema.class);
     		result = addressModel;
     		times = 1;
-    		
+
     		cache.loadRef(contactURL, refFormat, Schema.class);
     		result = contactModel;
     		times = 1;
  		}};
-    	
+
     	String actualRef = new ExternalRefProcessor(cache, testedOpenAPI).processRefToExternalSchema(customerURL, refFormat);
 
 		assertTrue(testedOpenAPI.getComponents().getSchemas().get("Customer") != null);
     	assertTrue(testedOpenAPI.getComponents().getSchemas().get("Contact") != null);
     	assertTrue(testedOpenAPI.getComponents().getSchemas().get("Address") != null);
     }
+
+
+	@Mocked
+	RemoteUrl remoteUrl;
+
+
+	@Test
+	public void testRelativeRefIncludingUrlRef(@Injectable final Schema mockedModel)
+			throws Exception {
+		final RefFormat refFormat = RefFormat.RELATIVE;
+
+		final String url = "https://my.example.remote.url.com/globals.yaml";
+
+		final String expectedResult = "components:\n" +
+				"  schemas:\n" +
+				"    link-object:\n" +
+				"      type: object\n" +
+				"      additionalProperties:\n" +
+				"        \"$ref\": \"#/components/schemas/rel-data\"\n" +
+				"    rel-data:\n" +
+				"      type: object\n" +
+				"      required:\n" +
+				"      - href\n" +
+				"      properties:\n" +
+				"        href:\n" +
+				"          type: string\n" +
+				"        note:\n" +
+				"          type: string\n" +
+				"    result:\n" +
+				"      type: object\n" +
+				"      properties:\n" +
+				"        name:\n" +
+				"          type: string\n" +
+				"        _links:\n" +
+				"          \"$ref\": \"#/components/schemas/link-object\"\n" +
+				"";
+		List<AuthorizationValue> auths = null;
+
+		new Expectations() {{
+			RemoteUrl.urlToString(url, auths);
+			times = 1;
+			result = expectedResult;
+		}};
+
+		OpenAPI mockedOpenAPI = new OpenAPI();
+		mockedOpenAPI.setComponents(new Components());
+		mockedOpenAPI.getComponents().setSchemas(new HashMap<>());
+		ResolverCache mockedResolverCache = new ResolverCache(mockedOpenAPI, null, null);
+
+		ExternalRefProcessor processor = new ExternalRefProcessor(mockedResolverCache, mockedOpenAPI);
+
+		processor.processRefToExternalSchema("./relative-with-url/relative-with-url.yaml#/relative-with-url", refFormat);
+		assertThat(((Schema) mockedOpenAPI.getComponents().getSchemas().get("relative-with-url").getProperties().get("Foo")).get$ref(),
+				is("https://my.example.remote.url.com/globals.yaml#/components/schemas/link-object")
+		);
+		assertThat(mockedOpenAPI.getComponents().getSchemas().keySet().contains("link-object"), is(true));
+		assertThat(mockedOpenAPI.getComponents().getSchemas().keySet().contains("rel-data"), is(true));
+		// assert that ref is relative ref is resolved. and the file path is from root yaml file.
+		assertThat(((Schema) mockedOpenAPI.getComponents().getSchemas().get("relative-with-url").getProperties().get("Bar")).get$ref(),
+        is("./relative-with-url/relative-with-local.yaml#/relative-same-file")
+    );
+	}
 }
diff --git a/modules/swagger-parser-v3/src/test/resources/relative-with-url/relative-with-local.yaml b/modules/swagger-parser-v3/src/test/resources/relative-with-url/relative-with-local.yaml
new file mode 100644
index 00000000..b30a1629
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/relative-with-url/relative-with-local.yaml
@@ -0,0 +1,4 @@
+relative-same-file:
+  properties:
+    data:
+      type: object
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/relative-with-url/relative-with-url.yaml b/modules/swagger-parser-v3/src/test/resources/relative-with-url/relative-with-url.yaml
new file mode 100644
index 00000000..4aa9562b
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/relative-with-url/relative-with-url.yaml
@@ -0,0 +1,7 @@
+relative-with-url:
+  properties:
+    Foo:
+      $ref: https://my.example.remote.url.com/globals.yaml#/components/schemas/link-object
+    Bar:
+      $ref: relative-with-local.yaml#/relative-same-file
+