Repository: swagger-api/swagger-parser
Pull request #1566 (2021-05-27 18:01:42)

Fix for 1518 with no SOF
Fix for #1518 
This PR addresses the duplicated schemas resolution that comes from differents urls, but are equals in their content.


Patch:
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java
index a5d2d938..e40db066 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java
@@ -162,7 +162,6 @@ public class ResolverCache {
         }
 
         updateLocalRefs(file, result);
-
         resolutionCache.put(ref, result);
 
         return result;
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ComponentsProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ComponentsProcessor.java
index 3d9ac09c..6e7ba3c0 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ComponentsProcessor.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/ComponentsProcessor.java
@@ -244,5 +244,18 @@ public class ComponentsProcessor {
                 }
             }
         }
+        //process schemas again to check properties that hasn't been solved
+        for (String modelName : schemaKeys) {
+            final Schema model = schemas.get(modelName);
+            Map<String, Schema> properties = model.getProperties();
+            if (properties != null) {
+                for (Map.Entry<String, Schema> propertyEntry : properties.entrySet()) {
+                    Schema property = propertyEntry.getValue();
+                    if(property.get$ref() != null) {
+                        schemaProcessor.processSchema(model);
+                    }
+                }
+            }
+        }
     }
 }
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 afab0228..ec0f4d50 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
@@ -49,7 +49,7 @@ public final class ExternalRefProcessor {
         this.openAPI = openAPI;
     }
 
-    private String finalNameRec(Map<String, Schema> schemas, String possiblyConflictingDefinitionName, Schema newScema,
+    private String finalNameRec(Map<String, Schema> schemas, String possiblyConflictingDefinitionName, Schema newSchema,
         int iteration) {
         String tryName =
             iteration == 0 ? possiblyConflictingDefinitionName : possiblyConflictingDefinitionName + "_" + iteration;
@@ -58,9 +58,23 @@ public final class ExternalRefProcessor {
             if (existingModel.get$ref() != null) {
                 // use the new model
                 existingModel = null;
-            } else if (!newScema.equals(existingModel)) {
+            } else if (!newSchema.equals(existingModel)) {
+                if(cache.getResolutionCache().get(newSchema.get$ref())!= null){
+                    return tryName;
+                }
                 LOGGER.debug("A model for " + existingModel + " already exists");
-                return finalNameRec(schemas, possiblyConflictingDefinitionName, newScema, ++iteration);
+                return finalNameRec(schemas, possiblyConflictingDefinitionName, newSchema, ++iteration);
+            }
+        }else{
+            // validate the name
+            if(existingModel == null){
+                for(String name: schemas.keySet()){
+                    if(name.toLowerCase().equals(tryName.toLowerCase())){
+                        existingModel = schemas.get(name);
+                        tryName = name;
+                        break;
+                    }
+                }
             }
         }
         return tryName;
@@ -95,7 +109,7 @@ public final class ExternalRefProcessor {
         newRef = finalNameRec(schemas, possiblyConflictingDefinitionName, schema, 0);
         cache.putRenamedRef($ref, newRef);
         Schema existingModel = schemas.get(newRef);
-        if(existingModel != null && existingModel.get$ref() != null) {
+       if(existingModel != null && existingModel.get$ref() != null) {
             // use the new model
             existingModel = null;
         }
@@ -192,7 +206,6 @@ public final class ExternalRefProcessor {
                 }
             }
         }
-
         return newRef;
     }
 
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/SchemaProcessor.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/SchemaProcessor.java
index acd1b408..1ed583a2 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/SchemaProcessor.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/processors/SchemaProcessor.java
@@ -116,7 +116,6 @@ public class SchemaProcessor {
                  }else {
                      processSchemaType(property);
                  }
-
              }
          }
     }


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ComponentsProcessorTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ComponentsProcessorTest.java
index 390a6446..5b5405fa 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ComponentsProcessorTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/processors/ComponentsProcessorTest.java
@@ -188,6 +188,10 @@ public class ComponentsProcessorTest {
 
             schemaProcessor.processSchema(refModel);
             times = 1;
+
+            resolvedModel.getProperties();
+            times = 1;
+
         }};
 
         new ComponentsProcessor(openAPI, mockResolverCache).processComponents();
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 c54cbb7f..6d4b5d9d 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
@@ -83,6 +83,30 @@ public class OpenAPIV3ParserTest {
     protected int serverPort = getDynamicPort();
     protected WireMockServer wireMockServer;
 
+    @Test
+    public void testIssue1518() {
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        SwaggerParseResult result = new OpenAPIV3Parser().readLocation("issue-1518/api.json", null, options);
+        OpenAPI openAPI = result.getOpenAPI();
+        assertTrue(((Schema)openAPI.getComponents().getSchemas().get("Analemmata").getProperties().get("tashotSipe")).get$ref().equals("#/components/schemas/TashotSipe"));
+        assertTrue(openAPI.getComponents().getSchemas().get("analemmata") == null);
+    }
+
+    @Test
+    public void testIssue1518StackOverFlow() {
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        try {
+            SwaggerParseResult result = new OpenAPIV3Parser().readLocation("example0/api.json", null, options);
+            Yaml.prettyPrint(result.getOpenAPI().getComponents().getSchemas());
+        }catch (StackOverflowError stackOverflowError){
+            assertTrue(false);
+        }
+
+
+    }
+
     @Test
     public void testIssue251() throws IOException {
         String pathFile = FileUtils.readFileToString(new File("src/test/resources/domain.yaml"), "UTF-8");
diff --git a/modules/swagger-parser-v3/src/test/resources/example0/api.json b/modules/swagger-parser-v3/src/test/resources/example0/api.json
new file mode 100644
index 00000000..64f68282
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/example0/api.json
@@ -0,0 +1,87 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "version": "v3.1.1",
+    "title": "Foo API",
+    "x-serviceName": "foo",
+    "description": "Blah blah blah",
+    "contact": {
+      "name": "Foo Team",
+      "email": "bar@foo.com"
+    }
+  },
+  "servers": [
+    {
+      "url": "http://localhost:8080"
+    }
+  ],
+  "paths": {
+    "/v4/foo": {
+      "get": {
+        "description": "Blah blah blah",
+        "operationId": "getFoos",
+        "tags": [
+          "Foo"
+        ],
+        "parameters": [
+          {
+            "name": "my_id",
+            "description": "Blah blah blah",
+            "in": "query",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "Blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Combination"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "Blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "Combination": {
+        "$ref": "./models/combination.json"
+      },
+      "Combinations": {
+        "$ref": "./models/combinations.json"
+      },
+      "Raygun": {
+        "$ref": "./models/target/raygun.json"
+      },
+      "Dingy": {
+        "$ref": "./models/target/dingy.json"
+      },
+      "Headlight": {
+        "$ref": "./models/target/headlight.json"
+      },
+      "Stamp": {
+        "$ref": "./models/target/stamp.json"
+      },
+      "Stripe": {
+        "$ref": "./models/target/stripe.json"
+      }
+    }
+  }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/example0/config.json b/modules/swagger-parser-v3/src/test/resources/example0/config.json
new file mode 100644
index 00000000..6cc8d39a
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/example0/config.json
@@ -0,0 +1,3 @@
+{
+  "artifactVersion": "1.1.1"
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/example0/models/combination.json b/modules/swagger-parser-v3/src/test/resources/example0/models/combination.json
new file mode 100644
index 00000000..e151688e
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/example0/models/combination.json
@@ -0,0 +1,50 @@
+{
+  "type": "object",
+  "description": "Blah blah blah",
+  "properties": {
+    "file": {
+      "$ref": "#/components/schemas/Link",
+      "description": "Blah blah blah",
+      "readOnly": true
+    },
+    "fileVersion": {
+      "$ref": "#/components/schemas/Link",
+      "description": "Blah blah blah"
+    },
+    "target": {
+      "oneOf": [
+        {
+          "$ref": "#/components/schemas/Raygun"
+        },
+        {
+          "$ref": "#/components/schemas/Dingy"
+        },
+        {
+          "$ref": "#/components/schemas/Headlight"
+        }
+      ],
+      "description": "Blah blah blah"
+    },
+    "createdBy": {
+      "$ref": "#/components/schemas/Link",
+      "description": "Blah blah blah",
+      "readOnly": true
+    }
+  },
+  "components": {
+    "schemas": {
+      "Link": {
+        "$ref": "./common/link.json"
+      },
+      "Raygun": {
+        "$ref": "./target/raygun.json"
+      },
+      "Dingy": {
+        "$ref": "./target/dingy.json"
+      },
+      "Headlight": {
+        "$ref": "./target/headlight.json"
+      }
+    }
+  }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/example0/models/combinations.json b/modules/swagger-parser-v3/src/test/resources/example0/models/combinations.json
new file mode 100644
index 00000000..923119ca
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/example0/models/combinations.json
@@ -0,0 +1,14 @@
+{
+  "type": "object",
+  "required": [
+    "data"
+  ],
+  "properties": {
+    "data": {
+      "type": "array",
+      "items": {
+        "$ref": "./combination.json"
+      }
+    }
+  }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/example0/models/common/error.json b/modules/swagger-parser-v3/src/test/resources/example0/models/common/error.json
new file mode 100644
index 00000000..094a8dd3
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/example0/models/common/error.json
@@ -0,0 +1,4 @@
+{
+  "type": "object",
+  "description": "Blah blah blah"
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/example0/models/common/link.json b/modules/swagger-parser-v3/src/test/resources/example0/models/common/link.json
new file mode 100644
index 00000000..03daf803
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/example0/models/common/link.json
@@ -0,0 +1,12 @@
+{
+  "properties": {
+    "type": {
+      "description": "Blah blah blah",
+      "type": "string"
+    },
+    "id": {
+      "description": "Blah blah blah",
+      "type": "string"
+    }
+  }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1518/api.json b/modules/swagger-parser-v3/src/test/resources/issue-1518/api.json
new file mode 100644
index 00000000..3d68d7e1
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1518/api.json
@@ -0,0 +1,373 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "version": "v3.1.1",
+    "title": "Foo/Foo1 API",
+    "x-serviceName": "duplicates",
+    "description": "blah blah blah",
+    "contact": {
+      "name": "Foo/Foo1 Team",
+      "email": "foo@foo1.com"
+    }
+  },
+  "servers": [
+    {
+      "url": "http://localhost:8080"
+    }
+  ],
+  "paths": {
+    "/v7/duplicates": {
+      "get": {
+        "description": "blah blah blah",
+        "operationId": "getAnalemmatas",
+        "tags": [
+          "Analemmatas"
+        ],
+        "parameters": [
+          {
+            "name": "dup_id",
+            "description": "blah blah blah",
+            "in": "query",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/AnacreonticPacedCoalheaver"
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          }
+        }
+      },
+      "post": {
+        "description": "blah blah blah",
+        "operationId": "createAnalemmata",
+        "tags": [
+          "Analemmata"
+        ],
+        "requestBody": {
+          "description": "blah blah blah",
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/Analemmata"
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Analemmata"
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/v7/duplicates/{id}": {
+      "get": {
+        "description": "blah blah blah",
+        "operationId": "getAnalemmataById",
+        "tags": [
+          "Analemmata"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "blah blah blah",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Analemmata"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          }
+        }
+      },
+      "patch": {
+        "description": "blah blah blah",
+        "operationId": "packsAnalemmataById",
+        "tags": [
+          "Analemmata"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "blah blah blah",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json-patch+json": {
+              "schema": {
+                "$ref": "#/components/schemas/PacksOpiniateds"
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Analemmata"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          }
+        }
+      },
+      "delete": {
+        "description": "blah blah blah",
+        "operationId": "deleteAnalemmataById",
+        "tags": [
+          "Analemmata"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "blah blah blah",
+            "required": true,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "204": {
+            "description": "blah blah blah"
+          },
+          "400": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "blah blah blah",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          },
+          "default": {
+            "description": "blah blah blah",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Essot"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "Analemmata": {
+        "$ref": "./models/analemmata.json"
+      },
+      "TashotSipe": {
+        "$ref": "./models/common/tashotSipe.json"
+      },
+      "Stunts": {
+        "$ref": "./models/common/stunts.json"
+      },
+      "Essot": {
+        "$ref": "./models/common/essot.json"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1518/models/analemmata.json b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/analemmata.json
new file mode 100644
index 00000000..4fd349f3
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/analemmata.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "description": "blah blah blah",
+  "properties": {
+    "tashotSipe": {
+      "$ref": "../api.json#/components/schemas/TashotSipe"
+    },
+    "stunts": {
+      "$ref": "../api.json#/components/schemas/Stunts"
+    }
+  }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/essot.json b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/essot.json
new file mode 100644
index 00000000..32ac3f34
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/essot.json
@@ -0,0 +1,11 @@
+{
+  "type": "object",
+  "description": "blah blah blah",
+  "properties": {
+    "statue": {
+      "type": "integer",
+      "format": "int32",
+      "description": "blah blah blah"
+    }
+  }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/stunts.json b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/stunts.json
new file mode 100644
index 00000000..aa8c4577
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/stunts.json
@@ -0,0 +1,7 @@
+{
+  "type": "string",
+  "enum": [
+    "XORK"
+  ],
+  "description": "blah blah blah"
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/tashotSipe.json b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/tashotSipe.json
new file mode 100644
index 00000000..6964363b
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/issue-1518/models/common/tashotSipe.json
@@ -0,0 +1,7 @@
+{
+  "type": "string",
+  "enum": [
+    "RAFTER"
+  ],
+  "description": "blah blah blah"
+}