Repository: swagger-api/swagger-parser
Pull request #1343 (2020-03-20 01:47:08)

Fixing remote paths, response schema resolution - Issue 1292 
Fixing remote paths, response schema resolution - Issue #1292 


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 02d63f1e..617f6bea 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
@@ -3,6 +3,8 @@ package io.swagger.v3.parser.processors;
 
 import io.swagger.v3.oas.models.Components;
 import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
 import io.swagger.v3.oas.models.callbacks.Callback;
 import io.swagger.v3.oas.models.examples.Example;
 import io.swagger.v3.oas.models.headers.Header;
@@ -27,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.slf4j.LoggerFactory;
 
 import java.net.URI;
+import java.nio.file.Path;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -83,13 +86,15 @@ public final class ExternalRefProcessor {
                 // use the new model
                 existingModel = null;
             }else{
-                //We add a number at the end of the definition name
-                int i = 2;
-                for (String name : schemas.keySet()) {
-                    if (name.equals(possiblyConflictingDefinitionName)) {
-                        tryName = possiblyConflictingDefinitionName + "_" + i;
-                        existingModel = schemas.get(tryName);
-                        i++;
+                if (!schema.equals(existingModel)){
+                    //We add a number at the end of the definition name
+                    int i = 2;
+                    for (String name : schemas.keySet()) {
+                        if (name.equals(possiblyConflictingDefinitionName)) {
+                            tryName = possiblyConflictingDefinitionName + "_" + i;
+                            existingModel = schemas.get(tryName);
+                            i++;
+                        }
                     }
                 }
             }
@@ -116,6 +121,7 @@ public final class ExternalRefProcessor {
                 }
             }
 
+
             if(schema instanceof ComposedSchema){
                 ComposedSchema composedSchema = (ComposedSchema) schema;
                 if (composedSchema.getAllOf() != null){
@@ -226,6 +232,84 @@ public final class ExternalRefProcessor {
         }
     }
 
+
+    public PathItem processRefToExternalPathItem(String $ref, RefFormat refFormat) {
+
+        final PathItem pathItem = cache.loadRef($ref, refFormat, PathItem.class);
+
+        String newRef;
+
+        Map<String, PathItem> paths = openAPI.getPaths();
+
+        if (paths == null) {
+            paths = new LinkedHashMap<>();
+        }
+
+        final String possiblyConflictingDefinitionName = computeDefinitionName($ref);
+
+        PathItem existingPathItem = paths.get(possiblyConflictingDefinitionName);
+
+        if (existingPathItem != null) {
+            LOGGER.debug("A model for " + existingPathItem + " already exists");
+            if(existingPathItem.get$ref() != null) {
+                // use the new model
+                existingPathItem = null;
+            }
+        }
+        newRef = possiblyConflictingDefinitionName;
+        cache.putRenamedRef($ref, newRef);
+
+        if(pathItem != null) {
+            if(pathItem.readOperationsMap() != null) {
+                final Map<PathItem.HttpMethod, Operation> operationMap = pathItem.readOperationsMap();
+                for (PathItem.HttpMethod httpMethod : operationMap.keySet()) {
+                    Operation operation = operationMap.get(httpMethod);
+                    if (operation.getResponses() != null) {
+                        final Map<String, ApiResponse> responses = operation.getResponses();
+                        if (responses != null) {
+                            for (String responseCode : responses.keySet()) {
+                                ApiResponse response = responses.get(responseCode);
+                                if (response != null) {
+                                    Schema schema = null;
+                                    if (response.getContent() != null) {
+                                        Map<String, MediaType> content = response.getContent();
+                                        for (String mediaName : content.keySet()) {
+                                            MediaType mediaType = content.get(mediaName);
+                                            if (mediaType.getSchema() != null) {
+                                                schema = mediaType.getSchema();
+                                                if (schema != null) {
+                                                    processRefSchemaObject(mediaType.getSchema(), $ref);
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    if (operation.getRequestBody() != null) {
+                        RequestBody body = operation.getRequestBody();
+                        Schema schema = null;
+                        if (body.getContent() != null) {
+                            Map<String, MediaType> content = body.getContent();
+                            for (String mediaName : content.keySet()) {
+                                MediaType mediaType = content.get(mediaName);
+                                if (mediaType.getSchema() != null) {
+                                    schema = mediaType.getSchema();
+                                    if (schema != null) {
+                                        processRefSchemaObject(mediaType.getSchema(), $ref);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return pathItem;
+    }
+
     private void processDiscriminator(Discriminator d, String file) {
         if (d != null && d.getMapping() != null) {
             processDiscriminatorMapping(d.getMapping(), file);
@@ -239,6 +323,7 @@ public final class ExternalRefProcessor {
             processSchema(subtype, file);
             mapping.put(key, subtype.get$ref());
         }
+
     }
 
     public String processRefToExternalResponse(String $ref, RefFormat refFormat) {
@@ -716,6 +801,7 @@ public final class ExternalRefProcessor {
         return newRef;
     }
 
+
     private void processRefContent(Map<String, MediaType> content, String $ref) {
         for(MediaType mediaType : content.values()) {
             if(mediaType.getSchema() != null) {
@@ -802,6 +888,8 @@ public final class ExternalRefProcessor {
         }
     }
 
+
+
     private void processRefSchema(Schema subRef, String externalFile) {
         RefFormat format = computeRefFormat(subRef.get$ref());
 
@@ -897,4 +985,5 @@ public final class ExternalRefProcessor {
     }
 
 
+
 }
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/PathsProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/PathsProcessor.java
index 8db11597..880837b0 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/PathsProcessor.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/PathsProcessor.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Map;
 
 import static io.swagger.v3.parser.util.RefUtils.computeRefFormat;
+import static io.swagger.v3.parser.util.RefUtils.isAnExternalRefFormat;
 
 public class PathsProcessor {
 
@@ -31,6 +32,7 @@ public class PathsProcessor {
     private final OpenAPIResolver.Settings settings;
     private final ParameterProcessor parameterProcessor;
     private final OperationProcessor operationProcessor;
+    private final ExternalRefProcessor externalRefProcessor;
 
     public PathsProcessor(ResolverCache cache, OpenAPI openAPI) {
         this(cache, openAPI, new OpenAPIResolver.Settings());
@@ -41,6 +43,7 @@ public class PathsProcessor {
         this.settings = settings;
         parameterProcessor = new ParameterProcessor(cache, openAPI);
         operationProcessor = new OperationProcessor(cache, openAPI);
+        this.externalRefProcessor = new ExternalRefProcessor(cache, openAPI);
     }
 
     public void processPaths() {
@@ -56,10 +59,8 @@ public class PathsProcessor {
             addParametersToEachOperation(pathItem);
 
             if (pathItem.get$ref() != null) {
-                RefFormat refFormat = computeRefFormat(pathItem.get$ref());
-                PathItem resolvedPath = cache.loadRef(pathItem.get$ref(), refFormat, PathItem.class);
 
-                // TODO: update references to the parent location
+                PathItem resolvedPath = processReferencePath(pathItem);
 
                 String pathRef = pathItem.get$ref().split("#")[0];
 
@@ -290,4 +291,16 @@ public class PathsProcessor {
     protected String computeLocalRef(String ref, String prefix) {
         return prefix + ref;
     }
+
+    public PathItem processReferencePath(PathItem pathItem){
+        RefFormat refFormat = computeRefFormat(pathItem.get$ref());
+        String $ref = pathItem.get$ref();
+        if (isAnExternalRefFormat(refFormat)){
+              pathItem = externalRefProcessor.processRefToExternalPathItem($ref, refFormat);
+        }else{
+             pathItem = cache.loadRef(pathItem.get$ref(), refFormat, PathItem.class);
+        }
+
+        return pathItem;
+    }
 }


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/FileReferenceTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/FileReferenceTest.java
index 1a816b72..b07f3986 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/FileReferenceTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/FileReferenceTest.java
@@ -2,6 +2,7 @@ package io.swagger.v3.parser.test;
 
 
 
+import io.swagger.v3.core.util.Yaml;
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.Operation;
 import io.swagger.v3.oas.models.PathItem;
@@ -78,6 +79,7 @@ public class FileReferenceTest {
 
         Operation get = path.getGet();
         assertEquals(get.getOperationId(), "getEvents");
+        Yaml.prettyPrint(swagger);
         assertTrue(swagger.getComponents().getSchemas().size() == 2);
         assertTrue(swagger.getComponents().getSchemas().get("Paging").getProperties().size() == 1);
     }
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
index 63a746d2..2599092e 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIResolverTest.java
@@ -232,7 +232,7 @@ public class OpenAPIResolverTest {
 
         //remote url schema
         Schema user = (Schema) pet.getProperties().get("user");
-        assertEquals(user.get$ref(),"#/components/schemas/User_2");
+        assertEquals(user.get$ref(),"#/components/schemas/User");
 
 
         //ArraySchema items
@@ -247,7 +247,7 @@ public class OpenAPIResolverTest {
         //Schema additionalProperties
         assertTrue(schemas.get("OrderRef").getAdditionalProperties() instanceof Schema);
         Schema additionalProperties = (Schema) schemas.get("OrderRef").getAdditionalProperties();
-        assertEquals(additionalProperties.get$ref(), "#/components/schemas/User_2");
+        assertEquals(additionalProperties.get$ref(), "#/components/schemas/User");
 
         //AllOfSchema
         ComposedSchema extended = (ComposedSchema) schemas.get("ExtendedErrorModel");
@@ -307,7 +307,7 @@ public class OpenAPIResolverTest {
         //internal Schema header
         Map<String, Header> headers = openAPI.getComponents().getHeaders();
         //header remote schema ref
-        assertEquals(headers.get("X-Rate-Limit-Remaining").getSchema().get$ref(),"#/components/schemas/User_2");
+        assertEquals(headers.get("X-Rate-Limit-Remaining").getSchema().get$ref(),"#/components/schemas/User");
 
         //header examples
         assertEquals(headers.get("X-Rate-Limit-Reset").getExamples().get("headerExample").get$ref(), "#/components/examples/dog" );
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java
index 8cae3650..f4ca4e38 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java
@@ -91,6 +91,23 @@ public class OpenAPIV3ParserTest {
     }
 
 
+    @Test
+    public void testIssue_1292() {
+        OpenAPIV3Parser openApiParser = new OpenAPIV3Parser();
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+
+        SwaggerParseResult parseResult = openApiParser.readLocation("issue-1292/petstore.yml", null, options);
+
+        OpenAPI openAPI = parseResult.getOpenAPI();
+
+        assertNotNull(openAPI.getPaths().get("/pets").getGet().getResponses().get("200").getContent().get("application/json").getSchema().get$ref(), "#/components/schemas/Pets");
+        assertNotNull(openAPI.getPaths().get("/pets").getGet().getResponses().getDefault().getContent().get("application/json").getSchema().get$ref(), "#/components/schemas/Error");
+        assertNotNull(openAPI.getComponents().getSchemas().get("Pet"));
+        assertNotNull(openAPI.getComponents().getSchemas().get("Pets"));
+        assertNotNull(openAPI.getComponents().getSchemas().get("Error"));
+    }
+
     @Test
     public void testFlattenComposedSchema() {
         OpenAPIV3Parser openApiParser = new OpenAPIV3Parser();
@@ -1089,9 +1106,9 @@ public class OpenAPIV3ParserTest {
         assertTrue(definitions.containsKey("x"));
         assertTrue(definitions.containsKey("y"));
         assertTrue(definitions.containsKey("z"));
-        assertEquals( definitions.get("i").get$ref(),"#/components/schemas/k_2");
+        assertEquals( definitions.get("i").get$ref(),"#/components/schemas/k");
         assertEquals( definitions.get("k").getTitle(), "k-definition");
-        assertEquals( definitions.get("k_2").getTitle(), "k-definition");
+
     }
 
     @Test
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1292/pets/def.yml b/modules/swagger-parser-v3/src/test/resources/issue-1292/pets/def.yml
new file mode 100644
index 00000000..e04b2e74
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1292/pets/def.yml
@@ -0,0 +1,30 @@
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: "#/components/schemas/Pet"
+    Error:
+      type: object
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1292/pets/pets.yml b/modules/swagger-parser-v3/src/test/resources/issue-1292/pets/pets.yml
new file mode 100644
index 00000000..b7c1e600
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1292/pets/pets.yml
@@ -0,0 +1,45 @@
+get:
+  summary: List all pets
+  operationId: listPets
+  tags:
+    - pets
+  parameters:
+    - name: limit
+      in: query
+      description: How many items to return at one time (max 100)
+      required: false
+      schema:
+        type: integer
+        format: int32
+  responses:
+    "200":
+      description: A paged array of pets
+      headers:
+        x-next:
+          description: A link to the next page of responses
+          schema:
+            type: string
+      content:
+        application/json:
+          schema:
+            $ref: "./def.yml#/components/schemas/Pets"
+    default:
+      description: unexpected error
+      content:
+        application/json:
+          schema:
+            $ref: "./def.yml#/components/schemas/Error"
+post:
+  summary: Create a pet
+  operationId: createPets
+  tags:
+    - pets
+  responses:
+    "201":
+      description: Null response
+    default:
+      description: unexpected error
+      content:
+        application/json:
+          schema:
+            $ref: "./def.yml#/components/schemas/Error"
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1292/petstore.yml b/modules/swagger-parser-v3/src/test/resources/issue-1292/petstore.yml
new file mode 100644
index 00000000..873f4b9b
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1292/petstore.yml
@@ -0,0 +1,11 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    $ref: "./pets/pets.yml"
diff --git a/modules/swagger-parser-v3/src/test/resources/nested-file-references/events.yaml b/modules/swagger-parser-v3/src/test/resources/nested-file-references/events.yaml
index 36013050..a88bb2d4 100644
--- a/modules/swagger-parser-v3/src/test/resources/nested-file-references/events.yaml
+++ b/modules/swagger-parser-v3/src/test/resources/nested-file-references/events.yaml
@@ -15,4 +15,4 @@ get:
               paging:
                 $ref: './common/paging.yaml#/Paging'
               items:
-                $ref: '#/components/schemas/StatusResponse'
+                $ref: './issue-312.yaml#/components/schemas/StatusResponse'
diff --git a/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-312.yaml b/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-312.yaml
index 73ba201b..a44c3e3f 100644
--- a/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-312.yaml
+++ b/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-312.yaml
@@ -13,4 +13,4 @@ components:
     StatusResponse:
       properties:
         http_code:
-          type: integer
+          type: integer
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-314.yaml b/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-314.yaml
index b877e9a8..bc174f62 100644
--- a/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-314.yaml
+++ b/modules/swagger-parser-v3/src/test/resources/nested-file-references/issue-314.yaml
@@ -12,4 +12,4 @@ components:
     StatusResponse:
       properties:
         http_code:
-          type: integer
+          type: integer
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3.yaml.template b/modules/swagger-parser-v3/src/test/resources/oas3.yaml.template
index 01b9c2bc..b88d04d4 100644
--- a/modules/swagger-parser-v3/src/test/resources/oas3.yaml.template
+++ b/modules/swagger-parser-v3/src/test/resources/oas3.yaml.template
@@ -64,6 +64,8 @@ tags:
     description: Find out more about our store
     url: http://swagger.io
 paths:
+  "/pathItemRef2":
+    "$ref": '#/paths/~1pet'
   "/mockResponses/objectMultipleExamples":
     get:
       responses:
@@ -96,8 +98,6 @@ paths:
                  $ref: '#/components/examples/Trillian'
   "/pathItemRef":
     "$ref": 'http://localhost:${dynamicPort}/remote/path#/paths/~1pet'
-  "/pathItemRef2":
-      "$ref": '#/paths/~1pet'
   "/pet":
     summary: summary
     description: description