Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openapi3: introduce ReferencesComponentInRootDocument(doc *T, ref componentRef) (string, bool) #945

Merged
merged 11 commits into from
Jun 16, 2024
141 changes: 141 additions & 0 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading