Skip to content

Commit 713eff1

Browse files
fenollpirees
andauthored
openapi3: serialize Extensions when using $ref (#1131)
Co-authored-by: Ian Rees <ian@ianrees.net>
1 parent 88234d0 commit 713eff1

9 files changed

Lines changed: 131 additions & 17 deletions

File tree

.github/docs/openapi3.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,12 +1314,23 @@ func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc
13141314
documents.
13151315

13161316
type Ref struct {
1317-
Ref string `json:"$ref" yaml:"$ref"`
1318-
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
1317+
Ref string `json:"$ref" yaml:"$ref"`
1318+
Extensions map[string]any `json:"-" yaml:"-"`
1319+
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
13191320
}
13201321
Ref is specified by OpenAPI/Swagger 3.0 standard. See
13211322
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
13221323

1324+
func (x Ref) MarshalJSON() ([]byte, error)
1325+
MarshalJSON returns the JSON encoding of Ref.
1326+
1327+
func (x Ref) MarshalYAML() (any, error)
1328+
MarshalYAML returns the YAML encoding of Ref.
1329+
1330+
func (e *Ref) Validate(ctx context.Context, opts ...ValidationOption) error
1331+
Validate returns an error if Extensions does not comply with the OpenAPI
1332+
spec.
1333+
13231334
type RefNameResolver func(*T, ComponentRef) string
13241335
RefNameResolver maps a component to a name that is used as it's internalized
13251336
name.

openapi3/internalize_refs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsEx
347347

348348
// Discriminator mapping values are special cases since they are not full
349349
// ref objects but are string references to schema objects.
350-
if s.Discriminator != nil && s.Discriminator.Mapping != nil {
350+
if s.Discriminator != nil {
351351
for k, mapRef := range s.Discriminator.Mapping {
352352
s2 := (*SchemaRef)(&mapRef)
353353
isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)

openapi3/loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
975975
// Only resolve refs that look like external references (contain a path).
976976
// Plain schema names like "Dog" or internal refs like "#/components/schemas/Dog"
977977
// don't need to be resolved by the loader.
978-
if value.Discriminator != nil && value.Discriminator.Mapping != nil {
978+
if value.Discriminator != nil {
979979
for k, v := range value.Discriminator.Mapping {
980980
// Only resolve if it looks like an external ref (contains path separator)
981981
if strings.Contains(v.Ref, "/") && !strings.HasPrefix(v.Ref, "#") {

openapi3/ref.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
11
package openapi3
22

3+
import (
4+
"context"
5+
"encoding/json"
6+
)
7+
38
//go:generate go run refsgenerator.go
49

510
// Ref is specified by OpenAPI/Swagger 3.0 standard.
611
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
712
type Ref struct {
8-
Ref string `json:"$ref" yaml:"$ref"`
9-
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
13+
Ref string `json:"$ref" yaml:"$ref"`
14+
Extensions map[string]any `json:"-" yaml:"-"`
15+
Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"`
16+
}
17+
18+
// MarshalYAML returns the YAML encoding of Ref.
19+
func (x Ref) MarshalYAML() (any, error) {
20+
m := make(map[string]any, 1+len(x.Extensions))
21+
for k, v := range x.Extensions {
22+
m[k] = v
23+
}
24+
if x := x.Ref; x != "" {
25+
m["$ref"] = x
26+
}
27+
return m, nil
28+
}
29+
30+
// MarshalJSON returns the JSON encoding of Ref.
31+
func (x Ref) MarshalJSON() ([]byte, error) {
32+
y, err := x.MarshalYAML()
33+
if err != nil {
34+
return nil, err
35+
}
36+
return json.Marshal(y)
37+
}
38+
39+
// Validate returns an error if Extensions does not comply with the OpenAPI spec.
40+
func (e *Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
41+
ctx = WithValidationOptions(ctx, opts...)
42+
return validateExtensions(ctx, e.Extensions)
1043
}

openapi3/refs.go

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openapi3/refs.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) {
5454
// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref.
5555
func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) {
5656
if ref := x.Ref; ref != "" {
57-
return &Ref{Ref: ref}, nil
57+
return &Ref{Ref: ref, Extensions: x.Extensions}, nil
5858
}
5959
return x.Value.MarshalYAML()
6060
}

openapi3/refs_test.go

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openapi3/refs_test.tmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
{{ range $type := .Types }}
1313
func Test{{ $type.Name }}Ref_Extensions(t *testing.T) {
1414
data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`)
15+
expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`)
1516

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

38+
// Verify round trip JSON
39+
// Compare as string to make error message more readable if different
40+
outJson, err := ref.MarshalJSON()
41+
assert.NoError(t, err)
42+
assert.Equal(t, string(outJson), string(expectMarshalJson), "MarshalJSON output is not the same as input data")
43+
3744
// non-extension not json lookable
3845
_, err = ref.JSONLookup("something")
3946
assert.Error(t, err)

openapi3filter/validate_request.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) error {
105105
if options.RejectWhenRequestBodyNotSpecified && input.Request.ContentLength > 0 {
106106
err := &RequestError{
107107
Input: input,
108-
Err: fmt.Errorf("request body not allowed for this request"),
108+
Err: errors.New("request body not allowed for this request"),
109109
}
110110
if !options.MultiError {
111111
return err

0 commit comments

Comments
 (0)