Repository: getkin/kin-openapi
Pull request #1007 (2024-08-25 10:48:21)

openapi3: reference originating locations in YAML specs - step 1
This PR adds the ability to correlate OpenAPI components with the location in the originating YAML spec.
Here's a summary of the changes required to achieve this:
1. Modify [yaml.v3 ](https://github.com/go-yaml/yaml/tree/v3) to add line and column numbers of each YAML element to the unmarshaled interfaces (see https://github.com/oasdiff/yaml3)
2. Modify [invopop/yaml](https://github.com/invopop/yaml) with a new function: `UnmarshalWithOrigin` (see https://github.com/oasdiff/yaml)
3. Modify this repo to add an `Origin` struct to OpenAPI components. This requires changes to the UnmarshalJSON functions and, in some cases, replacing native types, like `map[string]string`, by dedicated types that include the original native type and an Origin member.

This PR addresses issue https://github.com/getkin/kin-openapi/issues/986

Follow up tasks to be addressed by future PRs:
- Add `origin` to more elements, namely `Components` and some types that are unmarshalled with `unmarshalStringMapP` or `unmarshalStringMap`
- Merge changes in yaml.v3 and invopop/yaml back to original repos and remove `replace`  directives from go.mod
- Enhance `origin` to include end line and column and YAML comments as proposed by @fenollp


Patch:
diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt
index eddcbfb..44ab4d6 100644
--- a/.github/docs/openapi3.txt
+++ b/.github/docs/openapi3.txt
@@ -226,6 +226,7 @@ func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error
 
 type Callback struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	// Has unexported fields.
 }
@@ -274,6 +275,7 @@ type CallbackRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Callback
@@ -316,6 +318,9 @@ func (m Callbacks) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Callbacks to a copy of data.
+
 type ComponentRef interface {
 	RefString() string
 	RefPath() *url.URL
@@ -324,6 +329,7 @@ type ComponentRef interface {
 
 type Components struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Schemas         Schemas         `json:"schemas,omitempty" yaml:"schemas,omitempty"`
 	Parameters      ParametersMap   `json:"parameters,omitempty" yaml:"parameters,omitempty"`
@@ -355,6 +361,7 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp
 
 type Contact struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name  string `json:"name,omitempty" yaml:"name,omitempty"`
 	URL   string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -394,11 +401,15 @@ func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content
 
 func (content Content) Get(mime string) *MediaType
 
+func (content *Content) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Content to a copy of data.
+
 func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if Content does not comply with the OpenAPI spec.
 
 type Discriminator struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	PropertyName string    `json:"propertyName" yaml:"propertyName"` // required
 	Mapping      StringMap `json:"mapping,omitempty" yaml:"mapping,omitempty"`
@@ -421,6 +432,7 @@ func (discriminator *Discriminator) Validate(ctx context.Context, opts ...Valida
 
 type Encoding struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	ContentType   string  `json:"contentType,omitempty" yaml:"contentType,omitempty"`
 	Headers       Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -483,6 +495,7 @@ type ExampleRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Example
@@ -525,8 +538,12 @@ func (m Examples) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (examples *Examples) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Examples to a copy of data.
+
 type ExternalDocs struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Description string `json:"description,omitempty" yaml:"description,omitempty"`
 	URL         string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -593,6 +610,7 @@ type HeaderRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Header
@@ -635,8 +653,12 @@ func (m Headers) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (headers *Headers) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Headers to a copy of data.
+
 type Info struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Title          string   `json:"title" yaml:"title"` // Required
 	Description    string   `json:"description,omitempty" yaml:"description,omitempty"`
@@ -665,6 +687,7 @@ type IntegerFormatValidator = FormatValidator[int64]
 
 type License struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name string `json:"name" yaml:"name"` // Required
 	URL  string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -686,6 +709,7 @@ func (license *License) Validate(ctx context.Context, opts ...ValidationOption)
 
 type Link struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	OperationRef string         `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
 	OperationID  string         `json:"operationId,omitempty" yaml:"operationId,omitempty"`
@@ -713,6 +737,7 @@ type LinkRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Link
@@ -754,10 +779,16 @@ func (m Links) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (links *Links) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Links to a copy of data.
+
 type Loader struct {
 	// IsExternalRefsAllowed enables visiting other files
 	IsExternalRefsAllowed bool
 
+	// IncludeOrigin specifies whether to include the origin of the OpenAPI elements
+	IncludeOrigin bool
+
 	// ReadFromURIFunc allows overriding the any file/URL reading func
 	ReadFromURIFunc ReadFromURIFunc
 
@@ -793,8 +824,15 @@ func (loader *Loader) LoadFromURI(location *url.URL) (*T, error)
 func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error)
     ResolveRefsIn expands references if for instance spec was just unmarshaled
 
+type Location struct {
+	Line   int `json:"line,omitempty" yaml:"line,omitempty"`
+	Column int `json:"column,omitempty" yaml:"column,omitempty"`
+}
+    Location is a struct that contains the location of a field.
+
 type MediaType struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Schema   *SchemaRef           `json:"schema,omitempty" yaml:"schema,omitempty"`
 	Example  any                  `json:"example,omitempty" yaml:"example,omitempty"`
@@ -872,6 +910,7 @@ type NumberFormatValidator = FormatValidator[float64]
 
 type OAuthFlow struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	AuthorizationURL string    `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
 	TokenURL         string    `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
@@ -896,6 +935,7 @@ func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) e
 
 type OAuthFlows struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Implicit          *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
 	Password          *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
@@ -920,6 +960,7 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption)
 
 type Operation struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	// Optional tags for documentation.
 	Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
@@ -982,8 +1023,17 @@ func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOpti
     Validate returns an error if Operation does not comply with the OpenAPI
     spec.
 
+type Origin struct {
+	Key    *Location           `json:"key,omitempty" yaml:"key,omitempty"`
+	Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
+}
+    Origin contains the origin of a collection. Key is the location of the
+    collection itself. Fields is a map of the location of each field in the
+    collection.
+
 type Parameter struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name            string     `json:"name,omitempty" yaml:"name,omitempty"`
 	In              string     `json:"in,omitempty" yaml:"in,omitempty"`
@@ -1042,6 +1092,7 @@ type ParameterRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Parameter
@@ -1099,8 +1150,12 @@ func (m ParametersMap) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets ParametersMap to a copy of data.
+
 type PathItem struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Ref         string     `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 	Summary     string     `json:"summary,omitempty" yaml:"summary,omitempty"`
@@ -1140,6 +1195,7 @@ func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption
 
 type Paths struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	// Has unexported fields.
 }
@@ -1228,7 +1284,8 @@ func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc
     documents.
 
 type Ref struct {
-	Ref string `json:"$ref" yaml:"$ref"`
+	Ref    string  `json:"$ref" yaml:"$ref"`
+	Origin *Origin `json:"origin,omitempty" yaml:"origin,omitempty"`
 }
     Ref is specified by OpenAPI/Swagger 3.0 standard. See
     https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
@@ -1252,8 +1309,12 @@ func (m RequestBodies) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets RequestBodies to a copy of data.
+
 type RequestBody struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Description string  `json:"description,omitempty" yaml:"description,omitempty"`
 	Required    bool    `json:"required,omitempty" yaml:"required,omitempty"`
@@ -1301,6 +1362,7 @@ type RequestBodyRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *RequestBody
@@ -1339,6 +1401,7 @@ func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption)
 
 type Response struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Description *string `json:"description,omitempty" yaml:"description,omitempty"`
 	Headers     Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -1376,10 +1439,14 @@ func (m ResponseBodies) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets ResponseBodies to a copy of data.
+
 type ResponseRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Response
@@ -1418,6 +1485,7 @@ func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) er
 
 type Responses struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"-" yaml:"-"`
 
 	// Has unexported fields.
 }
@@ -1476,6 +1544,7 @@ func (responses *Responses) Value(key string) *ResponseRef
 
 type Schema struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	OneOf        SchemaRefs    `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
 	AnyOf        SchemaRefs    `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
@@ -1690,6 +1759,7 @@ type SchemaRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Schema
@@ -1784,6 +1854,9 @@ func (m Schemas) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (schemas *Schemas) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Schemas to a copy of data.
+
 type SecurityRequirement map[string][]string
     SecurityRequirement is specified by OpenAPI/Swagger standard version 3. See
     https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object
@@ -1792,6 +1865,9 @@ func NewSecurityRequirement() SecurityRequirement
 
 func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement
 
+func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets SecurityRequirement to a copy of data.
+
 func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if SecurityRequirement does not comply with the
     OpenAPI spec.
@@ -1808,6 +1884,7 @@ func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *
 
 type SecurityScheme struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Type             string      `json:"type,omitempty" yaml:"type,omitempty"`
 	Description      string      `json:"description,omitempty" yaml:"description,omitempty"`
@@ -1858,6 +1935,7 @@ type SecuritySchemeRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *SecurityScheme
@@ -1901,6 +1979,9 @@ func (m SecuritySchemes) JSONLookup(token string) (any, error)
     JSONLookup implements
     https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
 
+func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets SecuritySchemes to a copy of data.
+
 type SerializationMethod struct {
 	Style   string
 	Explode bool
@@ -1910,6 +1991,7 @@ type SerializationMethod struct {
 
 type Server struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	URL         string                     `json:"url" yaml:"url"` // Required
 	Description string                     `json:"description,omitempty" yaml:"description,omitempty"`
@@ -1940,6 +2022,7 @@ func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (e
 
 type ServerVariable struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Enum        []string `json:"enum,omitempty" yaml:"enum,omitempty"`
 	Default     string   `json:"default,omitempty" yaml:"default,omitempty"`
@@ -1987,6 +2070,9 @@ type StringMap map[string]string
     StringMap is a map[string]string that ignores the origin in the underlying
     json representation.
 
+func (stringMap *StringMap) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets StringMap to a copy of data.
+
 type T struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
 
@@ -2042,6 +2128,7 @@ func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error
 
 type Tag struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name         string        `json:"name,omitempty" yaml:"name,omitempty"`
 	Description  string        `json:"description,omitempty" yaml:"description,omitempty"`
diff --git a/go.mod b/go.mod
index 11bc6d0..7d37032 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,10 @@
 module github.com/getkin/kin-openapi
 
-go 1.20
+go 1.22.5
+
+replace gopkg.in/yaml.v3 => github.com/oasdiff/yaml3 v0.0.0-20240920135353-c185dc6ea7c6
+
+replace github.com/invopop/yaml => github.com/oasdiff/yaml v0.0.0-20240920191703-3e5a9fb5bdf3
 
 require (
 	github.com/go-openapi/jsonpointer v0.21.0
diff --git a/go.sum b/go.sum
index 6b91d0d..f9643cd 100644
--- a/go.sum
+++ b/go.sum
@@ -5,18 +5,23 @@ github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kO
 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/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-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=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/oasdiff/yaml v0.0.0-20240920191703-3e5a9fb5bdf3 h1:nqCxALSUgWobWkFGIrhLRzR/bpImQdGj+3JS4/scTJo=
+github.com/oasdiff/yaml v0.0.0-20240920191703-3e5a9fb5bdf3/go.mod h1:AOyUNV9ElKz7EEZeBm/48U54UtjtgCMT9fFbZEsClQc=
+github.com/oasdiff/yaml3 v0.0.0-20240920135353-c185dc6ea7c6 h1:+ZsuDTdapTJxfMQk7SOJiNMg0v36pui01L7FEO615r8=
+github.com/oasdiff/yaml3 v0.0.0-20240920135353-c185dc6ea7c6/go.mod h1:lqlOfJRrYpgeWHQj+ky2tf7UJ3PzgHTHRQEpc90nbp0=
 github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
 github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -26,7 +31,6 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
 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=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/maps.sh b/maps.sh
index 9cfd0ff..e2915bf 100755
--- a/maps.sh
+++ b/maps.sh
@@ -209,6 +209,17 @@ func (${name} ${type}) UnmarshalJSON(data []byte) (err error) {
 			continue
 		}
 
+		if k == originKey {
+			var data []byte
+			if data, err = json.Marshal(v); err != nil {
+				return
+			}
+			if err = json.Unmarshal(data, &x.Origin); err != nil {
+				return
+			}
+			continue
+		}
+
 		var data []byte
 		if data, err = json.Marshal(v); err != nil {
 			return
diff --git a/openapi3/callback.go b/openapi3/callback.go
index 34a6bea..3ae6da6 100644
--- a/openapi3/callback.go
+++ b/openapi3/callback.go
@@ -9,6 +9,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
 type Callback struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	m map[string]*PathItem
 }
@@ -52,3 +53,9 @@ func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption
 
 	return validateExtensions(ctx, callback.Extensions)
 }
+
+// UnmarshalJSON sets Callbacks to a copy of data.
+func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) {
+	*callbacks, _, err = unmarshalStringMapP[CallbackRef](data)
+	return
+}
diff --git a/openapi3/components.go b/openapi3/components.go
index 98c4b96..aecf8b6 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -25,6 +25,7 @@ type (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
 type Components struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Schemas         Schemas         `json:"schemas,omitempty" yaml:"schemas,omitempty"`
 	Parameters      ParametersMap   `json:"parameters,omitempty" yaml:"parameters,omitempty"`
@@ -94,6 +95,7 @@ func (components *Components) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "schemas")
 	delete(x.Extensions, "parameters")
 	delete(x.Extensions, "headers")
diff --git a/openapi3/contact.go b/openapi3/contact.go
index 6c76a6f..57cc801 100644
--- a/openapi3/contact.go
+++ b/openapi3/contact.go
@@ -9,6 +9,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
 type Contact struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name  string `json:"name,omitempty" yaml:"name,omitempty"`
 	URL   string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -50,6 +51,8 @@ func (contact *Contact) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "name")
 	delete(x.Extensions, "url")
 	delete(x.Extensions, "email")
diff --git a/openapi3/content.go b/openapi3/content.go
index 81b070e..73e301e 100644
--- a/openapi3/content.go
+++ b/openapi3/content.go
@@ -122,3 +122,9 @@ func (content Content) Validate(ctx context.Context, opts ...ValidationOption) e
 	}
 	return nil
 }
+
+// UnmarshalJSON sets Content to a copy of data.
+func (content *Content) UnmarshalJSON(data []byte) (err error) {
+	*content, _, err = unmarshalStringMapP[MediaType](data)
+	return
+}
diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go
index 16d2440..a8ab07b 100644
--- a/openapi3/discriminator.go
+++ b/openapi3/discriminator.go
@@ -9,6 +9,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
 type Discriminator struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	PropertyName string    `json:"propertyName" yaml:"propertyName"` // required
 	Mapping      StringMap `json:"mapping,omitempty" yaml:"mapping,omitempty"`
@@ -44,6 +45,8 @@ func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "propertyName")
 	delete(x.Extensions, "mapping")
 	if len(x.Extensions) == 0 {
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 1bcdaea..7eb507e 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -11,6 +11,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
 type Encoding struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	ContentType   string  `json:"contentType,omitempty" yaml:"contentType,omitempty"`
 	Headers       Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -80,6 +81,8 @@ func (encoding *Encoding) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "contentType")
 	delete(x.Extensions, "headers")
 	delete(x.Extensions, "style")
diff --git a/openapi3/example.go b/openapi3/example.go
index f9a7a6b..9d38e43 100644
--- a/openapi3/example.go
+++ b/openapi3/example.go
@@ -83,3 +83,9 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption)
 
 	return validateExtensions(ctx, example.Extensions)
 }
+
+// UnmarshalJSON sets Examples to a copy of data.
+func (examples *Examples) UnmarshalJSON(data []byte) (err error) {
+	*examples, _, err = unmarshalStringMapP[ExampleRef](data)
+	return
+}
diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go
index bd99511..f679414 100644
--- a/openapi3/external_docs.go
+++ b/openapi3/external_docs.go
@@ -12,6 +12,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
 type ExternalDocs struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Description string `json:"description,omitempty" yaml:"description,omitempty"`
 	URL         string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -49,6 +50,7 @@ func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "url")
 	if len(x.Extensions) == 0 {
diff --git a/openapi3/header.go b/openapi3/header.go
index dc54287..6b23db5 100644
--- a/openapi3/header.go
+++ b/openapi3/header.go
@@ -94,3 +94,9 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er
 	}
 	return nil
 }
+
+// UnmarshalJSON sets Headers to a copy of data.
+func (headers *Headers) UnmarshalJSON(data []byte) (err error) {
+	*headers, _, err = unmarshalStringMapP[HeaderRef](data)
+	return
+}
diff --git a/openapi3/info.go b/openapi3/info.go
index e246828..acca2f4 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -10,6 +10,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
 type Info struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Title          string   `json:"title" yaml:"title"` // Required
 	Description    string   `json:"description,omitempty" yaml:"description,omitempty"`
@@ -62,6 +63,7 @@ func (info *Info) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "title")
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "termsOfService")
diff --git a/openapi3/license.go b/openapi3/license.go
index c4f6c8d..df9d6c4 100644
--- a/openapi3/license.go
+++ b/openapi3/license.go
@@ -10,6 +10,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
 type License struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name string `json:"name" yaml:"name"` // Required
 	URL  string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -45,6 +46,7 @@ func (license *License) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "name")
 	delete(x.Extensions, "url")
 	if len(x.Extensions) == 0 {
diff --git a/openapi3/link.go b/openapi3/link.go
index 132f678..dfd320f 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -11,6 +11,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
 type Link struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	OperationRef string         `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
 	OperationID  string         `json:"operationId,omitempty" yaml:"operationId,omitempty"`
@@ -66,6 +67,8 @@ func (link *Link) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "operationRef")
 	delete(x.Extensions, "operationId")
 	delete(x.Extensions, "description")
@@ -92,3 +95,9 @@ func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error
 
 	return validateExtensions(ctx, link.Extensions)
 }
+
+// UnmarshalJSON sets Links to a copy of data.
+func (links *Links) UnmarshalJSON(data []byte) (err error) {
+	*links, _, err = unmarshalStringMapP[LinkRef](data)
+	return
+}
diff --git a/openapi3/loader.go b/openapi3/loader.go
index 31c3407..0f3b8cb 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -28,6 +28,9 @@ type Loader struct {
 	// IsExternalRefsAllowed enables visiting other files
 	IsExternalRefsAllowed bool
 
+	// IncludeOrigin specifies whether to include the origin of the OpenAPI elements
+	IncludeOrigin bool
+
 	// ReadFromURIFunc allows overriding the any file/URL reading func
 	ReadFromURIFunc ReadFromURIFunc
 
@@ -103,7 +106,7 @@ func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, el
 	if err != nil {
 		return nil, err
 	}
-	if err := unmarshal(data, element); err != nil {
+	if err := unmarshal(data, element, loader.IncludeOrigin); err != nil {
 		return nil, err
 	}
 
@@ -139,7 +142,7 @@ func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error) {
 func (loader *Loader) LoadFromData(data []byte) (*T, error) {
 	loader.resetVisitedPathItemRefs()
 	doc := &T{}
-	if err := unmarshal(data, doc); err != nil {
+	if err := unmarshal(data, doc, loader.IncludeOrigin); err != nil {
 		return nil, err
 	}
 	if err := loader.ResolveRefsIn(doc, nil); err != nil {
@@ -168,7 +171,7 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR
 	doc := &T{}
 	loader.visitedDocuments[uri] = doc
 
-	if err := unmarshal(data, doc); err != nil {
+	if err := unmarshal(data, doc, loader.IncludeOrigin); err != nil {
 		return nil, err
 	}
 
@@ -422,7 +425,7 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv
 		if err2 != nil {
 			return nil, nil, err
 		}
-		if err2 = unmarshal(data, &cursor); err2 != nil {
+		if err2 = unmarshal(data, &cursor, loader.IncludeOrigin); err2 != nil {
 			return nil, nil, err
 		}
 		if cursor, err2 = drill(cursor); err2 != nil || cursor == nil {
diff --git a/openapi3/maplike.go b/openapi3/maplike.go
index 7b8045c..35b3365 100644
--- a/openapi3/maplike.go
+++ b/openapi3/maplike.go
@@ -125,6 +125,17 @@ func (responses *Responses) UnmarshalJSON(data []byte) (err error) {
 			continue
 		}
 
+		if k == originKey {
+			var data []byte
+			if data, err = json.Marshal(v); err != nil {
+				return
+			}
+			if err = json.Unmarshal(data, &x.Origin); err != nil {
+				return
+			}
+			continue
+		}
+
 		var data []byte
 		if data, err = json.Marshal(v); err != nil {
 			return
@@ -256,6 +267,17 @@ func (callback *Callback) UnmarshalJSON(data []byte) (err error) {
 			continue
 		}
 
+		if k == originKey {
+			var data []byte
+			if data, err = json.Marshal(v); err != nil {
+				return
+			}
+			if err = json.Unmarshal(data, &x.Origin); err != nil {
+				return
+			}
+			continue
+		}
+
 		var data []byte
 		if data, err = json.Marshal(v); err != nil {
 			return
@@ -387,6 +409,17 @@ func (paths *Paths) UnmarshalJSON(data []byte) (err error) {
 			continue
 		}
 
+		if k == originKey {
+			var data []byte
+			if data, err = json.Marshal(v); err != nil {
+				return
+			}
+			if err = json.Unmarshal(data, &x.Origin); err != nil {
+				return
+			}
+			continue
+		}
+
 		var data []byte
 		if data, err = json.Marshal(v); err != nil {
 			return
diff --git a/openapi3/marsh.go b/openapi3/marsh.go
index daa9375..9fdc6df 100644
--- a/openapi3/marsh.go
+++ b/openapi3/marsh.go
@@ -16,7 +16,7 @@ func unmarshalError(jsonUnmarshalErr error) error {
 	return jsonUnmarshalErr
 }
 
-func unmarshal(data []byte, v any) error {
+func unmarshal(data []byte, v any, includeOrigin bool) error {
 	var jsonErr, yamlErr error
 
 	// See https://github.com/getkin/kin-openapi/issues/680
@@ -25,7 +25,7 @@ func unmarshal(data []byte, v any) error {
 	}
 
 	// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
-	if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil {
+	if yamlErr = yaml.UnmarshalWithOrigin(data, v, includeOrigin); yamlErr == nil {
 		return nil
 	}
 
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index d4466bc..d6edaf4 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -14,6 +14,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
 type MediaType struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Schema   *SchemaRef           `json:"schema,omitempty" yaml:"schema,omitempty"`
 	Example  any                  `json:"example,omitempty" yaml:"example,omitempty"`
@@ -101,6 +102,7 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "schema")
 	delete(x.Extensions, "example")
 	delete(x.Extensions, "examples")
diff --git a/openapi3/operation.go b/openapi3/operation.go
index 40abf73..7b57e84 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -14,6 +14,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
 type Operation struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	// Optional tags for documentation.
 	Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
@@ -116,6 +117,7 @@ func (operation *Operation) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "tags")
 	delete(x.Extensions, "summary")
 	delete(x.Extensions, "description")
diff --git a/openapi3/origin.go b/openapi3/origin.go
new file mode 100644
index 0000000..b0e4a93
--- /dev/null
+++ b/openapi3/origin.go
@@ -0,0 +1,17 @@
+package openapi3
+
+const originKey = "origin"
+
+// Origin contains the origin of a collection.
+// Key is the location of the collection itself.
+// Fields is a map of the location of each field in the collection.
+type Origin struct {
+	Key    *Location           `json:"key,omitempty" yaml:"key,omitempty"`
+	Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"`
+}
+
+// Location is a struct that contains the location of a field.
+type Location struct {
+	Line   int `json:"line,omitempty" yaml:"line,omitempty"`
+	Column int `json:"column,omitempty" yaml:"column,omitempty"`
+}
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 34fe291..d69bd5f 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -73,6 +73,7 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
 type Parameter struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name            string     `json:"name,omitempty" yaml:"name,omitempty"`
 	In              string     `json:"in,omitempty" yaml:"in,omitempty"`
@@ -216,6 +217,7 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error {
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
 
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "name")
 	delete(x.Extensions, "in")
 	delete(x.Extensions, "description")
@@ -414,3 +416,9 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti
 
 	return validateExtensions(ctx, parameter.Extensions)
 }
+
+// UnmarshalJSON sets ParametersMap to a copy of data.
+func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error) {
+	*parametersMap, _, err = unmarshalStringMapP[ParameterRef](data)
+	return
+}
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 859634f..2c60472 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -12,6 +12,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
 type PathItem struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Ref         string     `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 	Summary     string     `json:"summary,omitempty" yaml:"summary,omitempty"`
@@ -98,6 +99,7 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "$ref")
 	delete(x.Extensions, "summary")
 	delete(x.Extensions, "description")
diff --git a/openapi3/paths.go b/openapi3/paths.go
index 7674741..8067504 100644
--- a/openapi3/paths.go
+++ b/openapi3/paths.go
@@ -11,6 +11,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
 type Paths struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	m map[string]*PathItem
 }
diff --git a/openapi3/ref.go b/openapi3/ref.go
index 0706073..d24f4c9 100644
--- a/openapi3/ref.go
+++ b/openapi3/ref.go
@@ -5,5 +5,6 @@ package openapi3
 // Ref is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
 type Ref struct {
-	Ref string `json:"$ref" yaml:"$ref"`
+	Ref    string  `json:"$ref" yaml:"$ref"`
+	Origin *Origin `json:"origin,omitempty" yaml:"origin,omitempty"`
 }
diff --git a/openapi3/refs.go b/openapi3/refs.go
index d337b0e..fc11164 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -19,6 +19,7 @@ type CallbackRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Callback
@@ -72,6 +73,7 @@ func (x *CallbackRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -155,6 +157,7 @@ type ExampleRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Example
@@ -208,6 +211,7 @@ func (x *ExampleRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -291,6 +295,7 @@ type HeaderRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Header
@@ -344,6 +349,7 @@ func (x *HeaderRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -427,6 +433,7 @@ type LinkRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Link
@@ -480,6 +487,7 @@ func (x *LinkRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -563,6 +571,7 @@ type ParameterRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Parameter
@@ -616,6 +625,7 @@ func (x *ParameterRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -699,6 +709,7 @@ type RequestBodyRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *RequestBody
@@ -752,6 +763,7 @@ func (x *RequestBodyRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -835,6 +847,7 @@ type ResponseRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Response
@@ -888,6 +901,7 @@ func (x *ResponseRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -971,6 +985,7 @@ type SchemaRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *Schema
@@ -1024,6 +1039,7 @@ func (x *SchemaRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
@@ -1107,6 +1123,7 @@ type SecuritySchemeRef struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *SecurityScheme
@@ -1160,6 +1177,7 @@ func (x *SecuritySchemeRef) 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
diff --git a/openapi3/refs.tmpl b/openapi3/refs.tmpl
index a3f5bda..028deba 100644
--- a/openapi3/refs.tmpl
+++ b/openapi3/refs.tmpl
@@ -19,6 +19,7 @@ type {{ $type.Name }}Ref struct {
 	// Extensions only captures fields starting with 'x-' as no other fields
 	// are allowed by the openapi spec.
 	Extensions map[string]any
+	Origin     *Origin
 
 	Ref   string
 	Value *{{ $type.Name }}
@@ -72,6 +73,7 @@ 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
+		x.Origin = refOnly.Origin
 		if len(extra) != 0 {
 			x.extra = make([]string, 0, len(extra))
 			for key := range extra {
diff --git a/openapi3/request_body.go b/openapi3/request_body.go
index 6d4b818..39288ee 100644
--- a/openapi3/request_body.go
+++ b/openapi3/request_body.go
@@ -10,6 +10,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
 type RequestBody struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Description string  `json:"description,omitempty" yaml:"description,omitempty"`
 	Required    bool    `json:"required,omitempty" yaml:"required,omitempty"`
@@ -108,6 +109,7 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "required")
 	delete(x.Extensions, "content")
@@ -136,3 +138,9 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation
 
 	return validateExtensions(ctx, requestBody.Extensions)
 }
+
+// UnmarshalJSON sets RequestBodies to a copy of data.
+func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error) {
+	*requestBodies, _, err = unmarshalStringMapP[RequestBodyRef](data)
+	return
+}
diff --git a/openapi3/response.go b/openapi3/response.go
index af8fda6..d4bfe42 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -12,6 +12,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
 type Responses struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"-" yaml:"-"`
 
 	m map[string]*ResponseRef
 }
@@ -102,6 +103,7 @@ func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOpti
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
 type Response struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Description *string `json:"description,omitempty" yaml:"description,omitempty"`
 	Headers     Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -171,6 +173,7 @@ func (response *Response) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "headers")
 	delete(x.Extensions, "content")
@@ -225,3 +228,9 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption
 
 	return validateExtensions(ctx, response.Extensions)
 }
+
+// UnmarshalJSON sets ResponseBodies to a copy of data.
+func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) {
+	*responseBodies, _, err = unmarshalStringMapP[ResponseRef](data)
+	return
+}
diff --git a/openapi3/schema.go b/openapi3/schema.go
index f811960..10a00ea 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -81,6 +81,7 @@ func (s SchemaRefs) JSONLookup(token string) (any, error) {
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
 type Schema struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	OneOf        SchemaRefs    `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
 	AnyOf        SchemaRefs    `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
@@ -411,6 +412,7 @@ func (schema *Schema) UnmarshalJSON(data []byte) error {
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
 
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "oneOf")
 	delete(x.Extensions, "anyOf")
 	delete(x.Extensions, "allOf")
@@ -2244,3 +2246,9 @@ func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) {
 func unsupportedFormat(format string) error {
 	return fmt.Errorf("unsupported 'format' value %q", format)
 }
+
+// UnmarshalJSON sets Schemas to a copy of data.
+func (schemas *Schemas) UnmarshalJSON(data []byte) (err error) {
+	*schemas, _, err = unmarshalStringMapP[SchemaRef](data)
+	return
+}
diff --git a/openapi3/security_requirements.go b/openapi3/security_requirements.go
index 87891c9..6af80e8 100644
--- a/openapi3/security_requirements.go
+++ b/openapi3/security_requirements.go
@@ -49,3 +49,9 @@ func (security *SecurityRequirement) Validate(ctx context.Context, opts ...Valid
 
 	return nil
 }
+
+// UnmarshalJSON sets SecurityRequirement to a copy of data.
+func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) {
+	*security, _, err = unmarshalStringMap[[]string](data)
+	return
+}
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index b37a6ed..7bc889d 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -12,6 +12,7 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
 type SecurityScheme struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Type             string      `json:"type,omitempty" yaml:"type,omitempty"`
 	Description      string      `json:"description,omitempty" yaml:"description,omitempty"`
@@ -100,6 +101,7 @@ func (ss *SecurityScheme) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "type")
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "name")
@@ -216,6 +218,7 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
 type OAuthFlows struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Implicit          *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
 	Password          *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
@@ -270,6 +273,7 @@ func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "implicit")
 	delete(x.Extensions, "password")
 	delete(x.Extensions, "clientCredentials")
@@ -316,6 +320,7 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption)
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
 type OAuthFlow struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	AuthorizationURL string    `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
 	TokenURL         string    `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
@@ -359,6 +364,8 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "authorizationUrl")
 	delete(x.Extensions, "tokenUrl")
 	delete(x.Extensions, "refreshUrl")
@@ -427,3 +434,9 @@ func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...
 
 	return flow.Validate(ctx, opts...)
 }
+
+// UnmarshalJSON sets SecuritySchemes to a copy of data.
+func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error) {
+	*securitySchemes, _, err = unmarshalStringMapP[SecuritySchemeRef](data)
+	return
+}
diff --git a/openapi3/server.go b/openapi3/server.go
index 7a2007f..5a817e5 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -52,6 +52,7 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
 type Server struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	URL         string                     `json:"url" yaml:"url"` // Required
 	Description string                     `json:"description,omitempty" yaml:"description,omitempty"`
@@ -115,6 +116,7 @@ func (server *Server) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "url")
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "variables")
@@ -235,6 +237,7 @@ func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (e
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
 type ServerVariable struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Enum        []string `json:"enum,omitempty" yaml:"enum,omitempty"`
 	Default     string   `json:"default,omitempty" yaml:"default,omitempty"`
@@ -276,6 +279,7 @@ func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "enum")
 	delete(x.Extensions, "default")
 	delete(x.Extensions, "description")
diff --git a/openapi3/stringmap.go b/openapi3/stringmap.go
index 3819851..354a4c8 100644
--- a/openapi3/stringmap.go
+++ b/openapi3/stringmap.go
@@ -1,4 +1,76 @@
 package openapi3
 
+import "encoding/json"
+
 // StringMap is a map[string]string that ignores the origin in the underlying json representation.
 type StringMap map[string]string
+
+// UnmarshalJSON sets StringMap to a copy of data.
+func (stringMap *StringMap) UnmarshalJSON(data []byte) (err error) {
+	*stringMap, _, err = unmarshalStringMap[string](data)
+	return
+}
+
+// unmarshalStringMapP unmarshals given json into a map[string]*V
+func unmarshalStringMapP[V any](data []byte) (map[string]*V, *Origin, error) {
+	var m map[string]any
+	if err := json.Unmarshal(data, &m); err != nil {
+		return nil, nil, err
+	}
+
+	origin, err := deepCast[Origin](m[originKey])
+	if err != nil {
+		return nil, nil, err
+	}
+	delete(m, originKey)
+
+	result := make(map[string]*V, len(m))
+	for k, v := range m {
+		value, err := deepCast[V](v)
+		if err != nil {
+			return nil, nil, err
+		}
+		result[k] = value
+	}
+
+	return result, origin, nil
+}
+
+// unmarshalStringMap unmarshals given json into a map[string]V
+func unmarshalStringMap[V any](data []byte) (map[string]V, *Origin, error) {
+	var m map[string]any
+	if err := json.Unmarshal(data, &m); err != nil {
+		return nil, nil, err
+	}
+
+	origin, err := deepCast[Origin](m[originKey])
+	if err != nil {
+		return nil, nil, err
+	}
+	delete(m, originKey)
+
+	result := make(map[string]V, len(m))
+	for k, v := range m {
+		value, err := deepCast[V](v)
+		if err != nil {
+			return nil, nil, err
+		}
+		result[k] = *value
+	}
+
+	return result, origin, nil
+}
+
+// deepCast casts any value to a value of type V.
+func deepCast[V any](value any) (*V, error) {
+	data, err := json.Marshal(value)
+	if err != nil {
+		return nil, err
+	}
+
+	var result V
+	if err = json.Unmarshal(data, &result); err != nil {
+		return nil, err
+	}
+	return &result, nil
+}
diff --git a/openapi3/tag.go b/openapi3/tag.go
index 182d050..5e4086f 100644
--- a/openapi3/tag.go
+++ b/openapi3/tag.go
@@ -34,6 +34,7 @@ func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
 type Tag struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
+	Origin     *Origin        `json:"origin,omitempty" yaml:"origin,omitempty"`
 
 	Name         string        `json:"name,omitempty" yaml:"name,omitempty"`
 	Description  string        `json:"description,omitempty" yaml:"description,omitempty"`
@@ -75,6 +76,7 @@ func (t *Tag) UnmarshalJSON(data []byte) error {
 		return unmarshalError(err)
 	}
 	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, originKey)
 	delete(x.Extensions, "name")
 	delete(x.Extensions, "description")
 	delete(x.Extensions, "externalDocs")


Test patch:
diff --git a/openapi3/origin_test.go b/openapi3/origin_test.go
new file mode 100644
index 0000000..56d8f1d
--- /dev/null
+++ b/openapi3/origin_test.go
@@ -0,0 +1,303 @@
+package openapi3
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestOrigin_All(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	const dir = "testdata/origin/"
+	items, _ := os.ReadDir(dir)
+	for _, item := range items {
+		t.Run(item.Name(), func(t *testing.T) {
+			doc, err := loader.LoadFromFile(fmt.Sprintf("%s/%s", dir, item.Name()))
+			require.NoError(t, err)
+			if doc.Paths == nil {
+				t.Skip("no paths")
+			}
+			require.NotEmpty(t, doc.Paths.Origin)
+		})
+	}
+}
+
+func TestOrigin_Info(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/simple.yaml")
+	require.NoError(t, err)
+
+	require.NotNil(t, doc.Info.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   2,
+			Column: 1,
+		},
+		doc.Info.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   3,
+			Column: 3,
+		},
+		doc.Info.Origin.Fields["title"])
+
+	require.Equal(t,
+		Location{
+			Line:   4,
+			Column: 3,
+		},
+		doc.Info.Origin.Fields["version"])
+}
+
+func TestOrigin_Paths(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/simple.yaml")
+	require.NoError(t, err)
+
+	require.NotNil(t, doc.Paths.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   5,
+			Column: 1,
+		},
+		doc.Paths.Origin.Key)
+
+	base := doc.Paths.Find("/partner-api/test/another-method")
+
+	require.NotNil(t, base.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   13,
+			Column: 3,
+		},
+		base.Origin.Key)
+
+	require.NotNil(t, base.Get.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   14,
+			Column: 5,
+		},
+		base.Get.Origin.Key)
+}
+
+func TestOrigin_RequestBody(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/request_body.yaml")
+	require.NoError(t, err)
+
+	base := doc.Paths.Find("/subscribe").Post.RequestBody.Value
+	require.NotNil(t, base.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   8,
+			Column: 7,
+		},
+		base.Origin.Key)
+
+	require.NotNil(t, base.Content["application/json"].Origin)
+	require.Equal(t,
+		&Location{
+			Line:   10,
+			Column: 11,
+		},
+		base.Content["application/json"].Origin.Key)
+}
+
+func TestOrigin_Responses(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/simple.yaml")
+	require.NoError(t, err)
+
+	base := doc.Paths.Find("/partner-api/test/another-method").Get.Responses
+	require.NotNil(t, base.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   17,
+			Column: 7,
+		},
+		base.Origin.Key)
+
+	require.NotNil(t, base.Origin)
+	require.Nil(t, base.Value("200").Origin)
+	require.Equal(t,
+		&Location{
+			Line:   18,
+			Column: 9,
+		},
+		base.Value("200").Value.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   19,
+			Column: 11,
+		},
+		base.Value("200").Value.Origin.Fields["description"])
+}
+
+func TestOrigin_Parameters(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/parameters.yaml")
+	require.NoError(t, err)
+
+	base := doc.Paths.Find("/api/test").Get.Parameters[0].Value
+	require.NotNil(t, base)
+	require.Equal(t,
+		&Location{
+			Line:   9,
+			Column: 11,
+		},
+		base.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   10,
+			Column: 11,
+		},
+		base.Origin.Fields["in"])
+
+	require.Equal(t,
+		Location{
+			Line:   9,
+			Column: 11,
+		},
+		base.Origin.Fields["name"])
+}
+
+func TestOrigin_SchemaInAdditionalProperties(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/additional_properties.yaml")
+	require.NoError(t, err)
+
+	base := doc.Paths.Find("/partner-api/test/some-method").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.AdditionalProperties
+	require.NotNil(t, base)
+
+	require.NotNil(t, base.Schema.Value.Origin)
+	require.Equal(t,
+		&Location{
+			Line:   14,
+			Column: 17,
+		},
+		base.Schema.Value.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   15,
+			Column: 19,
+		},
+		base.Schema.Value.Origin.Fields["type"])
+}
+
+func TestOrigin_ExternalDocs(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/external_docs.yaml")
+	require.NoError(t, err)
+
+	base := doc.ExternalDocs
+	require.NotNil(t, base.Origin)
+
+	require.Equal(t,
+		&Location{
+			Line:   13,
+			Column: 1,
+		},
+		base.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   14,
+			Column: 3,
+		},
+		base.Origin.Fields["description"])
+
+	require.Equal(t,
+		Location{
+			Line:   15,
+			Column: 3,
+		},
+		base.Origin.Fields["url"])
+}
+
+func TestOrigin_Security(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.IncludeOrigin = true
+	loader.Context = context.Background()
+
+	doc, err := loader.LoadFromFile("testdata/origin/security.yaml")
+	require.NoError(t, err)
+
+	base := doc.Components.SecuritySchemes["petstore_auth"].Value
+	require.NotNil(t, base)
+
+	require.Equal(t,
+		&Location{
+			Line:   29,
+			Column: 5,
+		},
+		base.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   30,
+			Column: 7,
+		},
+		base.Origin.Fields["type"])
+
+	require.Equal(t,
+		&Location{
+			Line:   31,
+			Column: 7,
+		},
+		base.Flows.Origin.Key)
+
+	require.Equal(t,
+		&Location{
+			Line:   32,
+			Column: 9,
+		},
+		base.Flows.Implicit.Origin.Key)
+
+	require.Equal(t,
+		Location{
+			Line:   33,
+			Column: 11,
+		},
+		base.Flows.Implicit.Origin.Fields["authorizationUrl"])
+}
diff --git a/openapi3/testdata/origin/additional_properties.yaml b/openapi3/testdata/origin/additional_properties.yaml
new file mode 100644
index 0000000..8e865bc
--- /dev/null
+++ b/openapi3/testdata/origin/additional_properties.yaml
@@ -0,0 +1,20 @@
+openapi: 3.0.1
+info:
+  title: Test API
+  version: v1
+paths:
+  /partner-api/test/some-method:
+    get:
+      responses:
+        "200":
+          description: Success
+          content:
+            application/json:
+              schema:
+                additionalProperties:
+                  type: object
+                  properties:
+                    code:
+                      type: integer
+                    text:
+                      type: string
\ No newline at end of file
diff --git a/openapi3/testdata/origin/external_docs.yaml b/openapi3/testdata/origin/external_docs.yaml
new file mode 100644
index 0000000..bfc5ba0
--- /dev/null
+++ b/openapi3/testdata/origin/external_docs.yaml
@@ -0,0 +1,15 @@
+openapi: 3.0.1
+info:
+  title: Test API
+  version: v1
+paths:
+  /partner-api/test/some-method:
+    get:
+      tags:
+        - Test
+      responses:
+        "200":
+          description: Success
+externalDocs:
+  description: API Documentation
+  url: https://openweathermap.org/api
\ No newline at end of file
diff --git a/openapi3/testdata/origin/parameters.yaml b/openapi3/testdata/origin/parameters.yaml
new file mode 100644
index 0000000..4d4299e
--- /dev/null
+++ b/openapi3/testdata/origin/parameters.yaml
@@ -0,0 +1,19 @@
+info:
+  title: Tufin
+  version: 1.0.0
+openapi: 3.0.3
+paths:
+  /api/test:
+    get:
+      parameters:
+        - name: a
+          in: query
+          schema:
+            type: integer
+      responses:
+        200:
+          description: OK
+    post:
+      responses:
+        201:
+          description: OK
diff --git a/openapi3/testdata/origin/request_body.yaml b/openapi3/testdata/origin/request_body.yaml
new file mode 100644
index 0000000..0943638
--- /dev/null
+++ b/openapi3/testdata/origin/request_body.yaml
@@ -0,0 +1,22 @@
+openapi: 3.0.0
+info:
+  title: Security Requirement Example
+  version: 1.0.0
+paths:
+  /subscribe:
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                inProgressUrl:
+                  type: string
+                failedUrl:
+                  type: string
+                successUrl:
+                  type: string
+      responses:
+        "200":
+          description: OK
diff --git a/openapi3/testdata/origin/security.yaml b/openapi3/testdata/origin/security.yaml
new file mode 100644
index 0000000..7594e96
--- /dev/null
+++ b/openapi3/testdata/origin/security.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.0
+info:
+  title: Security Requirement Example
+  version: 1.0.0
+paths:
+  /subscribe:
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                inProgressUrl:
+                  type: string
+                failedUrl:
+                  type: string
+                successUrl:
+                  type: string
+      responses:
+        "200":
+          description: OK
+      security:
+      - petstore_auth:
+        - write:pets
+        - read:pets
+components:
+  securitySchemes:
+    petstore_auth:
+      type: oauth2
+      flows:
+        implicit:
+          authorizationUrl: http://example.org/api/oauth/dialog
+          scopes:
+            write:pets: modify pets in your account
+            read:pets: read your pets
\ No newline at end of file
diff --git a/openapi3/testdata/origin/simple.yaml b/openapi3/testdata/origin/simple.yaml
new file mode 100644
index 0000000..53f3080
--- /dev/null
+++ b/openapi3/testdata/origin/simple.yaml
@@ -0,0 +1,19 @@
+openapi: 3.0.1
+info:
+  title: Test API
+  version: v1
+paths:
+  /partner-api/test/some-method:
+    get:
+      tags:
+        - Test
+      responses:
+        "200":
+          description: Success
+  /partner-api/test/another-method:
+    get:
+      tags:
+        - Test
+      responses:
+        "200":
+          description: Success