Skip to content

Commit f6c20c3

Browse files
authored
Follow callbacks references (#347)
1 parent 1b47cce commit f6c20c3

File tree

4 files changed

+211
-1
lines changed

4 files changed

+211
-1
lines changed

openapi3/issue301_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestIssue301(t *testing.T) {
10+
sl := NewSwaggerLoader()
11+
sl.IsExternalRefsAllowed = true
12+
13+
doc, err := sl.LoadSwaggerFromFile("testdata/callbacks.yml")
14+
require.NoError(t, err)
15+
16+
err = doc.Validate(sl.Context)
17+
require.NoError(t, err)
18+
19+
transCallbacks := doc.Paths["/trans"].Post.Callbacks["transactionCallback"].Value
20+
require.Equal(t, "object", (*transCallbacks)["http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}"].Post.RequestBody.
21+
Value.Content["application/json"].Schema.
22+
Value.Type)
23+
24+
otherCallbacks := doc.Paths["/other"].Post.Callbacks["myEvent"].Value
25+
require.Equal(t, "boolean", (*otherCallbacks)["{$request.query.queryUrl}"].Post.RequestBody.
26+
Value.Content["application/json"].Schema.
27+
Value.Type)
28+
}

openapi3/swagger_loader.go

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (swaggerLoader *SwaggerLoader) allowsExternalRefs(ref string) (err error) {
8585
}
8686

8787
// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element.
88-
func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) (*url.URL, error) {
88+
func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element interface{}) (*url.URL, error) {
8989
if err := swaggerLoader.allowsExternalRefs(ref); err != nil {
9090
return nil, err
9191
}
@@ -221,6 +221,11 @@ func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, location *ur
221221
return
222222
}
223223
}
224+
for _, component := range components.Callbacks {
225+
if err = swaggerLoader.resolveCallbackRef(swagger, component, location); err != nil {
226+
return
227+
}
228+
}
224229

225230
// Visit all operations
226231
for entrypoint, pathItem := range swagger.Paths {
@@ -799,6 +804,94 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
799804
return nil
800805
}
801806

807+
func (swaggerLoader *SwaggerLoader) resolveCallbackRef(swagger *Swagger, component *CallbackRef, documentPath *url.URL) (err error) {
808+
809+
if component == nil {
810+
return errors.New("invalid callback: value MUST be an object")
811+
}
812+
if ref := component.Ref; ref != "" {
813+
if isSingleRefElement(ref) {
814+
var resolved Callback
815+
if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resolved); err != nil {
816+
return err
817+
}
818+
component.Value = &resolved
819+
} else {
820+
var resolved CallbackRef
821+
componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
822+
if err != nil {
823+
return err
824+
}
825+
if err := swaggerLoader.resolveCallbackRef(swagger, &resolved, componentPath); err != nil {
826+
return err
827+
}
828+
component.Value = resolved.Value
829+
}
830+
}
831+
value := component.Value
832+
if value == nil {
833+
return nil
834+
}
835+
836+
for entrypoint, pathItem := range *value {
837+
entrypoint, pathItem := entrypoint, pathItem
838+
err = func() (err error) {
839+
key := "-"
840+
if documentPath != nil {
841+
key = documentPath.EscapedPath()
842+
}
843+
key += entrypoint
844+
if _, ok := swaggerLoader.visitedPathItemRefs[key]; ok {
845+
return nil
846+
}
847+
swaggerLoader.visitedPathItemRefs[key] = struct{}{}
848+
849+
if pathItem == nil {
850+
return errors.New("invalid path item: value MUST be an object")
851+
}
852+
ref := pathItem.Ref
853+
if ref != "" {
854+
if isSingleRefElement(ref) {
855+
var p PathItem
856+
if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
857+
return err
858+
}
859+
*pathItem = p
860+
} else {
861+
if swagger, ref, documentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, documentPath); err != nil {
862+
return
863+
}
864+
865+
rest := strings.TrimPrefix(ref, "#/components/callbacks/")
866+
if rest == ref {
867+
return fmt.Errorf(`expected prefix "#/components/callbacks/" in URI %q`, ref)
868+
}
869+
id := unescapeRefString(rest)
870+
871+
definitions := swagger.Components.Callbacks
872+
if definitions == nil {
873+
return failedToResolveRefFragmentPart(ref, "callbacks")
874+
}
875+
resolved := definitions[id]
876+
if resolved == nil {
877+
return failedToResolveRefFragmentPart(ref, id)
878+
}
879+
880+
for _, p := range *resolved.Value {
881+
*pathItem = *p
882+
break
883+
}
884+
}
885+
}
886+
return swaggerLoader.resolvePathItemRefContinued(swagger, pathItem, documentPath)
887+
}()
888+
if err != nil {
889+
return err
890+
}
891+
}
892+
return nil
893+
}
894+
802895
func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, documentPath *url.URL) (err error) {
803896
if component != nil && component.Value != nil {
804897
if swaggerLoader.visitedLink == nil {
@@ -880,7 +973,10 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
880973
*pathItem = *resolved
881974
}
882975
}
976+
return swaggerLoader.resolvePathItemRefContinued(swagger, pathItem, documentPath)
977+
}
883978

979+
func (swaggerLoader *SwaggerLoader) resolvePathItemRefContinued(swagger *Swagger, pathItem *PathItem, documentPath *url.URL) (err error) {
884980
for _, parameter := range pathItem.Parameters {
885981
if err = swaggerLoader.resolveParameterRef(swagger, parameter, documentPath); err != nil {
886982
return
@@ -902,6 +998,11 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
902998
return
903999
}
9041000
}
1001+
for _, callback := range operation.Callbacks {
1002+
if err = swaggerLoader.resolveCallbackRef(swagger, callback, documentPath); err != nil {
1003+
return
1004+
}
1005+
}
9051006
}
9061007
return
9071008
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
post:
2+
requestBody:
3+
description: Callback payload
4+
content:
5+
'application/json':
6+
schema:
7+
$ref: 'callbacks.yml#/components/schemas/SomePayload'
8+
responses:
9+
'200':
10+
description: callback successfully processed

openapi3/testdata/callbacks.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Callback refd
4+
version: 1.2.3
5+
paths:
6+
/trans:
7+
post:
8+
description: ''
9+
requestBody:
10+
description: ''
11+
content:
12+
'application/json':
13+
schema:
14+
properties:
15+
id: {type: string}
16+
email: {format: email}
17+
responses:
18+
'201':
19+
description: subscription successfully created
20+
content:
21+
application/json:
22+
schema:
23+
type: object
24+
callbacks:
25+
transactionCallback:
26+
'http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}':
27+
$ref: callback-transactioned.yml
28+
29+
/other:
30+
post:
31+
description: ''
32+
parameters:
33+
- name: queryUrl
34+
in: query
35+
required: true
36+
description: |
37+
bla
38+
bla
39+
bla
40+
schema:
41+
type: string
42+
format: uri
43+
example: https://example.com
44+
responses:
45+
'201':
46+
description: ''
47+
content:
48+
application/json:
49+
schema:
50+
type: object
51+
callbacks:
52+
myEvent:
53+
$ref: '#/components/callbacks/MyCallbackEvent'
54+
55+
components:
56+
schemas:
57+
SomePayload: {type: object}
58+
SomeOtherPayload: {type: boolean}
59+
callbacks:
60+
MyCallbackEvent:
61+
'{$request.query.queryUrl}':
62+
post:
63+
requestBody:
64+
description: Callback payload
65+
content:
66+
'application/json':
67+
schema:
68+
$ref: '#/components/schemas/SomeOtherPayload'
69+
responses:
70+
'200':
71+
description: callback successfully processed

0 commit comments

Comments
 (0)