Skip to content

Commit 7fdd3bc

Browse files
authored
Support refs outside #/components - Resolves Issue 24 (#207)
1 parent 5249a0b commit 7fdd3bc

File tree

2 files changed

+153
-74
lines changed

2 files changed

+153
-74
lines changed

openapi3/swagger_loader.go

Lines changed: 92 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/url"
1111
"path"
12+
"reflect"
1213
"strings"
1314

1415
"github.com/ghodss/yaml"
@@ -247,24 +248,72 @@ func isSingleRefElement(ref string) bool {
247248
return !strings.Contains(ref, "#")
248249
}
249250

250-
func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref string, prefix string, path *url.URL) (
251-
components *Components,
252-
id string,
251+
func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref string, path *url.URL) (
252+
cursor interface{},
253253
componentPath *url.URL,
254254
err error,
255255
) {
256256
if swagger, ref, componentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, path); err != nil {
257-
return nil, "", nil, err
257+
return nil, nil, err
258258
}
259-
if !strings.HasPrefix(ref, prefix) {
260-
err := fmt.Errorf("expected prefix '%s' in URI '%s'", prefix, ref)
261-
return nil, "", nil, err
259+
260+
parsedURL, err := url.Parse(ref)
261+
if err != nil {
262+
return nil, nil, fmt.Errorf("Can't parse reference: '%s': %v", ref, parsedURL)
262263
}
263-
id = ref[len(prefix):]
264-
if strings.IndexByte(id, '/') >= 0 {
265-
return nil, "", nil, failedToResolveRefFragmentPart(ref, id)
264+
fragment := parsedURL.Fragment
265+
if !strings.HasPrefix(fragment, "/") {
266+
err := fmt.Errorf("expected fragment prefix '#/' in URI '%s'", ref)
267+
return nil, nil, err
268+
}
269+
270+
cursor = swagger
271+
for _, pathPart := range strings.Split(fragment[1:], "/") {
272+
273+
pathPart = strings.Replace(pathPart, "~1", "/", -1)
274+
pathPart = strings.Replace(pathPart, "~0", "~", -1)
275+
276+
cursor, err = drillIntoSwaggerField(cursor, pathPart)
277+
if err != nil {
278+
return nil, nil, fmt.Errorf("Failed to resolve '%s' in fragment in URI: '%s': %v", ref, pathPart, err.Error())
279+
}
280+
if cursor == nil {
281+
return nil, nil, failedToResolveRefFragmentPart(ref, pathPart)
282+
}
283+
}
284+
285+
return cursor, componentPath, nil
286+
}
287+
288+
func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, error) {
289+
val := reflect.Indirect(reflect.ValueOf(cursor))
290+
291+
switch val.Kind() {
292+
case reflect.Map:
293+
elementValue := val.MapIndex(reflect.ValueOf(fieldName))
294+
if !elementValue.IsValid() {
295+
return nil, fmt.Errorf("Map key not found: %v", fieldName)
296+
}
297+
return elementValue.Interface(), nil
298+
299+
case reflect.Struct:
300+
for i := 0; i < val.NumField(); i++ {
301+
field := val.Type().Field(i)
302+
tagValue := field.Tag.Get("yaml")
303+
yamlKey := strings.Split(tagValue, ",")[0]
304+
if yamlKey == fieldName {
305+
return val.Field(i).Interface(), nil
306+
}
307+
}
308+
// if cursor if a "ref wrapper" struct (e.g. RequestBodyRef), try digging into its Value field
309+
_, ok := val.Type().FieldByName("Value")
310+
if ok {
311+
return drillIntoSwaggerField(val.FieldByName("Value").Interface(), fieldName) // recurse into .Value
312+
}
313+
// give up
314+
return nil, fmt.Errorf("Struct field not found: %v", fieldName)
266315
}
267-
return &swagger.Components, id, componentPath, nil
316+
return nil, fmt.Errorf("Not a map or struct")
268317
}
269318

270319
func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref string, path *url.URL) (*Swagger, string, *url.URL, error) {
@@ -313,16 +362,12 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
313362

314363
component.Value = &header
315364
} else {
316-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, path)
365+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
317366
if err != nil {
318367
return err
319368
}
320-
definitions := components.Headers
321-
if definitions == nil {
322-
return failedToResolveRefFragment(ref)
323-
}
324-
resolved := definitions[id]
325-
if resolved == nil {
369+
resolved, ok := untypedResolved.(*HeaderRef)
370+
if !ok {
326371
return failedToResolveRefFragment(ref)
327372
}
328373
if err := swaggerLoader.resolveHeaderRef(swagger, resolved, componentPath); err != nil {
@@ -362,17 +407,13 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
362407
}
363408
component.Value = &param
364409
} else {
365-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, documentPath)
410+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
366411
if err != nil {
367412
return err
368413
}
369-
definitions := components.Parameters
370-
if definitions == nil {
371-
return failedToResolveRefFragmentPart(ref, "parameters")
372-
}
373-
resolved := definitions[id]
374-
if resolved == nil {
375-
return failedToResolveRefFragmentPart(ref, id)
414+
resolved, ok := untypedResolved.(*ParameterRef)
415+
if !ok {
416+
return failedToResolveRefFragment(ref)
376417
}
377418
if err := swaggerLoader.resolveParameterRef(swagger, resolved, componentPath); err != nil {
378419
return err
@@ -427,17 +468,13 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
427468

428469
component.Value = &requestBody
429470
} else {
430-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, path)
471+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
431472
if err != nil {
432473
return err
433474
}
434-
definitions := components.RequestBodies
435-
if definitions == nil {
436-
return failedToResolveRefFragmentPart(ref, "requestBodies")
437-
}
438-
resolved := definitions[id]
439-
if resolved == nil {
440-
return failedToResolveRefFragmentPart(ref, id)
475+
resolved, ok := untypedResolved.(*RequestBodyRef)
476+
if !ok {
477+
return failedToResolveRefFragment(ref)
441478
}
442479
if err = swaggerLoader.resolveRequestBodyRef(swagger, resolved, componentPath); err != nil {
443480
return err
@@ -486,17 +523,13 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
486523

487524
component.Value = &resp
488525
} else {
489-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, documentPath)
526+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
490527
if err != nil {
491528
return err
492529
}
493-
definitions := components.Responses
494-
if definitions == nil {
495-
return failedToResolveRefFragmentPart(ref, "responses")
496-
}
497-
resolved := definitions[id]
498-
if resolved == nil {
499-
return failedToResolveRefFragmentPart(ref, id)
530+
resolved, ok := untypedResolved.(*ResponseRef)
531+
if !ok {
532+
return failedToResolveRefFragment(ref)
500533
}
501534
if err := swaggerLoader.resolveResponseRef(swagger, resolved, componentPath); err != nil {
502535
return err
@@ -562,17 +595,14 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
562595
}
563596
component.Value = &schema
564597
} else {
565-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, documentPath)
598+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
566599
if err != nil {
567600
return err
568601
}
569-
definitions := components.Schemas
570-
if definitions == nil {
571-
return failedToResolveRefFragmentPart(ref, "schemas")
572-
}
573-
resolved := definitions[id]
574-
if resolved == nil {
575-
return failedToResolveRefFragmentPart(ref, id)
602+
603+
resolved, ok := untypedResolved.(*SchemaRef)
604+
if !ok {
605+
return failedToResolveRefFragment(ref)
576606
}
577607
if err := swaggerLoader.resolveSchemaRef(swagger, resolved, componentPath); err != nil {
578608
return err
@@ -650,17 +680,13 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
650680

651681
component.Value = &scheme
652682
} else {
653-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, path)
683+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
654684
if err != nil {
655685
return err
656686
}
657-
definitions := components.SecuritySchemes
658-
if definitions == nil {
659-
return failedToResolveRefFragmentPart(ref, "securitySchemes")
660-
}
661-
resolved := definitions[id]
662-
if resolved == nil {
663-
return failedToResolveRefFragmentPart(ref, id)
687+
resolved, ok := untypedResolved.(*SecuritySchemeRef)
688+
if !ok {
689+
return failedToResolveRefFragment(ref)
664690
}
665691
if err := swaggerLoader.resolveSecuritySchemeRef(swagger, resolved, componentPath); err != nil {
666692
return err
@@ -689,17 +715,13 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
689715

690716
component.Value = &example
691717
} else {
692-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, path)
718+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
693719
if err != nil {
694720
return err
695721
}
696-
definitions := components.Examples
697-
if definitions == nil {
698-
return failedToResolveRefFragmentPart(ref, "examples")
699-
}
700-
resolved := definitions[id]
701-
if resolved == nil {
702-
return failedToResolveRefFragmentPart(ref, id)
722+
resolved, ok := untypedResolved.(*ExampleRef)
723+
if !ok {
724+
return failedToResolveRefFragment(ref)
703725
}
704726
if err := swaggerLoader.resolveExampleRef(swagger, resolved, componentPath); err != nil {
705727
return err
@@ -728,17 +750,13 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
728750

729751
component.Value = &link
730752
} else {
731-
components, id, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, prefix, path)
753+
untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
732754
if err != nil {
733755
return err
734756
}
735-
definitions := components.Links
736-
if definitions == nil {
737-
return failedToResolveRefFragmentPart(ref, "links")
738-
}
739-
resolved := definitions[id]
740-
if resolved == nil {
741-
return failedToResolveRefFragmentPart(ref, id)
757+
resolved, ok := untypedResolved.(*LinkRef)
758+
if !ok {
759+
return failedToResolveRefFragment(ref)
742760
}
743761
if err := swaggerLoader.resolveLinkRef(swagger, resolved, componentPath); err != nil {
744762
return err

openapi3/swagger_loader_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,64 @@ paths:
490490
require.Equal(t, "getUserById", link.OperationID)
491491
require.Equal(t, "link to to the father", link.Description)
492492
}
493+
func TestResolveNonComponentsRef(t *testing.T) {
494+
spec := []byte(`
495+
openapi: 3.0.0
496+
info:
497+
title: An API
498+
version: v1
499+
500+
components:
501+
schemas:
502+
NewItem:
503+
required: [name]
504+
properties:
505+
name: {type: string}
506+
tag: {type: string}
507+
ErrorModel:
508+
type: object
509+
required: [code, message]
510+
properties:
511+
code: {type: integer}
512+
message: {type: string}
513+
514+
paths:
515+
/items:
516+
put:
517+
description: ''
518+
requestBody:
519+
required: true
520+
content:
521+
application/json:
522+
schema:
523+
$ref: '#/components/schemas/NewItem'
524+
responses:
525+
default:
526+
description: unexpected error
527+
content:
528+
application/json:
529+
schema:
530+
$ref: '#/components/schemas/ErrorModel'
531+
post:
532+
description: ''
533+
requestBody:
534+
required: true
535+
content:
536+
application/json:
537+
schema:
538+
$ref: '#/paths/~1items/put/requestBody/content/application~1json/schema'
539+
responses:
540+
default:
541+
description: unexpected error
542+
content:
543+
application/json:
544+
schema:
545+
$ref: '#/components/schemas/ErrorModel'
546+
`)
547+
548+
loader := openapi3.NewSwaggerLoader()
549+
doc, err := loader.LoadSwaggerFromData(spec)
550+
require.NoError(t, err)
551+
err = doc.Validate(loader.Context)
552+
require.NoError(t, err)
553+
}

0 commit comments

Comments
 (0)