Repository: swagger-api/swagger-parser
Pull request #1705 (2022-04-08 18:10:48)

Fix and test for issue 1643 - Parser does not validate an undefined ref property
Parser does not validate an undefined ref property
refs #1643


Patch:
diff --git a/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java b/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
index 9b562c13..b8cfacdb 100644
--- a/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
+++ b/modules/swagger-parser-core/src/main/java/io/swagger/v3/parser/core/models/ParseOptions.java
@@ -10,6 +10,7 @@ public class ParseOptions {
     private boolean skipMatches;
     private boolean allowEmptyStrings = true;
     private boolean validateExternalRefs = false;
+    private boolean validateInternalRefs = false;
     private boolean legacyYamlDeserialization = false;
 
     public boolean isResolve() {
@@ -89,4 +90,12 @@ public class ParseOptions {
     public void setLegacyYamlDeserialization(boolean legacyYamlDeserialization) {
         this.legacyYamlDeserialization = legacyYamlDeserialization;
     }
+
+    public void setValidateInternalRefs(boolean validateInternalRefs) {
+        this.validateInternalRefs = validateInternalRefs;
+    }
+
+    public boolean isValidateInternalRefs() {
+        return validateInternalRefs;
+    }
 }
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java
index 74f6fa43..b15ab812 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java
@@ -139,8 +139,9 @@ public class OpenAPIDeserializer {
 	private static final String REFERENCE_SEPARATOR = "#/";
 	private Components components;
 	private final Set<String> operationIDs = new HashSet<>();
+    private Map<String,String> localSchemaRefs = new HashMap<>();
 
-	public SwaggerParseResult deserialize(JsonNode rootNode) {
+    public SwaggerParseResult deserialize(JsonNode rootNode) {
 		return deserialize(rootNode, null);
 	}
 
@@ -152,6 +153,7 @@ public class OpenAPIDeserializer {
 		SwaggerParseResult result = new SwaggerParseResult();
 		ParseResult rootParse = new ParseResult();
 		rootParse.setAllowEmptyStrings(options.isAllowEmptyString());
+        rootParse.setValidateInternalRefs(options.isValidateInternalRefs());
 		OpenAPI api = parseRoot(rootNode, rootParse, path);
 		result.setOpenAPI(api);
 		result.setMessages(rootParse.getMessages());
@@ -184,6 +186,13 @@ public class OpenAPIDeserializer {
 				Components components = getComponents(obj, "components", result);
 				openAPI.setComponents(components);
 				this.components = components;
+                if(result.validateInternalRefs) {
+                    for (String schema : localSchemaRefs.keySet()) {
+                        if (components.getSchemas().get(schema) == null) {
+                            result.invalidType(localSchemaRefs.get(schema), schema, "schema", rootNode);
+                        }
+                    }
+                }
 			}
 
 			obj = getObject("paths", rootNode, true, location, result);
@@ -2292,6 +2301,10 @@ public class OpenAPIDeserializer {
 				} else {
 					schema.set$ref(ref.asText());
 				}
+                if(schema.get$ref().startsWith("#/components/schemas")){// it's internal
+                    String refName  = schema.get$ref().substring(schema.get$ref().lastIndexOf("/")+1);
+                    localSchemaRefs.put(refName,location);
+                }
 				return schema;
 			} else {
 				result.invalidType(location, "$ref", "string", node);
@@ -3185,6 +3198,7 @@ public class OpenAPIDeserializer {
 		private List<Location> unique = new ArrayList<>();
 		private List<Location> uniqueTags = new ArrayList<>();
 		private boolean allowEmptyStrings = true;
+        private boolean validateInternalRefs;
 
 		public ParseResult() {
 		}
@@ -3279,7 +3293,15 @@ public class OpenAPIDeserializer {
 			}
 			return messages;
 		}
-	}
+
+        public void setValidateInternalRefs(boolean validateInternalRefs) {
+            this.validateInternalRefs = validateInternalRefs;
+        }
+
+        public boolean isValidateInternalRefs() {
+            return validateInternalRefs;
+        }
+    }
 
 
 	protected static class Location {


Test patch:
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 d2a26894..0609ea82 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
@@ -85,6 +85,93 @@ public class OpenAPIV3ParserTest {
     protected int serverPort = getDynamicPort();
     protected WireMockServer wireMockServer;
 
+    @Test
+    public void testIssue1643_True() throws Exception{
+        ParseOptions options = new ParseOptions();
+        options.setValidateInternalRefs(true);
+        String issue1643 = "openapi: \"3.0.0\"\n" +
+                "info:\n" +
+                "  version: 1.0.0\n" +
+                "  title: People\n" +
+                "paths:\n" +
+                "  /person:\n" +
+                "    get:\n" +
+                "      operationId: getPerson\n" +
+                "      parameters:\n" +
+                "        - name: name\n" +
+                "          in: query\n" +
+                "          required: true\n" +
+                "          schema:\n" +
+                "            type: string\n" +
+                "      responses:\n" +
+                "        '200':\n" +
+                "          description: The person with the given name.          \n" +
+                "          content:\n" +
+                "            application/json:\n" +
+                "              schema:\n" +
+                "                $ref: '#/components/schemas/Person'\n" +
+                "components:\n" +
+                "  schemas:\n" +
+                "    Person:\n" +
+                "      required:\n" +
+                "      - name\n" +
+                "      type: object\n" +
+                "      properties:\n" +
+                "        name:\n" +
+                "          type: string\n" +
+                "        employee:\n" +
+                "          $ref: '#/components/schemas/Employee'";
+        SwaggerParseResult result = new OpenAPIV3Parser().readContents(issue1643, null, options);
+
+        Assert.assertNotNull(result);
+        Assert.assertNotNull(result.getOpenAPI());
+        assertEquals(result.getMessages().size(),1);
+        assertTrue(result.getMessages().contains("attribute components.schemas.Person.Employee is not of type `schema`"));
+    }
+
+    @Test
+    public void testIssue1643_False() throws Exception{
+        ParseOptions options = new ParseOptions();
+        String issue1643 = "openapi: \"3.0.0\"\n" +
+                "info:\n" +
+                "  version: 1.0.0\n" +
+                "  title: People\n" +
+                "paths:\n" +
+                "  /person:\n" +
+                "    get:\n" +
+                "      operationId: getPerson\n" +
+                "      parameters:\n" +
+                "        - name: name\n" +
+                "          in: query\n" +
+                "          required: true\n" +
+                "          schema:\n" +
+                "            type: string\n" +
+                "      responses:\n" +
+                "        '200':\n" +
+                "          description: The person with the given name.          \n" +
+                "          content:\n" +
+                "            application/json:\n" +
+                "              schema:\n" +
+                "                $ref: '#/components/schemas/Person'\n" +
+                "components:\n" +
+                "  schemas:\n" +
+                "    Person:\n" +
+                "      required:\n" +
+                "      - name\n" +
+                "      type: object\n" +
+                "      properties:\n" +
+                "        name:\n" +
+                "          type: string\n" +
+                "        employee:\n" +
+                "          $ref: '#/components/schemas/Employee'";
+        SwaggerParseResult result = new OpenAPIV3Parser().readContents(issue1643, null, options);
+
+        Assert.assertNotNull(result);
+        Assert.assertNotNull(result.getOpenAPI());
+        assertEquals(result.getMessages().size(),0);
+        assertFalse(result.getMessages().contains("attribute components.schemas.Person.Employee is not of type `schema`"));
+    }
+
     @Test
     public void testExampleFormatByte() throws Exception{