From 29755448d7796eeede8b291d0a05280f1df6504c Mon Sep 17 00:00:00 2001 From: Kanai Masumi Date: Tue, 23 Aug 2022 12:51:00 +0900 Subject: [PATCH 1/3] test: add test case TestLoadCircularRefFromFile() --- ...oad_cicular_ref_with_external_file_test.go | 30 +++++++++++++++++++ openapi3/testdata/circularRef/base.yml | 16 ++++++++++ openapi3/testdata/circularRef/other.yml | 10 +++++++ 3 files changed, 56 insertions(+) create mode 100644 openapi3/load_cicular_ref_with_external_file_test.go create mode 100644 openapi3/testdata/circularRef/base.yml create mode 100644 openapi3/testdata/circularRef/other.yml diff --git a/openapi3/load_cicular_ref_with_external_file_test.go b/openapi3/load_cicular_ref_with_external_file_test.go new file mode 100644 index 000000000..c66b9e87f --- /dev/null +++ b/openapi3/load_cicular_ref_with_external_file_test.go @@ -0,0 +1,30 @@ +//go:build go1.16 +// +build go1.16 + +package openapi3_test + +import ( + "embed" + "fmt" + "net/url" + "testing" + + "github.com/getkin/kin-openapi/openapi3" +) + +//go:embed testdata/circularRef/* +var circularResSpecs embed.FS + +func TestLoadCircularRefFromFile(t *testing.T) { + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, uri *url.URL) ([]byte, error) { + return circularResSpecs.ReadFile(uri.Path) + } + + doc, err := loader.LoadFromFile("testdata/circularRef/base.yml") + if err != nil { + t.Error(err) + } + fmt.Printf("%v\n", doc) +} diff --git a/openapi3/testdata/circularRef/base.yml b/openapi3/testdata/circularRef/base.yml new file mode 100644 index 000000000..ff8240eb0 --- /dev/null +++ b/openapi3/testdata/circularRef/base.yml @@ -0,0 +1,16 @@ +openapi: "3.0.3" +info: + title: Recursive cyclic refs example + version: "1.0" +components: + schemas: + Foo: + properties: + foo2: + $ref: "other.yml#/components/schemas/Foo2" + bar: + $ref: "#/components/schemas/Bar" + Bar: + properties: + foo: + $ref: "#/components/schemas/Foo" diff --git a/openapi3/testdata/circularRef/other.yml b/openapi3/testdata/circularRef/other.yml new file mode 100644 index 000000000..29b72d98c --- /dev/null +++ b/openapi3/testdata/circularRef/other.yml @@ -0,0 +1,10 @@ +openapi: "3.0.3" +info: + title: Recursive cyclic refs example + version: "1.0" +components: + schemas: + Foo2: + properties: + id: + type: string From a133d7fe95b4dab5485c5a84121233d67c22c513 Mon Sep 17 00:00:00 2001 From: Kanai Masumi Date: Tue, 23 Aug 2022 13:15:27 +0900 Subject: [PATCH 2/3] fix: handle circluar refs. and refs. to external spec. correctly --- openapi3/loader.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openapi3/loader.go b/openapi3/loader.go index e2f131e40..c981a3382 100644 --- a/openapi3/loader.go +++ b/openapi3/loader.go @@ -699,7 +699,8 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat return err } component.Value = resolved.Value - documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + foundPath := loader.getResolvedRefPath(ref, &resolved, documentPath, componentPath) + documentPath = loader.documentPathForRecursiveRef(documentPath, foundPath) } } value := component.Value @@ -746,6 +747,22 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat return nil } +func (loader *Loader) getResolvedRefPath(ref string, resolved *SchemaRef, cur, found *url.URL) string { + referencedFilename := strings.Split(ref, "#")[0] + if referencedFilename == "" { + if cur != nil { + return path.Base(cur.Path) + } + return "" + } + // ref. to external file + if resolved.Ref != "" { + return resolved.Ref + } + // found dest spec. file + return path.Dir(found.Path)[len(loader.rootDir):] +} + func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) { if component != nil && component.Value != nil { if loader.visitedSecurityScheme == nil { From 464e0cb94f5f1aa93443082b970363f16d3ae915 Mon Sep 17 00:00:00 2001 From: Kanai Masumi Date: Mon, 29 Aug 2022 15:50:28 +0900 Subject: [PATCH 3/3] test: add assertion to load_cicular_ref_with_external_file_test --- ...oad_cicular_ref_with_external_file_test.go | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/openapi3/load_cicular_ref_with_external_file_test.go b/openapi3/load_cicular_ref_with_external_file_test.go index c66b9e87f..85b127e93 100644 --- a/openapi3/load_cicular_ref_with_external_file_test.go +++ b/openapi3/load_cicular_ref_with_external_file_test.go @@ -5,10 +5,11 @@ package openapi3_test import ( "embed" - "fmt" "net/url" "testing" + "github.com/stretchr/testify/require" + "github.com/getkin/kin-openapi/openapi3" ) @@ -22,9 +23,60 @@ func TestLoadCircularRefFromFile(t *testing.T) { return circularResSpecs.ReadFile(uri.Path) } - doc, err := loader.LoadFromFile("testdata/circularRef/base.yml") + got, err := loader.LoadFromFile("testdata/circularRef/base.yml") if err != nil { t.Error(err) } - fmt.Printf("%v\n", doc) + + foo := &openapi3.SchemaRef{ + Ref: "", + Value: &openapi3.Schema{ + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + Properties: map[string]*openapi3.SchemaRef{ + "foo2": { // reference to an external file + Ref: "other.yml#/components/schemas/Foo2", + Value: &openapi3.Schema{ + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + Properties: map[string]*openapi3.SchemaRef{ + "id": { + Value: &openapi3.Schema{ + Type: "string", + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + }}, + }, + }, + }, + }, + }, + } + bar := &openapi3.SchemaRef{ + Ref: "", + Value: &openapi3.Schema{ + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + Properties: map[string]*openapi3.SchemaRef{}, + }, + } + // circular reference + bar.Value.Properties["foo"] = &openapi3.SchemaRef{Ref: "#/components/schemas/Foo", Value: foo.Value} + foo.Value.Properties["bar"] = &openapi3.SchemaRef{Ref: "#/components/schemas/Bar", Value: bar.Value} + + want := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "Recursive cyclic refs example", + Version: "1.0", + + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + }, + Components: openapi3.Components{ + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + Schemas: map[string]*openapi3.SchemaRef{ + "Foo": foo, + "Bar": bar, + }, + }, + ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}}, + } + + require.Equal(t, want, got) }