diff --git a/openapi3/internalize_refs_test.go b/openapi3/internalize_refs_test.go index b1ca846d2..fe6b29d90 100644 --- a/openapi3/internalize_refs_test.go +++ b/openapi3/internalize_refs_test.go @@ -2,7 +2,7 @@ package openapi3 import ( "context" - "io/ioutil" + "os" "regexp" "testing" @@ -41,25 +41,25 @@ func TestInternalizeRefs(t *testing.T) { err = doc.Validate(ctx) require.NoError(t, err, "validating internalized spec") - data, err := doc.MarshalJSON() + actual, err := doc.MarshalJSON() require.NoError(t, err, "marshalling internalized spec") // run a static check over the file, making sure each occurence of a // reference is followed by a # - numRefs := len(regexpRef.FindAll(data, -1)) - numInternalRefs := len(regexpRefInternal.FindAll(data, -1)) + numRefs := len(regexpRef.FindAll(actual, -1)) + numInternalRefs := len(regexpRefInternal.FindAll(actual, -1)) require.Equal(t, numRefs, numInternalRefs, "checking all references are internal") - // load from data, but with the path set to the current directory - doc2, err := sl.LoadFromData(data) + // load from actual, but with the path set to the current directory + doc2, err := sl.LoadFromData(actual) require.NoError(t, err, "reloading spec") err = doc2.Validate(ctx) require.NoError(t, err, "validating reloaded spec") // compare with expected - data0, err := ioutil.ReadFile(test.filename + ".internalized.yml") + expected, err := os.ReadFile(test.filename + ".internalized.yml") require.NoError(t, err) - require.JSONEq(t, string(data), string(data0)) + require.JSONEq(t, string(expected), string(actual)) }) } } diff --git a/openapi3/loader.go b/openapi3/loader.go index 28836f329..9ed8e3076 100644 --- a/openapi3/loader.go +++ b/openapi3/loader.go @@ -36,7 +36,8 @@ type Loader struct { Context context.Context - rootDir string + rootDir string + rootLocation string visitedPathItemRefs map[string]struct{} @@ -148,6 +149,7 @@ func (loader *Loader) LoadFromDataWithPath(data []byte, location *url.URL) (*T, func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.URL) (*T, error) { if loader.visitedDocuments == nil { loader.visitedDocuments = make(map[string]*T) + loader.rootLocation = location.Path } uri := location.String() if doc, ok := loader.visitedDocuments[uri]; ok { @@ -420,6 +422,11 @@ func (loader *Loader) documentPathForRecursiveRef(current *url.URL, resolvedRef if loader.rootDir == "" { return current } + + if resolvedRef == "" { + return &url.URL{Path: loader.rootLocation} + } + return &url.URL{Path: path.Join(loader.rootDir, resolvedRef)} } diff --git a/openapi3/loader_recursive_ref_test.go b/openapi3/loader_recursive_ref_test.go index 5b7c1506e..924cb6be8 100644 --- a/openapi3/loader_recursive_ref_test.go +++ b/openapi3/loader_recursive_ref_test.go @@ -14,6 +14,8 @@ func TestLoaderSupportsRecursiveReference(t *testing.T) { err = doc.Validate(loader.Context) require.NoError(t, err) require.Equal(t, "bar", doc.Paths["/foo"].Get.Responses.Get(200).Value.Content.Get("application/json").Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Example) + require.Equal(t, "ErrorDetails", doc.Paths["/foo"].Get.Responses.Get(400).Value.Content.Get("application/json").Schema.Value.Title) + require.Equal(t, "ErrorDetails", doc.Paths["/double-ref-foo"].Get.Responses.Get(400).Value.Content.Get("application/json").Schema.Value.Title) } func TestIssue447(t *testing.T) { diff --git a/openapi3/testdata/recursiveRef/components/models/error.yaml b/openapi3/testdata/recursiveRef/components/models/error.yaml new file mode 100644 index 000000000..b4d404793 --- /dev/null +++ b/openapi3/testdata/recursiveRef/components/models/error.yaml @@ -0,0 +1,2 @@ +type: object +title: ErrorDetails diff --git a/openapi3/testdata/recursiveRef/openapi.yml b/openapi3/testdata/recursiveRef/openapi.yml index 675722a60..9f884c710 100644 --- a/openapi3/testdata/recursiveRef/openapi.yml +++ b/openapi3/testdata/recursiveRef/openapi.yml @@ -5,6 +5,13 @@ info: paths: /foo: $ref: ./paths/foo.yml + /double-ref-foo: + get: + summary: Double ref response + description: Reference response with double reference. + responses: + "400": + $ref: "#/components/responses/400" components: schemas: Foo: @@ -15,3 +22,12 @@ components: $ref: ./components/Bar.yml Cat: $ref: ./components/Cat.yml + Error: + $ref: ./components/models/error.yaml + responses: + "400": + description: 400 Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" diff --git a/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml index 073059025..0d508527a 100644 --- a/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml +++ b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml @@ -9,11 +9,27 @@ } } }, + "responses": { + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "400 Bad Request" + } + }, "schemas": { "Bar": { "example": "bar", "type": "string" }, + "Error":{ + "title":"ErrorDetails", + "type":"object" + }, "Foo": { "properties": { "bar": { @@ -30,6 +46,10 @@ }, "type": "object" }, + "error":{ + "title":"ErrorDetails", + "type":"object" + }, "Cat": { "properties": { "cat": { @@ -46,6 +66,17 @@ }, "openapi": "3.0.3", "paths": { + "/double-ref-foo": { + "get": { + "description": "Reference response with double reference.", + "responses": { + "400": { + "$ref": "#/components/responses/400" + } + }, + "summary": "Double ref response" + } + }, "/foo": { "get": { "responses": { @@ -63,6 +94,9 @@ } }, "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400" } } }, diff --git a/openapi3/testdata/recursiveRef/paths/foo.yml b/openapi3/testdata/recursiveRef/paths/foo.yml index 43e03b7ab..4c845b532 100644 --- a/openapi3/testdata/recursiveRef/paths/foo.yml +++ b/openapi3/testdata/recursiveRef/paths/foo.yml @@ -11,3 +11,5 @@ get: properties: foo2: $ref: ../openapi.yml#/components/schemas/Foo2 + "400": + $ref: "../openapi.yml#/components/responses/400"