Repository: getkin/kin-openapi
Pull request #965 (2024-06-15 19:47:26)

Update Go module dependencies
- https://github.com/go-openapi/jsonpointer/compare/v0.20.2...v0.21.0
Updates module deps, Github CI

- https://github.com/invopop/yaml/compare/v0.2.0...v0.3.1
Updated linter, gopkg.in/yaml.v3 to fix CVE & go doc fixes.

- https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0


Patch:
diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt
index 7c95093..214af2a 100644
--- a/.github/docs/openapi3.txt
+++ b/.github/docs/openapi3.txt
@@ -110,6 +110,48 @@ func Int64Ptr(value int64) *int64
 func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error)
     ReadFromFile is a ReadFromURIFunc which reads local file URIs.
 
+func ReferencesComponentInRootDocument(doc *T, ref componentRef) (string, bool)
+    ReferencesComponentInRootDocument returns if the given component reference
+    references the same document or element as another component reference in
+    the root document's '#/components/<type>'. If it does, it returns the name
+    of it in the form '#/components/<type>/NameXXX'
+
+    Of course given a component from the root document will always match itself.
+
+    https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
+    https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls
+
+    Example. Take the spec with directory structure:
+
+        openapi.yaml
+        schemas/
+        ├─ record.yaml
+        ├─ records.yaml
+
+    In openapi.yaml we have:
+
+        components:
+          schemas:
+            Record:
+              $ref: schemas/record.yaml
+
+    Case 1: records.yml references a component in the root document
+
+        $ref: ../openapi.yaml#/components/schemas/Record
+
+    This would return...
+
+        #/components/schemas/Record
+
+    Case 2: records.yml indirectly refers to the same schema as a schema the
+    root document's '#/components/schemas'.
+
+        $ref: ./record.yaml
+
+    This would also return...
+
+        #/components/schemas/Record
+
 func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker)
     RegisterArrayUniqueItemsChecker is used to register a customized function
     used to check if JSON array have unique items.
@@ -191,11 +233,16 @@ func (callback *Callback) Value(key string) *PathItem
 type CallbackRef struct {
 	Ref   string
 	Value *Callback
+
 	// Has unexported fields.
 }
     CallbackRef represents either a Callback or a $ref to a Callback. When
     serializing and both fields are set, Ref is preferred over Value.
 
+func (x *CallbackRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *CallbackRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -206,6 +253,12 @@ func (x CallbackRef) MarshalJSON() ([]byte, error)
 func (x CallbackRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of CallbackRef.
 
+func (x *CallbackRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *CallbackRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *CallbackRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets CallbackRef to a copy of data.
 
@@ -379,11 +432,16 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption)
 type ExampleRef struct {
 	Ref   string
 	Value *Example
+
 	// Has unexported fields.
 }
     ExampleRef represents either a Example or a $ref to a Example. When
     serializing and both fields are set, Ref is preferred over Value.
 
+func (x *ExampleRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *ExampleRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -394,6 +452,12 @@ func (x ExampleRef) MarshalJSON() ([]byte, error)
 func (x ExampleRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of ExampleRef.
 
+func (x *ExampleRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *ExampleRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *ExampleRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets ExampleRef to a copy of data.
 
@@ -466,11 +530,16 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er
 type HeaderRef struct {
 	Ref   string
 	Value *Header
+
 	// Has unexported fields.
 }
     HeaderRef represents either a Header or a $ref to a Header. When serializing
     and both fields are set, Ref is preferred over Value.
 
+func (x *HeaderRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *HeaderRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -481,6 +550,12 @@ func (x HeaderRef) MarshalJSON() ([]byte, error)
 func (x HeaderRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of HeaderRef.
 
+func (x *HeaderRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *HeaderRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *HeaderRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets HeaderRef to a copy of data.
 
@@ -568,11 +643,16 @@ func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error
 type LinkRef struct {
 	Ref   string
 	Value *Link
+
 	// Has unexported fields.
 }
     LinkRef represents either a Link or a $ref to a Link. When serializing and
     both fields are set, Ref is preferred over Value.
 
+func (x *LinkRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *LinkRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -583,6 +663,12 @@ func (x LinkRef) MarshalJSON() ([]byte, error)
 func (x LinkRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of LinkRef.
 
+func (x *LinkRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *LinkRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *LinkRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets LinkRef to a copy of data.
 
@@ -879,11 +965,16 @@ func (parameter *Parameter) WithSchema(value *Schema) *Parameter
 type ParameterRef struct {
 	Ref   string
 	Value *Parameter
+
 	// Has unexported fields.
 }
     ParameterRef represents either a Parameter or a $ref to a Parameter.
     When serializing and both fields are set, Ref is preferred over Value.
 
+func (x *ParameterRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *ParameterRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -894,6 +985,12 @@ func (x ParameterRef) MarshalJSON() ([]byte, error)
 func (x ParameterRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of ParameterRef.
 
+func (x *ParameterRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *ParameterRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *ParameterRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets ParameterRef to a copy of data.
 
@@ -1112,11 +1209,16 @@ func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []strin
 type RequestBodyRef struct {
 	Ref   string
 	Value *RequestBody
+
 	// Has unexported fields.
 }
     RequestBodyRef represents either a RequestBody or a $ref to a RequestBody.
     When serializing and both fields are set, Ref is preferred over Value.
 
+func (x *RequestBodyRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *RequestBodyRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -1127,6 +1229,12 @@ func (x RequestBodyRef) MarshalJSON() ([]byte, error)
 func (x RequestBodyRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of RequestBodyRef.
 
+func (x *RequestBodyRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *RequestBodyRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *RequestBodyRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets RequestBodyRef to a copy of data.
 
@@ -1176,11 +1284,16 @@ func (m ResponseBodies) JSONLookup(token string) (interface{}, error)
 type ResponseRef struct {
 	Ref   string
 	Value *Response
+
 	// Has unexported fields.
 }
     ResponseRef represents either a Response or a $ref to a Response. When
     serializing and both fields are set, Ref is preferred over Value.
 
+func (x *ResponseRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *ResponseRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -1191,6 +1304,12 @@ func (x ResponseRef) MarshalJSON() ([]byte, error)
 func (x ResponseRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of ResponseRef.
 
+func (x *ResponseRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *ResponseRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *ResponseRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets ResponseRef to a copy of data.
 
@@ -1471,6 +1590,7 @@ func (err SchemaError) Unwrap() error
 type SchemaRef struct {
 	Ref   string
 	Value *Schema
+
 	// Has unexported fields.
 }
     SchemaRef represents either a Schema or a $ref to a Schema. When serializing
@@ -1479,6 +1599,10 @@ type SchemaRef struct {
 func NewSchemaRef(ref string, value *Schema) *SchemaRef
     NewSchemaRef simply builds a SchemaRef
 
+func (x *SchemaRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *SchemaRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -1489,6 +1613,12 @@ func (x SchemaRef) MarshalJSON() ([]byte, error)
 func (x SchemaRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of SchemaRef.
 
+func (x *SchemaRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *SchemaRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *SchemaRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets SchemaRef to a copy of data.
 
@@ -1620,12 +1750,17 @@ func (ss *SecurityScheme) WithType(value string) *SecurityScheme
 type SecuritySchemeRef struct {
 	Ref   string
 	Value *SecurityScheme
+
 	// Has unexported fields.
 }
     SecuritySchemeRef represents either a SecurityScheme or a $ref to a
     SecurityScheme. When serializing and both fields are set, Ref is preferred
     over Value.
 
+func (x *SecuritySchemeRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
 func (x *SecuritySchemeRef) JSONLookup(token string) (interface{}, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
@@ -1636,6 +1771,12 @@ func (x SecuritySchemeRef) MarshalJSON() ([]byte, error)
 func (x SecuritySchemeRef) MarshalYAML() (interface{}, error)
     MarshalYAML returns the YAML encoding of SecuritySchemeRef.
 
+func (x *SecuritySchemeRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *SecuritySchemeRef) RefString() string
+    RefString returns the $ref value.
+
 func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets SecuritySchemeRef to a copy of data.
 
diff --git a/README.md b/README.md
index aea156c..6e910bb 100644
--- a/README.md
+++ b/README.md
@@ -274,6 +274,31 @@ func safeErrorMessage(err *openapi3.SchemaError) string {
 }
 ```
 
+## Reconciling component $ref types
+
+`ReferencesComponentInRootDocument` is a useful helper function to check if a component reference
+coincides with a reference in the root document's component objects fixed fields.
+
+This can be used to determine if two schema definitions are of the same structure, helpful for
+code generation tools when generating go type models.
+
+```go
+doc, err = loader.LoadFromFile("openapi.yml")
+
+for _, path := range doc.Paths.InMatchingOrder() {
+	pathItem := doc.Paths.Find(path)
+
+	if pathItem.Get == nil || pathItem.Get.Responses.Status(200) {
+		continue
+	}
+
+	for _, s := range pathItem.Get.Responses.Status(200).Value.Content {
+		name, match := ReferencesComponentInRootDocument(doc, s.Schema)
+		fmt.Println(path, match, name) // /record true #/components/schemas/BookRecord
+	}
+}
+```
+
 This will change the schema validation errors to return only the `Reason` field, which is guaranteed to not include the original value.
 
 ## CHANGELOG: Sub-v1 breaking API changes
@@ -281,6 +306,9 @@ This will change the schema validation errors to return only the `Reason` field,
 ### v0.125.0
 * The `openapi3filter.ErrFunc` and `openapi3filter.LogFunc` func types now take the validated request's context as first argument.
 
+### v0.124.0
+* `openapi3.Schema.Type` & `openapi2.Parameter.Type` fields went from a `string` to the type `*Type` with methods: `Includes`, `Is`, `Permits` & `Slice`.
+
 ### v0.122.0
 * `Paths` field of `openapi3.T` is now a pointer
 * `Responses` field of `openapi3.Operation` is now a pointer
diff --git a/go.mod b/go.mod
index 3d6ff7e..10abe40 100644
--- a/go.mod
+++ b/go.mod
@@ -3,18 +3,18 @@ module github.com/getkin/kin-openapi
 go 1.20
 
 require (
-	github.com/go-openapi/jsonpointer v0.20.2
+	github.com/go-openapi/jsonpointer v0.21.0
 	github.com/gorilla/mux v1.8.1
-	github.com/invopop/yaml v0.2.0
+	github.com/invopop/yaml v0.3.1
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
 	github.com/perimeterx/marshmallow v1.1.5
-	github.com/stretchr/testify v1.8.4
+	github.com/stretchr/testify v1.9.0
 	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/go-openapi/swag v0.22.8 // indirect
+	github.com/go-openapi/swag v0.23.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 82537b7..fc84c31 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,14 @@
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
-github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
-github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
-github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
 github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
-github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
-github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
+github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
+github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -23,11 +23,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/openapi3/helpers.go b/openapi3/helpers.go
index d160eb1..25da8a7 100644
--- a/openapi3/helpers.go
+++ b/openapi3/helpers.go
@@ -2,7 +2,13 @@ package openapi3
 
 import (
 	"fmt"
+	"net/url"
+	"path"
+	"reflect"
 	"regexp"
+	"strings"
+
+	"github.com/go-openapi/jsonpointer"
 )
 
 const identifierPattern = `^[a-zA-Z0-9._-]+$`
@@ -39,3 +45,192 @@ func Int64Ptr(value int64) *int64 {
 func Uint64Ptr(value uint64) *uint64 {
 	return &value
 }
+
+type componentRef interface {
+	RefString() string
+	RefPath() *url.URL
+	CollectionName() string
+}
+
+// refersToSameDocument returns if the $ref refers to the same document.
+//
+// Documents in different directories will have distinct $ref values that resolve to
+// the same document.
+// For example, consider the 3 files:
+//
+//	/records.yaml
+//	/root.yaml         $ref: records.yaml
+//	/schema/other.yaml $ref: ../records.yaml
+//
+// The records.yaml reference in the 2 latter refers to the same document.
+func refersToSameDocument(o1 componentRef, o2 componentRef) bool {
+	if o1 == nil || o2 == nil {
+		return false
+	}
+
+	r1 := o1.RefPath()
+	r2 := o2.RefPath()
+
+	if r1 == nil || r2 == nil {
+		return false
+	}
+
+	// refURL is relative to the working directory & base spec file.
+	return referenceURIMatch(r1, r2)
+}
+
+// referencesRootDocument returns if the $ref points to the root document of the OpenAPI spec.
+//
+// If the document has no location, perhaps loaded from data in memory, it always returns false.
+func referencesRootDocument(doc *T, ref componentRef) bool {
+	if doc.url == nil || ref == nil || ref.RefPath() == nil {
+		return false
+	}
+
+	refURL := *ref.RefPath()
+	refURL.Fragment = ""
+
+	// Check referenced element was in the root document.
+	return referenceURIMatch(doc.url, &refURL)
+}
+
+func referenceURIMatch(u1 *url.URL, u2 *url.URL) bool {
+	s1, s2 := *u1, *u2
+	if s1.Scheme == "" {
+		s1.Scheme = "file"
+	}
+	if s2.Scheme == "" {
+		s2.Scheme = "file"
+	}
+
+	return s1.String() == s2.String()
+}
+
+// ReferencesComponentInRootDocument returns if the given component reference references
+// the same document or element as another component reference in the root document's
+// '#/components/<type>'. If it does, it returns the name of it in the form
+// '#/components/<type>/NameXXX'
+//
+// Of course given a component from the root document will always match itself.
+//
+// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
+// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls
+//
+// Example. Take the spec with directory structure:
+//
+//	openapi.yaml
+//	schemas/
+//	├─ record.yaml
+//	├─ records.yaml
+//
+// In openapi.yaml we have:
+//
+//	components:
+//	  schemas:
+//	    Record:
+//	      $ref: schemas/record.yaml
+//
+// Case 1: records.yml references a component in the root document
+//
+//	$ref: ../openapi.yaml#/components/schemas/Record
+//
+// This would return...
+//
+//	#/components/schemas/Record
+//
+// Case 2: records.yml indirectly refers to the same schema
+// as a schema the root document's '#/components/schemas'.
+//
+//	$ref: ./record.yaml
+//
+// This would also return...
+//
+//	#/components/schemas/Record
+func ReferencesComponentInRootDocument(doc *T, ref componentRef) (string, bool) {
+	if ref == nil || ref.RefString() == "" {
+		return "", false
+	}
+
+	// Case 1:
+	// Something like: ../another-folder/document.json#/myElement
+	if isRemoteReference(ref.RefString()) && isRootComponentReference(ref.RefString(), ref.CollectionName()) {
+		// Determine if it is *this* root doc.
+		if referencesRootDocument(doc, ref) {
+			_, name, _ := strings.Cut(ref.RefString(), path.Join("#/components/", ref.CollectionName()))
+
+			return path.Join("#/components/", ref.CollectionName(), name), true
+		}
+	}
+
+	// If there are no schemas defined in the root document return early.
+	if doc.Components == nil {
+		return "", false
+	}
+
+	collection, _, err := jsonpointer.GetForToken(doc.Components, ref.CollectionName())
+	if err != nil {
+		panic(err) // unreachable
+	}
+
+	var components map[string]componentRef
+
+	componentRefType := reflect.TypeOf(new(componentRef)).Elem()
+	if t := reflect.TypeOf(collection); t.Kind() == reflect.Map &&
+		t.Key().Kind() == reflect.String &&
+		t.Elem().AssignableTo(componentRefType) {
+		v := reflect.ValueOf(collection)
+
+		components = make(map[string]componentRef, v.Len())
+		for _, key := range v.MapKeys() {
+			strct := v.MapIndex(key)
+			// Type assertion safe, already checked via reflection above.
+			components[key.Interface().(string)] = strct.Interface().(componentRef)
+		}
+	} else {
+		return "", false
+	}
+
+	// Case 2:
+	// Something like: ../openapi.yaml#/components/schemas/myElement
+	for name, s := range components {
+		// Must be a reference to a YAML file.
+		if !isWholeDocumentReference(s.RefString()) {
+			continue
+		}
+
+		// Is the schema a ref to the same resource.
+		if !refersToSameDocument(s, ref) {
+			continue
+		}
+
+		// Transform the remote ref to the equivalent schema in the root document.
+		return path.Join("#/components/", ref.CollectionName(), name), true
+	}
+
+	return "", false
+}
+
+// isElementReference takes a $ref value and checks if it references a specific element.
+func isElementReference(ref string) bool {
+	return ref != "" && !isWholeDocumentReference(ref)
+}
+
+// isSchemaReference takes a $ref value and checks if it references a schema element.
+func isRootComponentReference(ref string, compType string) bool {
+	return isElementReference(ref) && strings.Contains(ref, path.Join("#/components/", compType))
+}
+
+// isWholeDocumentReference takes a $ref value and checks if it is whole document reference.
+func isWholeDocumentReference(ref string) bool {
+	return ref != "" && !strings.ContainsAny(ref, "#")
+}
+
+// isRemoteReference takes a $ref value and checks if it is remote reference.
+func isRemoteReference(ref string) bool {
+	return ref != "" && !strings.HasPrefix(ref, "#") && !isURLReference(ref)
+}
+
+// isURLReference takes a $ref value and checks if it is URL reference.
+func isURLReference(ref string) bool {
+	return strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") || strings.HasPrefix(ref, "//")
+}
diff --git a/openapi3/loader.go b/openapi3/loader.go
index 88ab566..e43f72b 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -173,6 +173,11 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR
 	}
 
 	doc := &T{}
+	if location != nil {
+		specURL := *location
+		doc.url = &specURL // shallow-copy
+	}
+
 	loader.visitedDocuments[uri] = doc
 
 	if err := unmarshal(data, doc); err != nil {
@@ -547,6 +552,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
 				return err
 			}
 			component.Value = &header
+			component.refPath = *documentPath
 		} else {
 			var resolved HeaderRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -560,6 +566,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	value := component.Value
@@ -597,6 +604,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
 				return err
 			}
 			component.Value = &param
+			component.refPath = *documentPath
 		} else {
 			var resolved ParameterRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -610,6 +618,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	value := component.Value
@@ -657,6 +666,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
 				return err
 			}
 			component.Value = &requestBody
+			component.refPath = *documentPath
 		} else {
 			var resolved RequestBodyRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -670,6 +680,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	value := component.Value
@@ -724,6 +735,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
 				return err
 			}
 			component.Value = &resp
+			component.refPath = *documentPath
 		} else {
 			var resolved ResponseRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -737,6 +749,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	value := component.Value
@@ -802,6 +815,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 				return err
 			}
 			component.Value = &schema
+			component.refPath = *documentPath
 		} else {
 			if visitedLimit(visited, ref) {
 				visited = append(visited, ref)
@@ -821,6 +835,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 		if loader.visitedSchema == nil {
 			loader.visitedSchema = make(map[*Schema]struct{})
@@ -893,6 +908,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
 				return err
 			}
 			component.Value = &scheme
+			component.refPath = *documentPath
 		} else {
 			var resolved SecuritySchemeRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -906,6 +922,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	return nil
@@ -933,6 +950,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
 				return err
 			}
 			component.Value = &example
+			component.refPath = *documentPath
 		} else {
 			var resolved ExampleRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -946,6 +964,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	return nil
@@ -973,6 +992,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 				return err
 			}
 			component.Value = &resolved
+			component.refPath = *documentPath
 		} else {
 			var resolved CallbackRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -986,6 +1006,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	value := component.Value
@@ -1023,6 +1044,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
 				return err
 			}
 			component.Value = &link
+			component.refPath = *documentPath
 		} else {
 			var resolved LinkRef
 			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
@@ -1036,6 +1058,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
 				return err
 			}
 			component.Value = resolved.Value
+			component.refPath = resolved.refPath
 		}
 	}
 	return nil
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 04bac8f..ea3c985 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"net/url"
 
 	"github.com/go-openapi/jsonpointer"
 )
@@ -24,6 +25,7 @@ type T struct {
 	ExternalDocs *ExternalDocs        `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
 
 	visited visitedComponent
+	url     *url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*T)(nil)
diff --git a/openapi3/refs.go b/openapi3/refs.go
index 087e5ab..34f9552 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"net/url"
 	"sort"
 
 	"github.com/go-openapi/jsonpointer"
@@ -17,12 +18,23 @@ type CallbackRef struct {
 	Ref   string
 	Value *Callback
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*CallbackRef)(nil)
 
 func (x *CallbackRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *CallbackRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *CallbackRef) CollectionName() string { return "callbacks" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *CallbackRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of CallbackRef.
 func (x CallbackRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -96,12 +108,23 @@ type ExampleRef struct {
 	Ref   string
 	Value *Example
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*ExampleRef)(nil)
 
 func (x *ExampleRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *ExampleRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *ExampleRef) CollectionName() string { return "examples" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *ExampleRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of ExampleRef.
 func (x ExampleRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -175,12 +198,23 @@ type HeaderRef struct {
 	Ref   string
 	Value *Header
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*HeaderRef)(nil)
 
 func (x *HeaderRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *HeaderRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *HeaderRef) CollectionName() string { return "headers" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *HeaderRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of HeaderRef.
 func (x HeaderRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -254,12 +288,23 @@ type LinkRef struct {
 	Ref   string
 	Value *Link
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*LinkRef)(nil)
 
 func (x *LinkRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *LinkRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *LinkRef) CollectionName() string { return "links" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *LinkRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of LinkRef.
 func (x LinkRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -333,12 +378,23 @@ type ParameterRef struct {
 	Ref   string
 	Value *Parameter
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*ParameterRef)(nil)
 
 func (x *ParameterRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *ParameterRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *ParameterRef) CollectionName() string { return "parameters" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *ParameterRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of ParameterRef.
 func (x ParameterRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -412,12 +468,23 @@ type RequestBodyRef struct {
 	Ref   string
 	Value *RequestBody
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
 
 func (x *RequestBodyRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *RequestBodyRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *RequestBodyRef) CollectionName() string { return "requestBodies" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *RequestBodyRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of RequestBodyRef.
 func (x RequestBodyRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -491,12 +558,23 @@ type ResponseRef struct {
 	Ref   string
 	Value *Response
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
 
 func (x *ResponseRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *ResponseRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *ResponseRef) CollectionName() string { return "responses" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *ResponseRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of ResponseRef.
 func (x ResponseRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -570,12 +648,23 @@ type SchemaRef struct {
 	Ref   string
 	Value *Schema
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*SchemaRef)(nil)
 
 func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *SchemaRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *SchemaRef) CollectionName() string { return "schemas" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *SchemaRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of SchemaRef.
 func (x SchemaRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
@@ -649,12 +738,23 @@ type SecuritySchemeRef struct {
 	Ref   string
 	Value *SecurityScheme
 	extra []string
+
+	refPath url.URL
 }
 
 var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil)
 
 func (x *SecuritySchemeRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
 
+// RefString returns the $ref value.
+func (x *SecuritySchemeRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *SecuritySchemeRef) CollectionName() string { return "securitySchemes" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *SecuritySchemeRef) RefPath() *url.URL { return &x.refPath }
+
 // MarshalYAML returns the YAML encoding of SecuritySchemeRef.
 func (x SecuritySchemeRef) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
diff --git a/openapi3/refs.tmpl b/openapi3/refs.tmpl
index 638d646..31e6a06 100644
--- a/openapi3/refs.tmpl
+++ b/openapi3/refs.tmpl
@@ -5,34 +5,46 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"net/url"
 	"sort"
 
 	"github.com/go-openapi/jsonpointer"
 	"github.com/perimeterx/marshmallow"
 )
 {{ range $type := .Types }}
-// {{ $type }}Ref represents either a {{ $type }} or a $ref to a {{ $type }}.
+// {{ $type.Name }}Ref represents either a {{ $type.Name }} or a $ref to a {{ $type.Name }}.
 // When serializing and both fields are set, Ref is preferred over Value.
-type {{ $type }}Ref struct {
+type {{ $type.Name }}Ref struct {
 	Ref   string
-	Value *{{ $type }}
+	Value *{{ $type.Name }}
 	extra []string
+
+	refPath url.URL
 }
 
-var _ jsonpointer.JSONPointable = (*{{ $type }}Ref)(nil)
+var _ jsonpointer.JSONPointable = (*{{ $type.Name }}Ref)(nil)
+
+func (x *{{ $type.Name }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
+
+// RefString returns the $ref value.
+func (x *{{ $type.Name }}Ref) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *{{ $type.Name }}Ref) CollectionName() string { return "{{ $type.CollectionName }}" }
 
-func (x *{{ $type }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
+// RefPath returns the path of the $ref relative to the root document.
+func (x *{{ $type.Name }}Ref) RefPath() *url.URL { return &x.refPath }
 
-// MarshalYAML returns the YAML encoding of {{ $type }}Ref.
-func (x {{ $type }}Ref) MarshalYAML() (interface{}, error) {
+// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref.
+func (x {{ $type.Name }}Ref) MarshalYAML() (interface{}, error) {
 	if ref := x.Ref; ref != "" {
 		return &Ref{Ref: ref}, nil
 	}
 	return x.Value.MarshalYAML()
 }
 
-// MarshalJSON returns the JSON encoding of {{ $type }}Ref.
-func (x {{ $type }}Ref) MarshalJSON() ([]byte, error) {
+// MarshalJSON returns the JSON encoding of {{ $type.Name }}Ref.
+func (x {{ $type.Name }}Ref) MarshalJSON() ([]byte, error) {
 	y, err := x.MarshalYAML()
 	if err != nil {
 		return nil, err
@@ -40,8 +52,8 @@ func (x {{ $type }}Ref) MarshalJSON() ([]byte, error) {
 	return json.Marshal(y)
 }
 
-// UnmarshalJSON sets {{ $type }}Ref to a copy of data.
-func (x *{{ $type }}Ref) UnmarshalJSON(data []byte) error {
+// UnmarshalJSON sets {{ $type.Name }}Ref to a copy of data.
+func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error {
 	var refOnly Ref
 	if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
 		x.Ref = refOnly.Ref
@@ -57,8 +69,8 @@ func (x *{{ $type }}Ref) UnmarshalJSON(data []byte) error {
 	return json.Unmarshal(data, &x.Value)
 }
 
-// Validate returns an error if {{ $type }}Ref does not comply with the OpenAPI spec.
-func (x *{{ $type }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
+// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec.
+func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
 	if extra := x.extra; len(extra) != 0 {
 		extras := make([]string, 0, len(extra))
@@ -82,7 +94,7 @@ func (x *{{ $type }}Ref) Validate(ctx context.Context, opts ...ValidationOption)
 }
 
 // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
-func (x *{{ $type }}Ref) JSONLookup(token string) (interface{}, error) {
+func (x *{{ $type.Name }}Ref) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return x.Ref, nil
 	}
diff --git a/openapi3/refsgenerator.go b/openapi3/refsgenerator.go
index 5bddfe2..f3d66bb 100644
--- a/openapi3/refsgenerator.go
+++ b/openapi3/refsgenerator.go
@@ -27,21 +27,26 @@ func main() {
 
 	packageTemplate := template.Must(template.New("openapi3-refs").Parse(tmplData))
 
+	type componentType struct {
+		Name           string
+		CollectionName string
+	}
+
 	if err := packageTemplate.Execute(file, struct {
 		Package string
-		Types   []string
+		Types   []componentType
 	}{
 		Package: os.Getenv("GOPACKAGE"), // set by the go:generate directive
-		Types: []string{
-			"Callback",
-			"Example",
-			"Header",
-			"Link",
-			"Parameter",
-			"RequestBody",
-			"Response",
-			"Schema",
-			"SecurityScheme",
+		Types: []componentType{
+			{Name: "Callback", CollectionName: "callbacks"},
+			{Name: "Example", CollectionName: "examples"},
+			{Name: "Header", CollectionName: "headers"},
+			{Name: "Link", CollectionName: "links"},
+			{Name: "Parameter", CollectionName: "parameters"},
+			{Name: "RequestBody", CollectionName: "requestBodies"},
+			{Name: "Response", CollectionName: "responses"},
+			{Name: "Schema", CollectionName: "schemas"},
+			{Name: "SecurityScheme", CollectionName: "securitySchemes"},
 		},
 	}); err != nil {
 		panic(err)


Test patch:
diff --git a/openapi3/helpers_test.go b/openapi3/helpers_test.go
new file mode 100644
index 0000000..ff350e3
--- /dev/null
+++ b/openapi3/helpers_test.go
@@ -0,0 +1,83 @@
+package openapi3
+
+import (
+	"net/url"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestReferencesComponentInRootDocument(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+
+	runAssertions := func(doc *T) {
+		// The element type of ./records.yml references a document which is also in the root document.
+		v, ok := ReferencesComponentInRootDocument(doc, doc.Components.Schemas["BookRecords"].Value.Items)
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/schemas/BookRecord", v)
+
+		// The array element type directly references the component in the root document.
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Components.Schemas["CdRecords"].Value.Items)
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/schemas/CdRecord", v)
+
+		// A component from the root document should
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Components.Schemas["CdRecord"])
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/schemas/CdRecord", v)
+
+		// The error response component is in the root doc.
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Paths.Find("/records").Get.Responses.Value("500"))
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/responses/ErrorResponse", v)
+
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Paths.Find("/records").Get.Responses.Value("500").Value.Content.Get("application/json").Schema)
+		assert.False(t, ok)
+		assert.Empty(t, v)
+
+		// Ref path doesn't include a './'
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Paths.Find("/record").Get.Parameters[0])
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/parameters/BookIDParameter", v)
+
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Paths.Find("/record").Get.Responses.Value("200").Value.Content.Get("application/json").Examples["first-example"])
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/examples/RecordResponseExample", v)
+
+		// Matches equivalent paths where string is no equal.
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Paths.Find("/record").Get.Responses.Value("200").Value.Headers["X-Custom-Header"])
+		assert.True(t, ok)
+		assert.Equal(t, "#/components/headers/CustomHeader", v)
+
+		// Same structure distinct definition of the same header
+		v, ok = ReferencesComponentInRootDocument(doc, doc.Paths.Find("/record").Get.Responses.Value("200").Value.Headers["X-Custom-Header2"])
+		assert.False(t, ok)
+		assert.Empty(t, v)
+	}
+
+	// Load from the file system
+	doc, err := loader.LoadFromFile("testdata/refsToRoot/openapi.yml")
+	require.NoError(t, err)
+
+	runAssertions(doc)
+
+	// Loading from a URL by mocking HTTP calls.
+	// Loads the data using the URI path from the testdata/ folder.
+	loader.ReadFromURIFunc = func(loader *Loader, url *url.URL) ([]byte, error) {
+		localURL := *url
+		localURL.Scheme = ""
+		localURL.Host = ""
+		localURL.Path = filepath.Join("testdata", localURL.Path)
+
+		return ReadFromFile(loader, &localURL)
+	}
+
+	u, _ := url.Parse("https://example.com/refsToRoot/openapi.yml")
+	doc, err = loader.LoadFromURI(u)
+	require.NoError(t, err)
+
+	runAssertions(doc)
+}
diff --git a/openapi3/testdata/refsToRoot/openapi.yml b/openapi3/testdata/refsToRoot/openapi.yml
new file mode 100644
index 0000000..ad9fc1e
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/openapi.yml
@@ -0,0 +1,60 @@
+openapi: 3.0.3
+info:
+  version: 1.0.0
+  title: Mode ref resolution Example
+paths:
+  /records:
+    get:
+      operationId: getBookRecords
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/BookRecords'
+        500:
+          $ref: './other/response.yml'
+  /record:
+    get:
+      operationId: getBookRecord
+      parameters:
+        - $ref: 'other/parameter.yml'
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/BookRecord'
+              examples:
+                first-example:
+                  $ref: './other/example.yml'
+          headers:
+            X-Custom-Header:
+              $ref: 'schema/book/../../other/header.yml'
+            X-Custom-Header2:
+              schema:
+                type: string
+        500:
+          $ref: './other/response.yml'
+components:
+  schemas:
+    BookRecord:
+      $ref: './schemas/book/record.yml'
+    BookRecords:
+      $ref: './schemas/book/records.yml'
+    CdRecord:
+      $ref: './schemas/cd/record.yml'
+    CdRecords:
+      $ref: './schemas/cd/records.yml'
+  responses:
+    ErrorResponse:
+      $ref: './other/response.yml'
+  parameters:
+    BookIDParameter:
+      $ref: './other/parameter.yml'
+  headers:
+    CustomHeader:
+      $ref: './other/header.yml'
+  examples:
+    RecordResponseExample:
+      $ref: './other/example.yml'
diff --git a/openapi3/testdata/refsToRoot/other/example.yml b/openapi3/testdata/refsToRoot/other/example.yml
new file mode 100644
index 0000000..2137d10
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/other/example.yml
@@ -0,0 +1,2 @@
+description: Example example
+id: 42
diff --git a/openapi3/testdata/refsToRoot/other/header.yml b/openapi3/testdata/refsToRoot/other/header.yml
new file mode 100644
index 0000000..5fe26bf
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/other/header.yml
@@ -0,0 +1,3 @@
+description: Example
+schema:
+  type: string
diff --git a/openapi3/testdata/refsToRoot/other/parameter.yml b/openapi3/testdata/refsToRoot/other/parameter.yml
new file mode 100644
index 0000000..b30e0a3
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/other/parameter.yml
@@ -0,0 +1,2 @@
+name: id
+in: query
diff --git a/openapi3/testdata/refsToRoot/other/response.yml b/openapi3/testdata/refsToRoot/other/response.yml
new file mode 100644
index 0000000..e1766af
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/other/response.yml
@@ -0,0 +1,4 @@
+content:
+  application/json:
+    schema:
+      $ref: '../schemas/error.yml'
diff --git a/openapi3/testdata/refsToRoot/schemas/book/record.yml b/openapi3/testdata/refsToRoot/schemas/book/record.yml
new file mode 100644
index 0000000..0e383fc
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/schemas/book/record.yml
@@ -0,0 +1,6 @@
+type: object
+required:
+  - id
+properties:
+  id:
+    type: number
diff --git a/openapi3/testdata/refsToRoot/schemas/book/records.yml b/openapi3/testdata/refsToRoot/schemas/book/records.yml
new file mode 100644
index 0000000..5097d39
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/schemas/book/records.yml
@@ -0,0 +1,3 @@
+type: array
+items:
+  $ref: './record.yml'
diff --git a/openapi3/testdata/refsToRoot/schemas/cd/record.yml b/openapi3/testdata/refsToRoot/schemas/cd/record.yml
new file mode 100644
index 0000000..0e383fc
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/schemas/cd/record.yml
@@ -0,0 +1,6 @@
+type: object
+required:
+  - id
+properties:
+  id:
+    type: number
diff --git a/openapi3/testdata/refsToRoot/schemas/cd/records.yml b/openapi3/testdata/refsToRoot/schemas/cd/records.yml
new file mode 100644
index 0000000..22ea033
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/schemas/cd/records.yml
@@ -0,0 +1,3 @@
+type: array
+items:
+  $ref: '../../openapi.yml#/components/schemas/CdRecord'
diff --git a/openapi3/testdata/refsToRoot/schemas/error.yml b/openapi3/testdata/refsToRoot/schemas/error.yml
new file mode 100644
index 0000000..f2ed543
--- /dev/null
+++ b/openapi3/testdata/refsToRoot/schemas/error.yml
@@ -0,0 +1,6 @@
+type: object
+required:
+  - msg
+properties:
+  id:
+    type: string