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

Follow callbacks references #347

Merged
merged 6 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions openapi3/issue301_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package openapi3

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestIssue301(t *testing.T) {
sl := NewSwaggerLoader()
sl.IsExternalRefsAllowed = true

doc, err := sl.LoadSwaggerFromFile("testdata/callbacks.yml")
require.NoError(t, err)

err = doc.Validate(sl.Context)
require.NoError(t, err)

transCallbacks := doc.Paths["/trans"].Post.Callbacks["transactionCallback"].Value
require.Equal(t, "object", (*transCallbacks)["http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}"].Post.RequestBody.
Value.Content["application/json"].Schema.
Value.Type)

otherCallbacks := doc.Paths["/other"].Post.Callbacks["myEvent"].Value
require.Equal(t, "boolean", (*otherCallbacks)["{$request.query.queryUrl}"].Post.RequestBody.
Value.Content["application/json"].Schema.
Value.Type)
}
103 changes: 102 additions & 1 deletion openapi3/swagger_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (swaggerLoader *SwaggerLoader) allowsExternalRefs(ref string) (err error) {
}

// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element.
func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) (*url.URL, error) {
func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element interface{}) (*url.URL, error) {
if err := swaggerLoader.allowsExternalRefs(ref); err != nil {
return nil, err
}
Expand Down Expand Up @@ -221,6 +221,11 @@ func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, location *ur
return
}
}
for _, component := range components.Callbacks {
if err = swaggerLoader.resolveCallbackRef(swagger, component, location); err != nil {
return
}
}

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

func (swaggerLoader *SwaggerLoader) resolveCallbackRef(swagger *Swagger, component *CallbackRef, documentPath *url.URL) (err error) {

if component == nil {
return errors.New("invalid callback: value MUST be an object")
}
if ref := component.Ref; ref != "" {
if isSingleRefElement(ref) {
var resolved Callback
if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resolved); err != nil {
return err
}
component.Value = &resolved
} else {
var resolved CallbackRef
componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
if err != nil {
return err
}
if err := swaggerLoader.resolveCallbackRef(swagger, &resolved, componentPath); err != nil {
return err
}
component.Value = resolved.Value
}
}
value := component.Value
if value == nil {
return nil
}

for entrypoint, pathItem := range *value {
entrypoint, pathItem := entrypoint, pathItem
err = func() (err error) {
key := "-"
if documentPath != nil {
key = documentPath.EscapedPath()
}
key += entrypoint
if _, ok := swaggerLoader.visitedPathItemRefs[key]; ok {
return nil
}
swaggerLoader.visitedPathItemRefs[key] = struct{}{}

if pathItem == nil {
return errors.New("invalid path item: value MUST be an object")
}
ref := pathItem.Ref
if ref != "" {
if isSingleRefElement(ref) {
var p PathItem
if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
return err
}
*pathItem = p
} else {
if swagger, ref, documentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, documentPath); err != nil {
return
}

rest := strings.TrimPrefix(ref, "#/components/callbacks/")
if rest == ref {
return fmt.Errorf(`expected prefix "#/components/callbacks/" in URI %q`, ref)
}
id := unescapeRefString(rest)

definitions := swagger.Components.Callbacks
if definitions == nil {
return failedToResolveRefFragmentPart(ref, "callbacks")
}
resolved := definitions[id]
if resolved == nil {
return failedToResolveRefFragmentPart(ref, id)
}

for _, p := range *resolved.Value {
*pathItem = *p
break
}
}
}
return swaggerLoader.resolvePathItemRefContinued(swagger, pathItem, documentPath)
}()
if err != nil {
return err
}
}
return nil
}

func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, documentPath *url.URL) (err error) {
if component != nil && component.Value != nil {
if swaggerLoader.visitedLink == nil {
Expand Down Expand Up @@ -880,7 +973,10 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
*pathItem = *resolved
}
}
return swaggerLoader.resolvePathItemRefContinued(swagger, pathItem, documentPath)
}

func (swaggerLoader *SwaggerLoader) resolvePathItemRefContinued(swagger *Swagger, pathItem *PathItem, documentPath *url.URL) (err error) {
for _, parameter := range pathItem.Parameters {
if err = swaggerLoader.resolveParameterRef(swagger, parameter, documentPath); err != nil {
return
Expand All @@ -902,6 +998,11 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
return
}
}
for _, callback := range operation.Callbacks {
if err = swaggerLoader.resolveCallbackRef(swagger, callback, documentPath); err != nil {
return
}
}
}
return
}
Expand Down
10 changes: 10 additions & 0 deletions openapi3/testdata/callback-transactioned.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
post:
requestBody:
description: Callback payload
content:
'application/json':
schema:
$ref: 'callbacks.yml#/components/schemas/SomePayload'
responses:
'200':
description: callback successfully processed
71 changes: 71 additions & 0 deletions openapi3/testdata/callbacks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
openapi: 3.1.0
info:
title: Callback refd
version: 1.2.3
paths:
/trans:
post:
description: ''
requestBody:
description: ''
content:
'application/json':
schema:
properties:
id: {type: string}
email: {format: email}
responses:
'201':
description: subscription successfully created
content:
application/json:
schema:
type: object
callbacks:
transactionCallback:
'http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}':
$ref: callback-transactioned.yml

/other:
post:
description: ''
parameters:
- name: queryUrl
in: query
required: true
description: |
bla
bla
bla
schema:
type: string
format: uri
example: https://example.com
responses:
'201':
description: ''
content:
application/json:
schema:
type: object
callbacks:
myEvent:
$ref: '#/components/callbacks/MyCallbackEvent'

components:
schemas:
SomePayload: {type: object}
SomeOtherPayload: {type: boolean}
callbacks:
MyCallbackEvent:
'{$request.query.queryUrl}':
post:
requestBody:
description: Callback payload
content:
'application/json':
schema:
$ref: '#/components/schemas/SomeOtherPayload'
responses:
'200':
description: callback successfully processed