Skip to content

Commit 546590b

Browse files
openapi3: fix resolving Callbacks (#757)
Co-authored-by: Pierre Fenoll <pierrefenoll@gmail.com> fix #341
1 parent 9145563 commit 546590b

File tree

5 files changed

+217
-103
lines changed

5 files changed

+217
-103
lines changed

openapi3/internalize_refs.go

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,16 @@ func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolve
7878
return false
7979
}
8080
name := refNameResolver(p.Ref)
81-
if _, ok := doc.Components.Parameters[name]; ok {
82-
p.Ref = "#/components/parameters/" + name
83-
return true
81+
if doc.Components != nil {
82+
if _, ok := doc.Components.Parameters[name]; ok {
83+
p.Ref = "#/components/parameters/" + name
84+
return true
85+
}
8486
}
8587

88+
if doc.Components == nil {
89+
doc.Components = &Components{}
90+
}
8691
if doc.Components.Parameters == nil {
8792
doc.Components.Parameters = make(ParametersMap)
8893
}
@@ -96,9 +101,15 @@ func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, par
96101
return false
97102
}
98103
name := refNameResolver(h.Ref)
99-
if _, ok := doc.Components.Headers[name]; ok {
100-
h.Ref = "#/components/headers/" + name
101-
return true
104+
if doc.Components != nil {
105+
if _, ok := doc.Components.Headers[name]; ok {
106+
h.Ref = "#/components/headers/" + name
107+
return true
108+
}
109+
}
110+
111+
if doc.Components == nil {
112+
doc.Components = &Components{}
102113
}
103114
if doc.Components.Headers == nil {
104115
doc.Components.Headers = make(Headers)
@@ -113,9 +124,15 @@ func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameRes
113124
return false
114125
}
115126
name := refNameResolver(r.Ref)
116-
if _, ok := doc.Components.RequestBodies[name]; ok {
117-
r.Ref = "#/components/requestBodies/" + name
118-
return true
127+
if doc.Components != nil {
128+
if _, ok := doc.Components.RequestBodies[name]; ok {
129+
r.Ref = "#/components/requestBodies/" + name
130+
return true
131+
}
132+
}
133+
134+
if doc.Components == nil {
135+
doc.Components = &Components{}
119136
}
120137
if doc.Components.RequestBodies == nil {
121138
doc.Components.RequestBodies = make(RequestBodies)
@@ -130,9 +147,15 @@ func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver,
130147
return false
131148
}
132149
name := refNameResolver(r.Ref)
133-
if _, ok := doc.Components.Responses[name]; ok {
134-
r.Ref = "#/components/responses/" + name
135-
return true
150+
if doc.Components != nil {
151+
if _, ok := doc.Components.Responses[name]; ok {
152+
r.Ref = "#/components/responses/" + name
153+
return true
154+
}
155+
}
156+
157+
if doc.Components == nil {
158+
doc.Components = &Components{}
136159
}
137160
if doc.Components.Responses == nil {
138161
doc.Components.Responses = make(Responses)
@@ -147,9 +170,15 @@ func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver Ref
147170
return
148171
}
149172
name := refNameResolver(ss.Ref)
150-
if _, ok := doc.Components.SecuritySchemes[name]; ok {
151-
ss.Ref = "#/components/securitySchemes/" + name
152-
return
173+
if doc.Components != nil {
174+
if _, ok := doc.Components.SecuritySchemes[name]; ok {
175+
ss.Ref = "#/components/securitySchemes/" + name
176+
return
177+
}
178+
}
179+
180+
if doc.Components == nil {
181+
doc.Components = &Components{}
153182
}
154183
if doc.Components.SecuritySchemes == nil {
155184
doc.Components.SecuritySchemes = make(SecuritySchemes)
@@ -164,9 +193,15 @@ func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, p
164193
return
165194
}
166195
name := refNameResolver(e.Ref)
167-
if _, ok := doc.Components.Examples[name]; ok {
168-
e.Ref = "#/components/examples/" + name
169-
return
196+
if doc.Components != nil {
197+
if _, ok := doc.Components.Examples[name]; ok {
198+
e.Ref = "#/components/examples/" + name
199+
return
200+
}
201+
}
202+
203+
if doc.Components == nil {
204+
doc.Components = &Components{}
170205
}
171206
if doc.Components.Examples == nil {
172207
doc.Components.Examples = make(Examples)
@@ -181,9 +216,15 @@ func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentI
181216
return
182217
}
183218
name := refNameResolver(l.Ref)
184-
if _, ok := doc.Components.Links[name]; ok {
185-
l.Ref = "#/components/links/" + name
186-
return
219+
if doc.Components != nil {
220+
if _, ok := doc.Components.Links[name]; ok {
221+
l.Ref = "#/components/links/" + name
222+
return
223+
}
224+
}
225+
226+
if doc.Components == nil {
227+
doc.Components = &Components{}
187228
}
188229
if doc.Components.Links == nil {
189230
doc.Components.Links = make(Links)
@@ -198,6 +239,10 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver,
198239
return false
199240
}
200241
name := refNameResolver(c.Ref)
242+
243+
if doc.Components == nil {
244+
doc.Components = &Components{}
245+
}
201246
if doc.Components.Callbacks == nil {
202247
doc.Components.Callbacks = make(Callbacks)
203248
}
@@ -293,6 +338,9 @@ func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, p
293338

294339
func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) {
295340
for _, ops := range paths {
341+
if isExternalRef(ops.Ref, parentIsExternal) {
342+
parentIsExternal = true
343+
}
296344
// inline full operations
297345
ops.Ref = ""
298346

openapi3/issue341_test.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package openapi3
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/stretchr/testify/require"
@@ -20,7 +21,43 @@ func TestIssue341(t *testing.T) {
2021

2122
bs, err := doc.MarshalJSON()
2223
require.NoError(t, err)
23-
require.JSONEq(t, `{"info":{"title":"test file","version":"n/a"},"openapi":"3.0.0","paths":{"/testpath":{"get":{"responses":{"200":{"$ref":"#/components/responses/testpath_200_response"}}}}}}`, string(bs))
24+
require.JSONEq(t, `{"info":{"title":"test file","version":"n/a"},"openapi":"3.0.0","paths":{"/testpath":{"$ref":"testpath.yaml#/paths/~1testpath"}}}`, string(bs))
2425

2526
require.Equal(t, "string", doc.Paths["/testpath"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
27+
28+
doc.InternalizeRefs(context.Background(), nil)
29+
bs, err = doc.MarshalJSON()
30+
require.NoError(t, err)
31+
require.JSONEq(t, `{
32+
"components": {
33+
"responses": {
34+
"testpath_200_response": {
35+
"content": {
36+
"application/json": {
37+
"schema": {
38+
"type": "string"
39+
}
40+
}
41+
},
42+
"description": "a custom response"
43+
}
44+
}
45+
},
46+
"info": {
47+
"title": "test file",
48+
"version": "n/a"
49+
},
50+
"openapi": "3.0.0",
51+
"paths": {
52+
"/testpath": {
53+
"get": {
54+
"responses": {
55+
"200": {
56+
"$ref": "#/components/responses/testpath_200_response"
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}`, string(bs))
2663
}

openapi3/issue753_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestIssue753(t *testing.T) {
10+
loader := NewLoader()
11+
12+
doc, err := loader.LoadFromFile("testdata/issue753.yml")
13+
require.NoError(t, err)
14+
15+
err = doc.Validate(loader.Context)
16+
require.NoError(t, err)
17+
18+
require.NotNil(t, (*doc.Paths["/test1"].Post.Callbacks["callback1"].Value)["{$request.body#/callback}"].Post.RequestBody.Value.Content["application/json"].Schema.Value)
19+
require.NotNil(t, (*doc.Paths["/test2"].Post.Callbacks["callback2"].Value)["{$request.body#/callback}"].Post.RequestBody.Value.Content["application/json"].Schema.Value)
20+
}

openapi3/loader.go

Lines changed: 37 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Loader struct {
4444

4545
visitedDocuments map[string]*T
4646

47+
visitedCallback map[*Callback]struct{}
4748
visitedExample map[*Example]struct{}
4849
visitedHeader map[*Header]struct{}
4950
visitedLink map[*Link]struct{}
@@ -243,11 +244,11 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
243244
}
244245

245246
// Visit all operations
246-
for entrypoint, pathItem := range doc.Paths {
247+
for _, pathItem := range doc.Paths {
247248
if pathItem == nil {
248249
continue
249250
}
250-
if err = loader.resolvePathItemRef(doc, entrypoint, pathItem, location); err != nil {
251+
if err = loader.resolvePathItemRef(doc, pathItem, location); err != nil {
251252
return
252253
}
253254
}
@@ -850,6 +851,16 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
850851
}
851852

852853
func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documentPath *url.URL) (err error) {
854+
if component != nil && component.Value != nil {
855+
if loader.visitedCallback == nil {
856+
loader.visitedCallback = make(map[*Callback]struct{})
857+
}
858+
if _, ok := loader.visitedCallback[component.Value]; ok {
859+
return nil
860+
}
861+
loader.visitedCallback[component.Value] = struct{}{}
862+
}
863+
853864
if component == nil {
854865
return errors.New("invalid callback: value MUST be an object")
855866
}
@@ -878,57 +889,8 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
878889
return nil
879890
}
880891

881-
for entrypoint, pathItem := range *value {
882-
entrypoint, pathItem := entrypoint, pathItem
883-
err = func() (err error) {
884-
key := "-"
885-
if documentPath != nil {
886-
key = documentPath.EscapedPath()
887-
}
888-
key += entrypoint
889-
if _, ok := loader.visitedPathItemRefs[key]; ok {
890-
return nil
891-
}
892-
loader.visitedPathItemRefs[key] = struct{}{}
893-
894-
if pathItem == nil {
895-
return errors.New("invalid path item: value MUST be an object")
896-
}
897-
ref := pathItem.Ref
898-
if ref != "" {
899-
if isSingleRefElement(ref) {
900-
var p PathItem
901-
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
902-
return err
903-
}
904-
*pathItem = p
905-
} else {
906-
if doc, ref, documentPath, err = loader.resolveRef(doc, ref, documentPath); err != nil {
907-
return
908-
}
909-
910-
rest := strings.TrimPrefix(ref, "#/components/callbacks/")
911-
if rest == ref {
912-
return fmt.Errorf(`expected prefix "#/components/callbacks/" in URI %q`, ref)
913-
}
914-
id := unescapeRefString(rest)
915-
916-
if doc.Components == nil || doc.Components.Callbacks == nil {
917-
return failedToResolveRefFragmentPart(ref, "callbacks")
918-
}
919-
resolved := doc.Components.Callbacks[id]
920-
if resolved == nil {
921-
return failedToResolveRefFragmentPart(ref, id)
922-
}
923-
924-
for _, p := range *resolved.Value {
925-
*pathItem = *p
926-
break
927-
}
928-
}
929-
}
930-
return loader.resolvePathItemRefContinued(doc, pathItem, documentPath)
931-
}()
892+
for _, pathItem := range *value {
893+
err := loader.resolvePathItemRef(doc, pathItem, documentPath)
932894
if err != nil {
933895
return err
934896
}
@@ -973,48 +935,42 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
973935
return nil
974936
}
975937

976-
func (loader *Loader) resolvePathItemRef(doc *T, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) {
977-
key := "_"
978-
if documentPath != nil {
979-
key = documentPath.EscapedPath()
980-
}
981-
key += entrypoint
982-
if _, ok := loader.visitedPathItemRefs[key]; ok {
983-
return nil
984-
}
985-
loader.visitedPathItemRefs[key] = struct{}{}
986-
938+
func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPath *url.URL) (err error) {
987939
if pathItem == nil {
988940
return errors.New("invalid path item: value MUST be an object")
989941
}
990942
ref := pathItem.Ref
991943
if ref != "" {
944+
if pathItem.Summary != "" ||
945+
pathItem.Description != "" ||
946+
pathItem.Connect != nil ||
947+
pathItem.Delete != nil ||
948+
pathItem.Get != nil ||
949+
pathItem.Head != nil ||
950+
pathItem.Options != nil ||
951+
pathItem.Patch != nil ||
952+
pathItem.Post != nil ||
953+
pathItem.Put != nil ||
954+
pathItem.Trace != nil ||
955+
len(pathItem.Servers) != 0 ||
956+
len(pathItem.Parameters) != 0 {
957+
return nil
958+
}
992959
if isSingleRefElement(ref) {
993960
var p PathItem
994961
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
995962
return err
996963
}
997964
*pathItem = p
998965
} else {
999-
if doc, ref, documentPath, err = loader.resolveRef(doc, ref, documentPath); err != nil {
1000-
return
1001-
}
1002-
1003-
rest := strings.TrimPrefix(ref, "#/paths/")
1004-
if rest == ref {
1005-
return fmt.Errorf(`expected prefix "#/paths/" in URI %q`, ref)
1006-
}
1007-
id := unescapeRefString(rest)
1008-
1009-
if doc.Paths == nil {
1010-
return failedToResolveRefFragmentPart(ref, "paths")
1011-
}
1012-
resolved := doc.Paths[id]
1013-
if resolved == nil {
1014-
return failedToResolveRefFragmentPart(ref, id)
966+
var resolved PathItem
967+
doc, documentPath, err = loader.resolveComponent(doc, ref, documentPath, &resolved)
968+
if err != nil {
969+
return err
1015970
}
1016-
*pathItem = *resolved
971+
*pathItem = resolved
1017972
}
973+
pathItem.Ref = ref
1018974
}
1019975
return loader.resolvePathItemRefContinued(doc, pathItem, documentPath)
1020976
}

0 commit comments

Comments
 (0)