diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go index cb0cda438..71216a28a 100644 --- a/openapi3/swagger_loader.go +++ b/openapi3/swagger_loader.go @@ -166,9 +166,11 @@ func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataWithPathInternal(data []b } func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.URL) (err error) { - swaggerLoader.visited = make(map[interface{}]struct{}) + if swaggerLoader.visited == nil { + swaggerLoader.visited = make(map[interface{}]struct{}) + } if swaggerLoader.visitedFiles == nil { - swaggerLoader.visitedFiles = make(map[string]struct{}) + swaggerLoader.reset() } // Visit all components @@ -232,7 +234,7 @@ func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) { } newPath, err := copyURL(basePath) if err != nil { - return nil, fmt.Errorf("Can't copy path: '%s'", basePath.String()) + return nil, fmt.Errorf("cannot copy path: %q", basePath.String()) } newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) return newPath, nil @@ -274,9 +276,7 @@ func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref strin cursor = swagger for _, pathPart := range strings.Split(fragment[1:], "/") { - - pathPart = strings.Replace(pathPart, "~1", "/", -1) - pathPart = strings.Replace(pathPart, "~0", "~", -1) + pathPart = unescapeRefString(pathPart) if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil { return nil, nil, fmt.Errorf("Failed to resolve '%s' in fragment in URI: '%s': %v", ref, pathPart, err.Error()) @@ -369,7 +369,7 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component if component == nil { return errors.New("invalid header: value MUST be a JSON object") } - if ref := component.Ref; len(ref) > 0 { + if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var header Header if err := swaggerLoader.loadSingleElementFromURI(ref, path, &header); err != nil { @@ -416,7 +416,7 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon return errors.New("invalid parameter: value MUST be a JSON object") } ref := component.Ref - if len(ref) > 0 { + if ref != "" { if isSingleRefElement(ref) { var param Parameter if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil { @@ -477,7 +477,7 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp if component == nil { return errors.New("invalid requestBody: value MUST be a JSON object") } - if ref := component.Ref; len(ref) > 0 { + if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var requestBody RequestBody if err := swaggerLoader.loadSingleElementFromURI(ref, path, &requestBody); err != nil { @@ -532,7 +532,7 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone return errors.New("invalid response: value MUST be a JSON object") } ref := component.Ref - if len(ref) > 0 { + if ref != "" { if isSingleRefElement(ref) { var resp Response @@ -607,7 +607,7 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component return errors.New("invalid schema: value MUST be a JSON object") } ref := component.Ref - if len(ref) > 0 { + if ref != "" { if isSingleRefElement(ref) { var schema Schema if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil { @@ -692,7 +692,7 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c if component == nil { return errors.New("invalid securityScheme: value MUST be a JSON object") } - if ref := component.Ref; len(ref) > 0 { + if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var scheme SecurityScheme if err := swaggerLoader.loadSingleElementFromURI(ref, path, &scheme); err != nil { @@ -729,7 +729,7 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen if component == nil { return errors.New("invalid example: value MUST be a JSON object") } - if ref := component.Ref; len(ref) > 0 { + if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var example Example if err := swaggerLoader.loadSingleElementFromURI(ref, path, &example); err != nil { @@ -766,7 +766,7 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component * if component == nil { return errors.New("invalid link: value MUST be a JSON object") } - if ref := component.Ref; len(ref) > 0 { + if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var link Link if err := swaggerLoader.loadSingleElementFromURI(ref, path, &link); err != nil { diff --git a/openapi3/swagger_loader_issue235_test.go b/openapi3/swagger_loader_issue235_test.go new file mode 100644 index 000000000..79515c12d --- /dev/null +++ b/openapi3/swagger_loader_issue235_test.go @@ -0,0 +1,25 @@ +package openapi3 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIssue235OK(t *testing.T) { + loader := NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + doc, err := loader.LoadSwaggerFromFile("testdata/issue235.spec0.yml") + require.NoError(t, err) + err = doc.Validate(loader.Context) + require.NoError(t, err) +} + +func TestIssue235CircularDep(t *testing.T) { + t.Skip("TODO: return an error on circular dependencies between external files of a spec") + loader := NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + doc, err := loader.LoadSwaggerFromFile("testdata/issue235.spec0-typo.yml") + require.Nil(t, doc) + require.Error(t, err) +} diff --git a/openapi3/testdata/issue235.spec0-typo.yml b/openapi3/testdata/issue235.spec0-typo.yml new file mode 100644 index 000000000..543600620 --- /dev/null +++ b/openapi3/testdata/issue235.spec0-typo.yml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +info: + title: 'OAI Specification in YAML' + version: 0.0.1 +paths: + /test: + get: + responses: + "200": + $ref: '#/components/responses/GetTestOK' +components: + responses: + GetTestOK: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ObjectA' + schemas: + ObjectA: + type: object + properties: + object_b: + $ref: 'issue235.spec0-typo.yml#/components/schemas/ObjectD' diff --git a/openapi3/testdata/issue235.spec0.yml b/openapi3/testdata/issue235.spec0.yml new file mode 100644 index 000000000..d9236aaec --- /dev/null +++ b/openapi3/testdata/issue235.spec0.yml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +info: + title: 'OAI Specification in YAML' + version: 0.0.1 +paths: + /test: + get: + responses: + "200": + $ref: '#/components/responses/GetTestOK' +components: + responses: + GetTestOK: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ObjectA' + schemas: + ObjectA: + type: object + properties: + object_b: + $ref: 'issue235.spec1.yml#/components/schemas/ObjectD' diff --git a/openapi3/testdata/issue235.spec1.yml b/openapi3/testdata/issue235.spec1.yml new file mode 100644 index 000000000..a1bc67906 --- /dev/null +++ b/openapi3/testdata/issue235.spec1.yml @@ -0,0 +1,12 @@ +components: + schemas: + ObjectD: + type: object + properties: + result: + $ref: '#/components/schemas/ObjectE' + + ObjectE: + properties: + name: + $ref: issue235.spec2.yml#/components/schemas/ObjectX diff --git a/openapi3/testdata/issue235.spec2.yml b/openapi3/testdata/issue235.spec2.yml new file mode 100644 index 000000000..b0bcb0fa2 --- /dev/null +++ b/openapi3/testdata/issue235.spec2.yml @@ -0,0 +1,7 @@ +components: + schemas: + ObjectX: + type: object + properties: + name: + type: string