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: Serialize Extensions when using $ref #1005

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1228,11 +1228,22 @@ func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc
documents.

type Ref struct {
Ref string `json:"$ref" yaml:"$ref"`
Ref string `json:"$ref" yaml:"$ref"`
Extensions map[string]any `json:"-" yaml:"-"`
}
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

func (x Ref) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Ref.

func (x Ref) MarshalYAML() (any, error)
MarshalYAML returns the YAML encoding of Ref.

func (e *Ref) Validate(ctx context.Context, opts ...ValidationOption) error
Validate returns an error if Extensions does not comply with the OpenAPI
spec.

type RefNameResolver func(*T, ComponentRef) string
RefNameResolver maps a component to an name that is used as it's
internalized name.
Expand Down
35 changes: 34 additions & 1 deletion openapi3/ref.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
package openapi3

import (
"context"
"encoding/json"
)

//go:generate go run refsgenerator.go

// 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"`
Extensions map[string]any `json:"-" yaml:"-"`
}

// MarshalYAML returns the YAML encoding of Ref.
func (x Ref) MarshalYAML() (any, error) {
m := make(map[string]any, 1+len(x.Extensions))
for k, v := range x.Extensions {
m[k] = v
}
if x := x.Ref; x != "" {
m["$ref"] = x
}
return m, nil
}

// MarshalJSON returns the JSON encoding of Ref.
func (x Ref) MarshalJSON() ([]byte, error) {
y, err := x.MarshalYAML()
if err != nil {
return nil, err
}
return json.Marshal(y)
}

// Validate returns an error if Extensions does not comply with the OpenAPI spec.
func (e *Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
return validateExtensions(ctx, e.Extensions)
}
18 changes: 9 additions & 9 deletions openapi3/refs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion openapi3/refs.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) {
// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref.
func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
return &Ref{Ref: ref, Extensions: x.Extensions}, nil
}
return x.Value.MarshalYAML()
}
Expand Down
63 changes: 63 additions & 0 deletions openapi3/refs_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions openapi3/refs_test.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
{{ range $type := .Types }}
func Test{{ $type.Name }}Ref_Extensions(t *testing.T) {
data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`)
expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`)

ref := {{ $type.Name }}Ref{}
err := json.Unmarshal(data, &ref)
Expand All @@ -34,6 +35,12 @@ func Test{{ $type.Name }}Ref_Extensions(t *testing.T) {
err = ref.Validate(context.Background(), AllowExtraSiblingFields("something"))
assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined

// Verify round trip JSON
// Compare as string to make error message more readable if different
outJson, err := ref.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, string(outJson), string(expectMarshalJson), "MarshalJSON output is not the same as input data")

// non-extension not json lookable
_, err = ref.JSONLookup("something")
assert.Error(t, err)
Expand Down
Loading