Repository: swagger-api/swagger-parser
Pull request #2087 (2024-05-01 19:37:20)

Fix ref handling in CB, AnyOf & examples (fixes : #2086)
This PR is to address the $ref handling issues in callbacks and discriminator mapping objects as detailed in #2086.
It does it with the following changes 
- Extends discriminator mapping handling for `anyOf` like its done for `OneOf` in `SchemaProcessor` class.
- Add ref path resolution for examples of Parameters
- Handles ref path resolution for array items with `ComplexSchemas`
- Refactors the `processRefToExternalPathItem` in `ExternalRefProcessor` so that it can be used for $ref path resolution in PathItems used within callbacks.

Each done in dedicated commits, although there might be some spill over. 



anthochristen: @gracekarina I see that you have worked on the files in which most of this changes are done for this issue (#2086). Would be great if you can take a look at these changes.
anthochristen: Have a blanket IT type test for now, can add more in depth UTs once  I get some comments on these changes.
anthochristen: P.S: These issues are not present in the npm `swagger-parser` [lib](https://www.npmjs.com/package/swagger-parser).

anthochristen: @frantuma I've changed the oneOf and anyOf to be inclusive, kindly help take a look when possible, thanks!
anthochristen: @frantuma @gracekarina @walaniam Can anyone kindly help look at these changes?
emead1: Hi @frantuma @gracekarina @walaniam - I am the Product Manager for Java Frameworks at PayPal. This PR is currently blocking an enterprise-level initiative to upgrade to OAS 3.0. Below is the business case and impact if this PR is not merged in the coming weeks. Please let me know if there is a better channel to escalate this in to get better visibility. Thank you in advance - appreciate the partnership!

The code generation tool is crucial for developers to accurately validate specification and implement REST APIs at PayPal. This [PR](https://github.com/swagger-api/swagger-parser/pull/2087) fixes [critical bug](https://github.com/swagger-api/swagger-parser/issues/2086) that impact virtually all OAS3 REST API specifications using examples, parameter, discriminator and callback features, making the codegen tool incompatible in its current state. Without this fix, developers are forced with cumbersome workarounds to choose between restructure their API specifications that hinder reusability or generate java model classes on the resolved specification resulting significant disruption to existing API implementation. This issue not only impacts individual teams but also prevents the API Platform team from effectively testing and ensuring the platform meets customer needs. Ultimately, Failing to merge this [PR](https://github.com/swagger-api/swagger-parser/pull/2087) to address this critical issue could delay API deployments and hinder the overall adoption of the new platform.

APIReferenceTemplate Repo: https://github.paypal.com/ApiSpecifications-R/APIReferenceSpecification/tree/develop/v1/schema/components

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 0413f793..8f9c92c7 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
@@ -4,10 +4,15 @@ package io.swagger.v3.parser.processors;
 import java.net.URI;
 import java.nio.file.Paths;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import io.swagger.v3.oas.models.Components;
 import io.swagger.v3.oas.models.OpenAPI;
@@ -29,6 +34,7 @@ import io.swagger.v3.oas.models.security.SecurityScheme;
 import io.swagger.v3.parser.ResolverCache;
 import io.swagger.v3.parser.models.RefFormat;
 import io.swagger.v3.parser.models.RefType;
+
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.LoggerFactory;
@@ -246,9 +252,31 @@ public final class ExternalRefProcessor {
             }
             if (property instanceof ComposedSchema) {
                 ComposedSchema composed = (ComposedSchema) property;
+                final Map<String, String> refMap = Optional.ofNullable(composed.getDiscriminator())
+                                .map(Discriminator::getMapping).orElse(Collections.emptyMap()).entrySet()
+                                .stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
+                Map<String, Schema> refCache = (!refMap.isEmpty() &&
+                        (composed.getAnyOf() != null || composed.getOneOf() != null)) ? Stream.of(
+                                composed.getAnyOf(), composed.getOneOf()
+                        )
+                            .filter(Objects::nonNull)
+                            .filter(l -> !l.isEmpty())
+                            .flatMap(Collection::stream)
+                            .filter(s -> s.get$ref() != null)
+                            .collect(Collectors.toMap(Schema::get$ref, Function.identity())) : Collections.emptyMap();
+
                 processProperties(composed.getAllOf(), file);
                 processProperties(composed.getAnyOf(), file);
                 processProperties(composed.getOneOf(), file);
+
+                if (!refMap.isEmpty() && !refCache.isEmpty()) {
+                    refCache.entrySet()
+                            .stream().filter(e -> !e.getKey().equals(e.getValue().get$ref()))
+                            .forEach(entry -> {
+                                String newRef = entry.getValue().get$ref();
+                                property.getDiscriminator().getMapping().put(refMap.get(entry.getKey()), newRef);
+                            });
+                }
             }
         }
     }
@@ -298,71 +326,75 @@ public final class ExternalRefProcessor {
         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 (mediaType.getExamples() != null) {
-                                                    processRefExamples(mediaType.getExamples(), $ref);
-                                                }
+            processPathItem(pathItem, $ref);
+        }
+
+        return pathItem;
+    }
 
+    private void processPathItem(PathItem pathItem, String $ref) {
+        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 (mediaType.getExamples() != null) {
+                                                processRefExamples(mediaType.getExamples(), $ref);
                                             }
+
                                         }
                                     }
-                                    if (response.getLinks() != null) {
-                                        processRefLinks(response.getLinks(), $ref);
-                                    }
-                                    if (response.getHeaders() != null) {
-                                        processRefHeaders(response.getHeaders(), $ref);
-                                    }
+                                }
+                                if (response.getLinks() != null) {
+                                    processRefLinks(response.getLinks(), $ref);
+                                }
+                                if (response.getHeaders() != null) {
+                                    processRefHeaders(response.getHeaders(), $ref);
                                 }
                             }
                         }
                     }
-                    if (operation.getRequestBody() != null) {
-                        RequestBody body = operation.getRequestBody();
-                        if (body.getContent() != null) {
-                            Schema schema;
-                            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);
-                                    }
+                }
+                if (operation.getRequestBody() != null) {
+                    RequestBody body = operation.getRequestBody();
+                    if (body.getContent() != null) {
+                        Schema schema;
+                        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);
                                 }
                             }
                         }
                     }
+                }
 
-                    final List<Parameter> parameters = operation.getParameters();
-                    if (parameters != null) {
-                        parameters.stream()
-                            .filter(parameter -> parameter.getSchema() != null)
-                            .forEach(parameter -> this.processRefSchemaObject(parameter.getSchema(), $ref));
-                    }
+                final List<Parameter> parameters = operation.getParameters();
+                if (parameters != null) {
+                    parameters.stream()
+                        .filter(parameter -> parameter.getSchema() != null)
+                        .forEach(parameter -> this.processRefSchemaObject(parameter.getSchema(), $ref));
                 }
             }
         }
-
-        return pathItem;
     }
 
     private void processDiscriminator(Discriminator d, String file) {
@@ -838,6 +870,9 @@ public final class ExternalRefProcessor {
             if(parameter.getSchema() != null){
                 processRefSchemaObject(parameter.getSchema(), $ref);
             }
+            if(parameter.getExamples() != null) {
+               processRefExamples(parameter.getExamples(), $ref);
+            }
         }
 
         return newRef;
@@ -897,6 +932,24 @@ public final class ExternalRefProcessor {
                         processRefToExternalCallback(file + callback.get$ref(), RefFormat.RELATIVE);
                     }
                 }
+            } else {
+                for (String path : callback.keySet()) {
+                    PathItem pathItem = callback.get(path);
+                    if(pathItem != null) {
+                        if (pathItem.get$ref() != null) {
+                            RefFormat pathRefFormat = computeRefFormat(pathItem.get$ref());
+                            String path$ref = pathItem.get$ref();
+                            if (isAnExternalRefFormat(refFormat)) {
+                                pathItem = this.processRefToExternalPathItem(path$ref, pathRefFormat);
+                            } else {
+                                pathItem = cache.loadRef(pathItem.get$ref(), refFormat, PathItem.class);
+                            }
+                            callback.put(path, pathItem);
+                        } else {
+                            this.processPathItem(pathItem, $ref);
+                        }
+                    }
+                }
             }
         }
 
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 467cb853..94cecd67 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
@@ -19,8 +19,12 @@ import io.swagger.v3.parser.ResolverCache;
 import io.swagger.v3.parser.models.RefFormat;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
 
 import static io.swagger.v3.parser.util.RefUtils.computeRefFormat;
 import static io.swagger.v3.parser.util.RefUtils.isAnExternalRefFormat;
@@ -155,6 +159,9 @@ public class PathsProcessor {
                 for (String name : callbacks.keySet()) {
                     Callback callback = callbacks.get(name);
                     if (callback != null) {
+                        if(callback.get$ref() != null) {
+                            callback.set$ref(computeRef(callback.get$ref(), pathRef));
+                        }
                         for(String callbackName : callback.keySet()) {
                             PathItem pathItem = callback.get(callbackName);
                             updateRefs(pathItem,pathRef);
@@ -256,15 +263,33 @@ public class PathsProcessor {
                 for (Schema innerModel : composedSchema.getAllOf()) {
                     updateRefs(innerModel, pathRef);
                 }
-            }if (composedSchema.getAnyOf() != null) {
-                for(Schema innerModel : composedSchema.getAnyOf()) {
-                    updateRefs(innerModel, pathRef);
-                }
-            }if (composedSchema.getOneOf() != null) {
-                for (Schema innerModel : composedSchema.getOneOf()) {
-                    updateRefs(innerModel, pathRef);
+            }
+            if (composedSchema.getAnyOf() != null || composedSchema.getOneOf() != null) {
+                // Map to cache old - new refs in composed schemas
+                Map<String, String> refMappings = composedSchema.getDiscriminator() != null &&
+                        composedSchema.getDiscriminator().getMapping() != null ? new HashMap<>() : null;
+
+                Stream.of(composedSchema.getAnyOf(), composedSchema.getOneOf())
+                        .filter(Objects::nonNull).filter(l -> !l.isEmpty())
+                        .flatMap(Collection::stream)
+                        .forEach(innerModel -> {
+                            String oldRef = innerModel.get$ref();
+                            updateRefs(innerModel, pathRef);
+                            if(oldRef != null && refMappings != null && !oldRef.equals(innerModel.get$ref())) {
+                                refMappings.put(oldRef, innerModel.get$ref());
+                            }
+                });
+                // Update refs in discriminator mappings
+                if(refMappings != null && !refMappings.isEmpty()) {
+                    Map<String, String> discriminatorMappings = composedSchema.getDiscriminator().getMapping();
+                    for(String key : discriminatorMappings.keySet()) {
+                        if(refMappings.containsKey(discriminatorMappings.get(key))) {
+                            discriminatorMappings.put(key, refMappings.get(discriminatorMappings.get(key)));
+                        }
+                    }
                 }
             }
+
         }
         else if(model instanceof ArraySchema) {
             ArraySchema arraySchema = (ArraySchema) model;
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 6eab18cf..9e6e7faf 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
@@ -14,9 +14,12 @@ import io.swagger.v3.parser.models.RefType;
 import io.swagger.v3.parser.util.OpenAPIDeserializer;
 import io.swagger.v3.parser.util.RefUtils;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
 
 import static io.swagger.v3.parser.util.RefUtils.computeRefFormat;
 import static io.swagger.v3.parser.util.RefUtils.isAnExternalRefFormat;
@@ -157,33 +160,21 @@ public class SchemaProcessor {
                     }
                 }
             }
-        }if(composedSchema.getOneOf() != null){
-            final List<Schema> schemas = composedSchema.getOneOf();
-            if (schemas != null) {
-                for (Schema schema : schemas) {
-                    if (schema.get$ref() != null) {
-                        String oldRef = schema.get$ref();
-                        processReferenceSchema(schema);
-                        String newRef = schema.get$ref();
-                        changeDiscriminatorMapping(composedSchema, oldRef, newRef);
-                    } else {
-                        processSchemaType(schema);
-                    }
-                }
-            }
-        }if(composedSchema.getAnyOf() != null){
-            final List<Schema> schemas = composedSchema.getAnyOf();
-            if (schemas != null) {
-                for (Schema schema : schemas) {
-                    if (schema.get$ref() != null) {
-                        processReferenceSchema(schema);
-                    } else {
-                        processSchemaType(schema);
-                    }
-                }
-            }
         }
-
+        if(composedSchema.getOneOf() != null || composedSchema.getAnyOf() != null) {
+            Stream.of(composedSchema.getOneOf(), composedSchema.getAnyOf())
+                    .filter(Objects::nonNull).filter(l -> !l.isEmpty()).flatMap(Collection::stream)
+                    .forEach(schema -> {
+                        if (schema.get$ref() != null) {
+                            String oldRef = schema.get$ref();
+                            processReferenceSchema(schema);
+                            String newRef = schema.get$ref();
+                            changeDiscriminatorMapping(composedSchema, oldRef, newRef);
+                        } else {
+                            processSchemaType(schema);
+                        }
+                    });
+        }
     }
 
     private void changeDiscriminatorMapping(ComposedSchema composedSchema, String oldRef, String newRef) {


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3RefTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3RefTest.java
new file mode 100644
index 00000000..a3737f63
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3RefTest.java
@@ -0,0 +1,87 @@
+package io.swagger.v3.parser.test;
+
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.testng.Assert;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.examples.Example;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.parser.OpenAPIV3Parser;
+import io.swagger.v3.parser.core.models.ParseOptions;
+
+import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+
+public class OpenAPIV3RefTest {
+    private OpenAPI oas;
+    @Before
+    public void parseOASSpec() {
+        ParseOptions options = new ParseOptions();
+        options.setResolve(true);
+        oas = new OpenAPIV3Parser().read("oas3-refs-test/openapi.json", null, options);
+    }
+    @Test
+    public void testParameterExampleRefProcessed() {
+        String paramName = "correlation_id";
+        Map<String, Example> paramExamples = oas.getComponents().getParameters().get(paramName).getExamples();
+        Assert.assertEquals(paramExamples.size(), 1, "Parameter has an example");
+        Assert.assertTrue(paramExamples.values()
+                .stream().allMatch(e -> e.get$ref().equalsIgnoreCase("#/components/examples/" + paramName)),
+                "Examples are referenced");
+        Assert.assertEquals(oas.getComponents().getExamples().get(paramName).getValue(), "7758b780aaaca",
+                "Examples are processed");
+    }
+
+    @Test
+    public void testDiscriminatorMappingRefsUpdated() {
+         Schema reqSchema = oas.getPaths().values().stream()
+                .findFirst()
+                .map(path -> path.getPost().getRequestBody().getContent().getOrDefault("application/json", null))
+                 .map(MediaType::getSchema)
+                .orElseThrow(() -> new IllegalStateException("Path not processed!"));
+        Collection<String> discriminator = reqSchema.getDiscriminator().getMapping().values();
+        Set<String> allOfs = ((List<Schema>)reqSchema.getAnyOf())
+                .stream().map(Schema::get$ref).collect(Collectors.toSet());
+        assertTrue(allOfs.stream().allMatch(s -> s.contains(COMPONENTS_SCHEMAS_REF)),"Schema mappings are processed");
+        assertTrue(allOfs.containsAll(discriminator),"Discriminator mappings are updated");
+
+    }
+
+    @Test
+    public void testCallbackRef() {
+        assertEquals(oas.getComponents().getCallbacks().size(), 1, "Callbacks processed");
+        Operation cbOperation = oas.getComponents().getCallbacks()
+                .get("vaccination_complete")
+                .get("{$request.body#/callback_url}/pets/{$request.path.id}/vaccinations").getPut();
+        assertNotNull(cbOperation);
+        assertEquals(cbOperation.getRequestBody().getContent().get("application/json").getSchema().get$ref(),
+                COMPONENTS_SCHEMAS_REF + "vaccination_record", "Callback Request body processed");
+        assertEquals(cbOperation.getResponses().get("400").getContent().get("application/json").getSchema().get$ref(),
+                COMPONENTS_SCHEMAS_REF + "error" , "Callback responses Processed");
+    }
+
+    @Test
+    public void testComposedArrayItemsRef() {
+        Schema adoptionRequest = oas.getComponents()
+               .getSchemas().get("adoption_request");
+        assertEquals(adoptionRequest.getTitle(), "Adoption Request", "Processed ref Schemas");
+        assertEquals(((Schema) adoptionRequest.getProperties().get("adopter_alias")).get$ref(),
+                COMPONENTS_SCHEMAS_REF + "alias_array", "Processed ref added to Schemas");
+        Schema adopterAlias = ((Schema) oas.getComponents().getSchemas().get("alias_array"));
+        assertEquals(adopterAlias.getTitle(),"AdopterAlias", "Processed Schemas");
+        assertEquals(adopterAlias.getItems().getAllOf().size(), 2, "Processed schemas items");
+    }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/callbacks-issue/domain.yaml b/modules/swagger-parser-v3/src/test/resources/callbacks-issue/domain.yaml
index 4d6c8551..f4fb27c4 100644
--- a/modules/swagger-parser-v3/src/test/resources/callbacks-issue/domain.yaml
+++ b/modules/swagger-parser-v3/src/test/resources/callbacks-issue/domain.yaml
@@ -31,4 +31,15 @@ components:
                   $ref: '#/components/schemas/WebhookVerification'
           responses:
             '202':
-              description: Your server returns this code if it accepts the message it was sent
\ No newline at end of file
+              description: Your server returns this code if it accepts the message it was sent
+  schemas:
+    WebhookVerification:
+      type: string
+  parameters:
+    x-api-key:
+      name: Correlation-Id
+      in: header
+      description: description
+      required: false
+      schema:
+        type: string
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/callbacks/pets.id.order-vaccination/post/vaccination_complete.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/callbacks/pets.id.order-vaccination/post/vaccination_complete.json
new file mode 100644
index 00000000..b3eb3c68
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/callbacks/pets.id.order-vaccination/post/vaccination_complete.json
@@ -0,0 +1,110 @@
+{
+  "{$request.body#/callback_url}/pets/{$request.path.id}/vaccinations": {
+    "put": {
+      "summary": "Vaccination record sent on completion",
+      "description": "New vaccination record for a pet sent after vaccination order is complete.",
+      "x-slo": {
+        "response_time_95th_percentile": 100,
+        "error_rate": 0.001
+      },
+      "x-visibility": {
+        "extent": "INTERNAL"
+      },
+      "requestBody": {
+        "required": true,
+        "content": {
+          "application/json": {
+            "schema": {
+              "$ref": "../../../schemas/vaccination_record.json"
+            },
+            "examples": {
+              "vaccination": {
+                "summary": "Vaccination with ID",
+                "description": "Vaccination record for pet with vaccination id.",
+                "value": {
+                  "id": "8ce1edf4-3014-4b5c-b50d-9ed697768ead",
+                  "vaccine_name": "Feline Viral Rhinotracheitis",
+                  "administration_date": "2023-12-20"
+                }
+              }
+            }
+          }
+        }
+      },
+      "responses": {
+        "204": {
+          "description": "Your server implementation should return this HTTP status code if the data was received successfully."
+        },
+        "400": {
+          "description": "Your server should return this HTTP status code if the request is malformed.",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "../../../schemas/error.json"
+              },
+              "examples": {
+                "bad_vaccine_name": {
+                  "summary": "Vaccine name is invalid.",
+                  "value": {
+                    "name": "INVALID_REQUEST",
+                    "debug_id": "b1d1f06c7246c",
+                    "message": "Request is not well-formed, syntactically incorrect, or violates schema.",
+                    "details": [
+                      {
+                        "field": "/vaccine_name",
+                        "location": "body",
+                        "value": "#bad-value#",
+                        "issue": "INVALID_PARAMETER_SYNTAX",
+                        "description": "The value of a field does not conform to the expected format."
+                      }
+                    ]
+                  }
+                }
+              }
+            }
+          }
+        },
+        "404": {
+          "description": "Your server should return this HTTP status code if the pet id is not found.",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "../../../schemas/error.json"
+              },
+              "examples": {
+                "generic": {
+                  "summary": "Pet 'Not Found' error.",
+                  "value": {
+                    "name": "RESOURCE_NOT_FOUND",
+                    "debug_id": "b1d1f06c7246c",
+                    "message": "The specified resource does not exist."
+                  }
+                }
+              }
+            }
+          }
+        },
+        "500": {
+          "description": "Your server should return this HTTP status code if there is an internal error processing the request.",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "../../../schemas/error.json"
+              },
+              "examples": {
+                "generic": {
+                  "summary": "Generic internal server error.",
+                  "value": {
+                    "name": "INTERNAL_SERVER_ERROR",
+                    "debug_id": "b1d1f06c7246c",
+                    "message": "An internal server error has occurred."
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/examples/parameter/header/correlation_id.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/examples/parameter/header/correlation_id.json
new file mode 100644
index 00000000..54e0f4c9
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/examples/parameter/header/correlation_id.json
@@ -0,0 +1,5 @@
+{
+  "summary": "A correlation id.",
+  "description": "A correlation-id header with a randomized id value.",
+  "value": "7758b780aaaca"
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/parameters/header/adopt/correlation_id.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/parameters/header/adopt/correlation_id.json
new file mode 100644
index 00000000..685192f4
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/parameters/header/adopt/correlation_id.json
@@ -0,0 +1,15 @@
+{
+    "name": "Correlation-Id",
+    "in": "header",
+    "description": "Correlation ID for distributed tracing.",
+    "required": false,
+    "schema": {
+        "$ref": "../../../schemas/correlation_id.json"
+    },
+    "examples": {
+        "correlation_id": {
+            "$ref": "../../../examples/parameter/header/correlation_id.json"
+        }
+    },
+    "x-redacted": true
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request.json
new file mode 100644
index 00000000..4fe8f9ac
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request.json
@@ -0,0 +1,54 @@
+{
+    "title": "Adoption Request",
+    "description": "A pet adoption request.",
+    "type": "object",
+    "required": [
+        "animal_type",
+        "pet_id",
+        "adopter_name",
+        "adopter_address"
+    ],
+    "properties": {
+        "animal_type": {
+            "$ref": "./animal_type.json"
+        },
+        "pet_id": {
+            "allOf": [
+                {
+                    "$ref": "correlation_id.json"
+                },
+                {
+                    "title": "Pet Id",
+                    "description": "Unique ID of a pet.",
+                    "x-redacted": true,
+                    "readOnly": true
+                }
+            ]
+        },
+        "adopter_name": {
+            "allOf": [
+                {
+                    "$ref": "vaccine_name.json"
+                },
+                {
+                    "description": "The adopter's name.",
+                    "x-redacted": true
+                }
+            ]
+        },
+        "adopter_alias" : {
+           "$ref": "./alias_array.json"
+        },
+        "adopter_address": {
+            "allOf": [
+                {
+                    "$ref": "full_name.json"
+                },
+                {
+                    "description": "The adopter's address.",
+                    "x-redacted": true
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_bird.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_bird.json
new file mode 100644
index 00000000..de82a1aa
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_bird.json
@@ -0,0 +1,19 @@
+{
+    "allOf": [
+        {
+            "$ref": "./adoption_request.json"
+        },
+        {
+            "title": "Adoption Request for Bird",
+            "description": "A bird adoption request.",
+            "type": "object",
+            "properties": {
+                "have_birdcage": {
+                    "description": "Indicates if the adopter has a birdcage.",
+                    "type": "boolean",
+                    "x-redacted": true
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_cat.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_cat.json
new file mode 100644
index 00000000..86267aac
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_cat.json
@@ -0,0 +1,24 @@
+{
+    "allOf": [
+        {
+            "$ref": "./adoption_request.json"
+        },
+        {
+            "title": "Adoption Request for Cat",
+            "description": "A cat adoption request.",
+            "type": "object",
+            "properties": {
+                "have_litter_box": {
+                    "description": "Indicates if the adopter has a litter box.",
+                    "type": "boolean",
+                    "x-redacted": true
+                },
+                "have_scratching_post": {
+                    "description": "Indicates if the adopter has a scratching post.",
+                    "type": "boolean",
+                    "x-redacted": true
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_dog.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_dog.json
new file mode 100644
index 00000000..e645a025
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_dog.json
@@ -0,0 +1,24 @@
+{
+    "allOf": [
+        {
+            "$ref": "./adoption_request.json"
+        },
+        {
+            "title": "Adoption Request for Dog",
+            "description": "A dog adoption request.",
+            "type": "object",
+            "properties": {
+                "have_collar": {
+                    "description": "Indicates if the adopter has a dog collar.",
+                    "type": "boolean",
+                    "x-redacted": true
+                },
+                "have_leash": {
+                    "description": "Indicates if the adopter has a dog leash.",
+                    "type": "boolean",
+                    "x-redacted": true
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_fish.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_fish.json
new file mode 100644
index 00000000..84077eb0
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_fish.json
@@ -0,0 +1,19 @@
+{
+    "allOf": [
+        {
+            "$ref": "./adoption_request.json"
+        },
+        {
+            "title": "Adoption Request for Fish",
+            "description": "A fish adoption request.",
+            "type": "object",
+            "properties": {
+                "have_aquarium": {
+                    "description": "Indicates if the adopter has an aquarium.",
+                    "type": "boolean",
+                    "x-redacted": false
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_lizard.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_lizard.json
new file mode 100644
index 00000000..d55c2b50
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/adoption_request_for_lizard.json
@@ -0,0 +1,24 @@
+{
+    "allOf": [
+        {
+            "$ref": "./adoption_request.json"
+        },
+        {
+            "title": "Adoption Request for Lizard",
+            "description": "A lizard adoption request.",
+            "type": "object",
+            "properties": {
+                "have_terrarium": {
+                    "description": "Indicates if the adopter has a terrarium.",
+                    "type": "boolean",
+                    "x-redacted": false
+                },
+                "have_heat_lamp": {
+                    "description": "Indicates if the adopter has a heat lamp.",
+                    "type": "boolean",
+                    "x-redacted": true
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/alias_array.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/alias_array.json
new file mode 100644
index 00000000..606a7f7f
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/alias_array.json
@@ -0,0 +1,15 @@
+{
+  "type": "array",
+  "title": "AdopterAlias",
+  "items": {
+    "allOf": [
+      {
+        "$ref": "full_name.json"
+      },
+      {
+        "description": "The adopter's address.",
+        "x-redacted": true
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/animal_type.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/animal_type.json
new file mode 100644
index 00000000..13b33d20
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/animal_type.json
@@ -0,0 +1,20 @@
+{
+    "title": "Animal Type",
+    "description": "Type of animal.",
+    "type": "string",
+    "enum": [
+        "BIRD",
+        "CAT",
+        "DOG",
+        "FISH",
+        "LIZARD"
+    ],
+    "x-enumDescriptions": {
+        "BIRD": "A warm-blooded egg-laying vertebrate distinguished by the possession of feathers, wings, and a beak and (typically) by being able to fly.",
+        "CAT": "A small domesticated carnivorous mammal with soft fur, a short snout, and retractable claws.",
+        "DOG": "A domesticated carnivorous mammal that typically has a long snout, an acute sense of smell, non-retractable claws, and a barking, howling, or whining voice.",
+        "FISH": "A limbless cold-blooded vertebrate animal with gills and fins and living wholly in water.",
+        "LIZARD": "A reptile that typically has a long body and tail, four legs, movable eyelids, and a rough, scaly, or spiny skin."
+    },
+    "x-redacted": true
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/correlation_id.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/correlation_id.json
new file mode 100644
index 00000000..56df4a2f
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/correlation_id.json
@@ -0,0 +1,8 @@
+{
+    "title": "Correlation Id",
+    "description": "A correlation id used for debugging.",
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 100,
+    "pattern": "^.*$"
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/date_no_time.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/date_no_time.json
new file mode 100644
index 00000000..963cfed2
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/date_no_time.json
@@ -0,0 +1,7 @@
+{
+    "type": "string",
+    "description": "The stand-alone date, in [Internet date and time format](https://tools.ietf.org/html/rfc3339#section-5.6). To represent special legal values, such as a date of birth, you should use dates with no associated time or time-zone data. Whenever possible, use the standard `date_time` type. This regular expression does not validate all dates. For example, February 31 is valid and nothing is known about leap years.",
+    "minLength": 10,
+    "maxLength": 10,
+    "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$"
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/error.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/error.json
new file mode 100644
index 00000000..95fb0910
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/error.json
@@ -0,0 +1,57 @@
+{
+    "title": "Error",
+    "description": "The error details.",
+    "type": "object",
+    "required": [
+        "name",
+        "message",
+        "debug_id"
+    ],
+    "properties": {
+        "name": {
+            "description": "The human-readable, unique name of the error.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 10000,
+            "pattern": "^.*$",
+            "x-redacted": false
+        },
+        "message": {
+            "description": "The message that describes the error.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 100000,
+            "pattern": "^.*$",
+            "x-redacted": true
+        },
+        "debug_id": {
+            "allOf": [
+                {
+                    "$ref": "correlation_id.json"
+                },
+                {
+                    "description": "Trace ID, used for correlation purposes.",
+                    "x-redacted": true
+                }
+            ]
+        },
+        "details": {
+            "description": "An array of additional details about the error.",
+            "type": "array",
+            "minItems": 0,
+            "maxItems": 10000,
+            "items": {
+                "$ref": "error_details.json"
+            }
+        },
+        "links": {
+            "description": "An array of request-related [HATEOAS links](/api/rest/responses/#hateoas-links).",
+            "type": "array",
+            "minItems": 0,
+            "maxItems": 10000,
+            "items": {
+                "$ref": "./link_description.json"
+            }
+        }
+    }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/error_details.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/error_details.json
new file mode 100644
index 00000000..e70b7bb1
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/error_details.json
@@ -0,0 +1,69 @@
+{
+    "title": "Error Details",
+    "description": "The error details. Required for client-side `4XX` errors.",
+    "type": "object",
+    "required": [
+        "issue"
+    ],
+    "properties": {
+        "field": {
+            "description": "The field that caused the error. If this field is in the body, set this value to the field's JSON pointer value. Required for client-side errors.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 10000,
+            "pattern": "^.*$",
+            "x-redacted": true
+        },
+        "value": {
+            "description": "The value of the field that caused the error.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 100000,
+            "pattern": "^.*$",
+            "x-redacted": true
+        },
+        "location": {
+            "description": "The location of the field that caused the error.",
+            "type": "string",
+            "default": "body",
+            "enum": [
+                "body",
+                "header",
+                "path",
+                "query"
+            ],
+            "x-enumDescriptions": {
+                "body": "The field is in the request body.",
+                "header": "The field is a header.",
+                "path": "The field is a path parameter.",
+                "query": "The field is a query parameter."
+            },
+            "x-redacted": true
+        },
+        "issue": {
+            "description": "The unique, fine-grained application-level error code.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 100000,
+            "pattern": "^.*$",
+            "x-redacted": false
+        },
+        "links": {
+            "description": "An array of request-related [HATEOAS links](/api/rest/responses/#hateoas-links) that are either relevant to the issue by providing additional information or offering potential resolutions.",
+            "type": "array",
+            "minItems": 1,
+            "maxItems": 4,
+            "items": {
+                "$ref": "./link_description.json"
+            }
+        },
+        "description": {
+            "description": "The human-readable description for an issue. The description can change over the lifetime of an API, so clients must not depend on this value.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 100000,
+            "pattern": "^.*$",
+            "x-redacted": false
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/full_name.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/full_name.json
new file mode 100644
index 00000000..bc1f16c2
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/full_name.json
@@ -0,0 +1,7 @@
+{
+    "title": "Name",
+    "description": "Name of the Pet.",
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 100
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/link_description.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/link_description.json
new file mode 100644
index 00000000..4c05c2de
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/link_description.json
@@ -0,0 +1,93 @@
+{
+    "title": "Link Description",
+    "description": "The request-related [HATEOAS link](/api/rest/responses/#hateoas-links) information.",
+    "type": "object",
+    "required": [
+        "href",
+        "rel"
+    ],
+    "properties": {
+        "href": {
+            "description": "The complete target URL. To make the related call, combine the method with this [URI Template-formatted](https://tools.ietf.org/html/rfc6570) link. For pre-processing, include the `$`, `(`, and `)` characters. The `href` is the key HATEOAS component that links a completed call with a subsequent call.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 20000,
+            "pattern": "^.*$",
+            "x-redacted": false
+        },
+        "rel": {
+            "description": "The [link relation type](https://tools.ietf.org/html/rfc5988#section-4), which serves as an ID for a link that unambiguously describes the semantics of the link. See [Link Relations](https://www.iana.org/assignments/link-relations/link-relations.xhtml).",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 100,
+            "pattern": "^.*$",
+            "x-redacted": false
+        },
+        "method": {
+            "description": "The HTTP method required to make the related call.",
+            "type": "string",
+            "enum": [
+                "CONNECT",
+                "DELETE",
+                "GET",
+                "HEAD",
+                "OPTIONS",
+                "PATCH",
+                "POST",
+                "PUT"
+            ],
+            "x-redacted": false
+        },
+        "title": {
+            "description": "The link title.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 1000,
+            "pattern": "^.*$",
+            "x-redacted": false
+        },
+        "mediaType": {
+            "description": "The media type, as defined by [RFC 2046](https://www.ietf.org/rfc/rfc2046.txt). Describes the link target.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 255,
+            "pattern": "^[a-zA-Z\\-/\\.0-9\\+]+$",
+            "x-redacted": true
+        },
+        "encType": {
+            "description": "The media type in which to submit the request data.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 255,
+            "pattern": "^[a-zA-Z\\-/\\.0-9\\+]+$",
+            "default": "application/json",
+            "x-redacted": true
+        },
+        "schema": {
+            "allOf": [
+                {
+                    "$ref": "link_schema.json"
+                },
+                {
+                    "description": "The schema that describes the request data.",
+                    "x-visibility": {
+                        "extent": "INTERNAL"
+                    }
+                }
+            ]
+        },
+        "targetSchema": {
+            "allOf": [
+                {
+                    "$ref": "link_schema.json"
+                },
+                {
+                    "description": "The schema that describes the link target.",
+                    "x-visibility": {
+                        "extent": "INTERNAL"
+                    }
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/link_schema.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/link_schema.json
new file mode 100644
index 00000000..da9427b9
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/link_schema.json
@@ -0,0 +1,136 @@
+{
+    "title": "Link Schema",
+    "description": "The request data or link target.",
+    "type": "object",
+    "properties": {
+        "additionalItems": {
+            "title": "Additional Items",
+            "description": "Any additional items.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "dependencies": {
+            "title": "Dependencies",
+            "description": "The dependencies.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "items": {
+            "title": "Items",
+            "description": "An item.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "definitions": {
+            "title": "Definitions",
+            "description": "The definitions.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "patternProperties": {
+            "title": "Pattern Properties",
+            "description": "The pattern properties.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "properties": {
+            "title": "Properties",
+            "description": "The properties.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "allOf": {
+            "description": "An array of sub-schemas. The data must validate against all sub-schemas.",
+            "type": "array",
+            "minItems": 0,
+            "maxItems": 10000,
+            "items": {
+                "title": "All Of Item",
+                "description": "A sub-schema against which the data must validate.",
+                "type": "object",
+                "x-redacted": true
+            }
+        },
+        "anyOf": {
+            "description": "An array of sub-schemas. The data must validate against one or more sub-schemas.",
+            "type": "array",
+            "minItems": 0,
+            "maxItems": 10000,
+            "items": {
+                "title": "Any Of Item",
+                "description": "A sub-schema against which the data must validate.",
+                "type": "object",
+                "x-redacted": true
+            }
+        },
+        "oneOf": {
+            "description": "An array of sub-schemas. The data must validate against one sub-schema.",
+            "type": "array",
+            "minItems": 0,
+            "maxItems": 10000,
+            "items": {
+                "title": "One Of Item",
+                "description": "A sub-schema against which the data must validate.",
+                "type": "object",
+                "x-redacted": false
+            }
+        },
+        "not": {
+            "title": "Not",
+            "description": "Not.",
+            "type": "object",
+            "x-redacted": false
+        },
+        "links": {
+            "description": "An array of links.",
+            "type": "array",
+            "minItems": 0,
+            "maxItems": 10000,
+            "items": {
+                "title": "Link",
+                "description": "A link.",
+                "type": "object",
+                "x-redacted": false
+            }
+        },
+        "fragmentResolution": {
+            "description": "The fragment resolution.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 10000,
+            "pattern":  "^.*$",
+            "x-redacted": false
+        },
+        "media": {
+            "title": "Media",
+            "description": "The media type and context-encoding scheme.",
+            "type": "object",
+            "properties": {
+                "type": {
+                    "description": "The media type. See [Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types](https://tools.ietf.org/html/rfc2046).",
+                    "type": "string",
+                    "minLength": 0,
+                    "maxLength": 255,
+                    "pattern": "^[a-zA-Z\\-/\\.0-9\\+]+$",
+                    "x-redacted": false
+                },
+                "binaryEncoding": {
+                    "description": "The content-encoding scheme. See [Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies](https://tools.ietf.org/html/rfc2045).",
+                    "type": "string",
+                    "minLength": 0,
+                    "maxLength": 255,
+                    "pattern": "^[a-zA-Z\\-/\\.0-9\\+]+$",
+                    "x-redacted": false
+                }
+            }
+        },
+        "pathStart": {
+            "description": "To apply this schema to the instances' URIs, start the URIs with this value.",
+            "type": "string",
+            "minLength": 0,
+            "maxLength": 10000,
+            "format": "uri",
+            "x-redacted": true
+        }
+    }
+}
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/links.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/links.json
new file mode 100644
index 00000000..6674eec5
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/links.json
@@ -0,0 +1,17 @@
+{
+    "title": "Links",
+    "description": "Resource links.",
+    "type": "array",
+    "minItems": 1,
+    "maxItems": 100,
+    "items": {
+        "allOf": [
+            {
+                "$ref": "../../common_components/v5/schema/json/openapi-3.0/components/schemas/rest/link_description.json"
+            },
+            {
+                "x-redacted": false
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccination_order.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccination_order.json
new file mode 100644
index 00000000..ffd0c2c1
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccination_order.json
@@ -0,0 +1,36 @@
+{
+    "title": "Vaccination Order",
+    "description": "An order to perform a vaccination.",
+    "type": "object",
+    "required": [
+        "vaccine_name",
+        "by_date"
+    ],
+    "properties": {
+        "vaccine_name": {
+            "$ref": "./vaccine_name.json"
+        },
+        "by_date": {
+            "allOf": [
+                {
+                    "$ref": "../../common_components/v5/schema/json/openapi-3.0/components/schemas/date_time/date_no_time.json"
+                },
+                {
+                    "description": "Date vaccine must be administered by.",
+                    "x-redacted": true
+                }
+            ]
+        },
+        "after_date": {
+            "allOf": [
+                {
+                    "$ref": "../../common_components/v5/schema/json/openapi-3.0/components/schemas/date_time/date_no_time.json"
+                },
+                {
+                    "description": "Date vaccine must be administered after.",
+                    "x-redacted": true
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccination_record.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccination_record.json
new file mode 100644
index 00000000..ba3d092f
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccination_record.json
@@ -0,0 +1,37 @@
+{
+    "title": "Vaccination Record",
+    "description": "Record of an administered vaccination.",
+    "type": "object",
+    "required": [
+        "id",
+        "vaccine_name",
+        "administration_date"
+    ],
+    "properties": {
+        "id": {
+            "allOf": [
+                {
+                    "$ref": "./correlation_id.json"
+                },
+                {
+                    "description": "Unique ID of a vaccination.",
+                    "x-redacted": true
+                }
+            ]
+        },
+        "vaccine_name": {
+            "$ref": "./vaccine_name.json"
+        },
+        "administration_date": {
+            "allOf": [
+                {
+                    "$ref": "./date_no_time.json"
+                },
+                {
+                    "description": "Date vaccine administered.",
+                    "x-redacted": true
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccinations.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccinations.json
new file mode 100644
index 00000000..7da3a9b6
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccinations.json
@@ -0,0 +1,10 @@
+{
+    "title": "Vaccinations",
+    "description": "List of vaccinations.",
+    "type": "array",
+    "minItems": 1,
+    "maxItems": 1000,
+    "items": {
+        "$ref": "./vaccination_record.json"
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine.json
new file mode 100644
index 00000000..1844e59b
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine.json
@@ -0,0 +1,45 @@
+{
+    "title": "Vaccine",
+    "description": "Information about a vaccine that may be administered. Vaccine information is owned by an outside partner. The information is available in the pet store so that employees may administer available vaccines based on the vaccine information.",
+    "type": "object",
+    "required": [
+        "id",
+        "vaccine_name",
+        "number_of_doses"
+    ],
+    "properties": {
+        "id": {
+            "$ref": "./vaccine_id.json"
+        },
+        "vaccine_name": {
+            "$ref": "./vaccine_name.json"
+        },
+        "animal_types": {
+            "description": "The types of animals that may receive this vaccine.",
+            "type": "array",
+            "minItems": 1,
+            "maxItems": 100,
+            "items": {
+                "$ref": "./animal_type.json"
+            }
+        },
+        "number_of_doses": {
+            "description": "The number of doses to administer for the vaccine to be effective.",
+            "type": "integer",
+            "minimum": 1,
+            "maximum": 10,
+            "x-redacted": false
+        },
+        "interval_between_doses": {
+            "allOf": [
+                {
+                    "$ref": "../../common_components/v5/schema/json/openapi-3.0/components/schemas/date_time/time_duration.json"
+                },
+                {
+                    "description": "The time between doses if more than one dose is required.",
+                    "x-redacted": true
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine_id.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine_id.json
new file mode 100644
index 00000000..48fe09fa
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine_id.json
@@ -0,0 +1,9 @@
+{
+    "title": "Vaccine ID",
+    "description": "Unique ID of a vaccine as defined by the external partner's system.",
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 5000,
+    "pattern": "^.*$",
+    "x-redacted": false
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine_name.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine_name.json
new file mode 100644
index 00000000..73f537dc
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/components/schemas/vaccine_name.json
@@ -0,0 +1,9 @@
+{
+    "title": "Vaccine Name",
+    "description": "Name of the vaccine.",
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 1000,
+    "pattern": "^[A-Za-z0-9 ]+$",
+    "x-redacted": true
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/openapi.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/openapi.json
new file mode 100644
index 00000000..4f097756
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/openapi.json
@@ -0,0 +1,290 @@
+{
+    "openapi": "3.0.3",
+    "info": {
+        "title": "OpenAPI 3.0 Specification Reference",
+        "description": "This specification demonstrates ref uses.",
+        "version": "1.1",
+        "contact": {
+            "name": "Another OAS 3 User",
+            "url": "https://example.com/",
+            "email": "antho93@gmail.com"
+        }
+    },
+    "servers": [
+        {
+            "url": "https://{host}/v1/demo",
+            "description": "LIVE",
+            "variables": {
+                "host": {
+                    "enum": [
+                        "api.example.com",
+                        "api-m.example.com"
+                    ],
+                    "default": "api.example.com"
+                }
+            }
+        },
+        {
+            "url": "https://api.sandbox.example.com/v1/demo",
+            "description": "Sandbox."
+        }
+    ],
+    "tags": [
+        {
+            "name": "adoption",
+            "description": "Adopt Pets"
+        }
+    ],
+    "paths": {
+        "/adopt": {
+            "$ref": "./paths/adopt.json"
+        }
+    },
+    "components" : {
+            "schemas": {
+                "an-object": {
+                    "type": "object",
+                    "properties": {
+                        "an-array": {
+                            "$ref": "./components/schemas/alias_array.json"
+                        }
+                    }
+                }
+            },
+            "responses": {
+            "204_response": {
+                "description": "No Content."
+            },
+            "401_error_response": {
+                "description": "Unauthorized.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Unauthorized' error.",
+                                "value": {
+                                    "name": "AUTHENTICATION_FAILURE",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "Authentication failed due to missing Authorization header, or invalid authentication credentials."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "403_error_response": {
+                "description": "Forbidden.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Forbidden' error.",
+                                "value": {
+                                    "name": "NOT_AUTHORIZED",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "Authorization failed due to insufficient permissions."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "404_error_response": {
+                "description": "Not Found.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Not Found' error.",
+                                "value": {
+                                    "name": "RESOURCE_NOT_FOUND",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The specified resource does not exist."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "405_error_response": {
+                "description": "Method Not Allowed.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Method Not Allowed' error.",
+                                "value": {
+                                    "name": "METHOD_NOT_SUPPORTED",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The server does not implement the requested HTTP method."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "406_error_response": {
+                "description": "Not Acceptable.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Not Acceptable' error.",
+                                "value": {
+                                    "name": "MEDIA_TYPE_NOT_ACCEPTABLE",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The server does not implement the media type that would be acceptable to the client."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "410_error_response": {
+                "description": "Gone.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Gone' error.",
+                                "value": {
+                                    "name": "RESOURCE_NOT_AVAILABLE",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The requested resource is no longer available."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "413_error_response": {
+                "description": "Payload Too Large.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Payload Too Large' error.",
+                                "value": {
+                                    "name": "PAYLOAD_TOO_LARGE",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The request entity is larger than the size limit defined by the server."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "415_error_response": {
+                "description": "Unsupported Media Type.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Unsupported Media Type' error.",
+                                "value": {
+                                    "name": "UNSUPPORTED_MEDIA_TYPE",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The server does not support the request payload's media type."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "429_error_response": {
+                "description": "Too Many Requests.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Too Many Requests' error.",
+                                "value": {
+                                    "name": "RATE_LIMIT_REACHED",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "Too many requests. Blocked due to rate limiting."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "500_error_response": {
+                "description": "Internal Server Error.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic internal server error.",
+                                "value": {
+                                    "name": "INTERNAL_SERVER_ERROR",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "An internal server error has occurred."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "503_error_response": {
+                "description": "Service Unavailable.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        },
+                        "examples": {
+                            "generic": {
+                                "summary": "Generic 'Service Unavailable' error.",
+                                "value": {
+                                    "name": "SERVICE_UNAVAILABLE",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "Service Unavailable."
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "default_response": {
+                "description": "Default response.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "components/schemas/error.json"
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/paths/adopt.json b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/paths/adopt.json
new file mode 100644
index 00000000..3de7cd2a
--- /dev/null
+++ b/modules/swagger-parser-v3/src/test/resources/oas3-refs-test/paths/adopt.json
@@ -0,0 +1,171 @@
+{
+    "post": {
+        "tags": [
+            "pets"
+        ],
+        "summary": "Adopt Pet",
+        "description": "Adopt a pet.",
+        "operationId": "adopt",
+        "x-slo": {
+            "response_time_95th_percentile": 220,
+            "error_rate": 0.004
+        },
+        "parameters": [
+            {
+                "$ref": "../components/parameters/header/adopt/correlation_id.json"
+            }
+        ],
+        "requestBody": {
+            "required": true,
+            "content": {
+                "application/json": {
+                    "schema": {
+                        "anyOf": [
+                            {
+                                "$ref": "../components/schemas/adoption_request_for_bird.json"
+                            },
+                            {
+                                "$ref": "../components/schemas/adoption_request_for_cat.json"
+                            },
+                            {
+                                "$ref": "../components/schemas/adoption_request_for_dog.json"
+                            },
+                            {
+                                "$ref": "../components/schemas/adoption_request_for_fish.json"
+                            },
+                            {
+                                "$ref": "../components/schemas/adoption_request_for_lizard.json"
+                            }
+                        ],
+                        "discriminator": {
+                            "propertyName": "animal_type",
+                            "mapping": {
+                                "BIRD": "../components/schemas/adoption_request_for_bird.json",
+                                "CAT": "../components/schemas/adoption_request_for_cat.json",
+                                "DOG": "../components/schemas/adoption_request_for_dog.json",
+                                "FISH": "../components/schemas/adoption_request_for_fish.json",
+                                "LIZARD": "../components/schemas/adoption_request_for_lizard.json"
+                            }
+                        }
+                    },
+                    "examples": {
+                        "success": {
+                            "summary": "Successful Adoption Request",
+                            "description": "A request for adoption resulting in success.",
+                            "value": {
+                                "pet_id": "d1ef2439-3a70-4d0b-aef0-48a9a485ef95",
+                                "adopter_name": {
+                                    "name": "Suzy McGee"
+                                },
+                                "adopter_alias": [
+                                 "Suzy M"
+                                ],
+                                "adopter_address": {
+                                    "address_line_1": "23 Maple Ave",
+                                    "address_line_2": "Apt 3",
+                                    "admin_area_2": "Bangor",
+                                    "admin_area_1": "ME",
+                                    "postal_code": "04401",
+                                    "country_code": "US"
+                                },
+                                "animal_type": "CAT",
+                                "have_litter_box": true,
+                                "have_scratching_post": false
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        "responses": {
+            "204": {
+                "description": "Successful adoption."
+            },
+            "400": {
+                "description": "Bad request.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "../components/schemas/error.json"
+                        },
+                        "examples": {
+                            "missing_pet_id": {
+                                "summary": "Missing required parameter, pet_id.",
+                                "value": {
+                                    "name": "INVALID_REQUEST",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "Request is not well-formed, syntactically incorrect, or violates schema.",
+                                    "details": [
+                                        {
+                                            "field": "/pet_id",
+                                            "location": "body",
+                                            "issue": "MISSING_REQUIRED_PARAMETER",
+                                            "description": "A required field or parameter is missing."
+                                        }
+                                    ]
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "403": {
+                "$ref": "../openapi.json#/components/responses/403_error_response"
+            },
+            "422": {
+                "description": "Unprocessable entity.",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "../components/schemas/error.json"
+                        },
+                        "examples": {
+                            "risk_assessment_deny": {
+                                "summary": "Deny adoption based on risk assessment.",
+                                "value": {
+                                    "name": "UNPROCESSABLE_ENTITY",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
+                                    "details": [
+                                        {
+                                            "issue": "RISK_ASSESSMENT_DENY",
+                                            "description": "Risk assessment determined that the requested action is denied."
+                                        }
+                                    ]
+                                }
+                            },
+                            "unknown_pet_id": {
+                                "summary": "Pet with id not found.",
+                                "value": {
+                                    "name": "UNPROCESSABLE_ENTITY",
+                                    "debug_id": "b1d1f06c7246c",
+                                    "message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
+                                    "details": [
+                                        {
+                                            "field": "/pet_id",
+                                            "location": "body",
+                                            "value": "d1ef2439-3a70-4d0b-aef0-48a9a485ef97",
+                                            "issue": "PET_NOT_FOUND",
+                                            "description": "No pet found with the specified id."
+                                        }
+                                    ]
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "500": {
+                "$ref": "../openapi.json#/components/responses/500_error_response"
+            },
+            "default": {
+                "$ref": "../openapi.json#/components/responses/default_response"
+            }
+        },
+        "callbacks": {
+            "vaccination_complete": {
+                "$ref": "../components/callbacks/pets.id.order-vaccination/post/vaccination_complete.json"
+            }
+        }
+    }
+}
\ No newline at end of file