Repository: swagger-api/swagger-parser
Pull request #1843 (2022-10-27 20:39:50)

fix internal references validation for nested schemas


Patch:
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 07ad4fd5..e5ae493f 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
@@ -332,6 +332,10 @@ public class OpenAPIDeserializer {
 				openAPI.setComponents(components);
 				this.components = components;
                 if(result.validateInternalRefs) {
+                    /* TODO currently only capable of validating if ref is to root schema withing #/components/schemas
+                     * need to evaluate json pointer instead to also allow validation of nested schemas
+                     * e.g. #/components/schemas/foo/properties/bar
+                     */
                     for (String schema : localSchemaRefs.keySet()) {
                         if (components.getSchemas().get(schema) == null) {
                             result.invalidType(localSchemaRefs.get(schema), schema, "schema", rootNode);
@@ -2763,7 +2767,11 @@ public class OpenAPIDeserializer {
 				} else {
 					schema.set$ref(ref.asText());
 				}
-                if(schema.get$ref().startsWith("#/components/schemas")){// it's internal
+                /* TODO currently only capable of validating if ref is to root schema withing #/components/schemas
+                 * need to evaluate json pointer instead to also allow validation of nested schemas
+                 * e.g. #/components/schemas/foo/properties/bar
+                 */
+                if(schema.get$ref().startsWith("#/components/schemas") && StringUtils.countMatches(schema.get$ref(), "/") == 3){
                     String refName  = schema.get$ref().substring(schema.get$ref().lastIndexOf("/")+1);
                     localSchemaRefs.put(refName,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 148bfe66..35dbf99d 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
@@ -3362,4 +3362,12 @@ public class OpenAPIV3ParserTest {
         Assert.assertNotNull(openAPI);
         assertEquals(Yaml.pretty(openAPI), yamlStringResolved);
     }
+
+    @Test
+    public void testInternalRefsValidation() throws Exception {
+        String yamlString = FileUtils.readFileToString(new File("src/test/resources/internal-refs.yaml"), "UTF-8");
+        ParseOptions options = new ParseOptions();
+        SwaggerParseResult parseResult = new OpenAPIV3Parser().readContents(yamlString, null, options);
+        assertEquals(parseResult.getMessages().size(), 1);
+    }
 }
diff --git a/modules/swagger-parser-v3/src/test/resources/internal-refs.yaml b/modules/swagger-parser-v3/src/test/resources/internal-refs.yaml
new file mode 100644
index 00000000..9b97b5f7
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/internal-refs.yaml
@@ -0,0 +1,76 @@
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: test
+paths: {}
+components: 
+  schemas:    
+    MemberRef:
+      description: Provides basic ID info about a member with an href to the full object
+      type: object
+      properties:
+        uuid:
+          description: "Uniquely identifies a member across the entire platform"
+          type: string
+          format: uuid
+          minLength: 36
+          maxLength: 36
+          example: "26a0bac8-5c37-4c54-b3b1-1ad4551db061"
+        alt_id:
+          description: "An ID defined for a member, typically provided by a customer during integration, which will be unique within a given group."
+          type: string
+          minLength: 1
+          maxLength: 36
+          example: "ABC-123"
+        group:
+          $ref: "#/components/schemas/MemberGroup"
+        href:
+          type: string
+          format: uri
+          readOnly: true
+      anyOf:
+      - required: ["uuid"]
+        properties:
+          uuid:
+            $ref: "#/components/schemas/MemberRef/properties/uuid"
+          alt_id:
+            $ref: "#/components/schemas/MemberRef/properties/alt_id"
+          group:
+            $ref: "#/components/schemas/MemberRef/properties/group"
+          href:
+            $ref: "#/components/schemas/MemberRef/properties/href"
+      - required: ["alt_id", "group"]
+        properties:
+          uuid:
+            $ref: "#/components/schemas/MemberRef/properties/uuid"
+          alt_id:
+            $ref: "#/components/schemas/MemberRef/properties/alt_id"
+          group:
+            $ref: "#/components/schemas/MemberRef/properties/group"
+          href:
+            $ref: "#/components/schemas/MemberRef/properties/href"
+    MemberSmall:
+      description: >-
+        Provides abbreviated information about a member and is used when returning an array of members
+      readOnly: true
+      properties:
+        uuid:
+          $ref: "#/components/schemas/MemberRef/properties/uuid"
+        alt_id:
+          $ref: "#/components/schemas/MemberRef/properties/alt_id"
+        group:
+          $ref: "#/components/schemas/MemberRef/properties/group"
+        href:
+          $ref: "#/components/schemas/MemberRef/properties/href"
+        activated:
+          description: >-
+            Indicates that the member has logged into their account
+          type: boolean
+          example: true
+        disabled:
+          description: >-
+            Indicates that the member's account is disabled and they will no longer be able to login
+          type: boolean
+          example: false
+      
+       
\ No newline at end of file