Repository: getkin/kin-openapi
Pull request #585 (2022-08-29 14:30:53)

cleanup after #583
Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>


Patch:
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index 3e19b79..1733a49 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -200,7 +200,7 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver)
 }
 
 func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver) {
-	if s == nil {
+	if s == nil || doc.isVisitedSchema(s) {
 		return
 	}
 
@@ -229,6 +229,9 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver) {
 func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver) {
 	for _, h := range hs {
 		doc.addHeaderToSpec(h, refNameResolver)
+		if doc.isVisitedHeader(h.Value) {
+			continue
+		}
 		doc.derefParameter(h.Value.Parameter, refNameResolver)
 	}
 }
@@ -324,6 +327,8 @@ func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameReso
 //
 //	doc.InternalizeRefs(context.Background(), nil)
 func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string) {
+	doc.resetVisited()
+
 	if refNameResolver == nil {
 		refNameResolver = DefaultRefNameResolver
 	}
diff --git a/openapi3/loader.go b/openapi3/loader.go
index c981a33..806b819 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -748,8 +748,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 }
 
 func (loader *Loader) getResolvedRefPath(ref string, resolved *SchemaRef, cur, found *url.URL) string {
-	referencedFilename := strings.Split(ref, "#")[0]
-	if referencedFilename == "" {
+	if referencedFilename := strings.Split(ref, "#")[0]; referencedFilename == "" {
 		if cur != nil {
 			return path.Base(cur.Path)
 		}
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index c0188c2..cb9183d 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -21,6 +21,8 @@ type T struct {
 	Servers      Servers              `json:"servers,omitempty" yaml:"servers,omitempty"`
 	Tags         Tags                 `json:"tags,omitempty" yaml:"tags,omitempty"`
 	ExternalDocs *ExternalDocs        `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+
+	visited visitedComponent
 }
 
 // MarshalJSON returns the JSON encoding of T.
diff --git a/openapi3/visited.go b/openapi3/visited.go
new file mode 100644
index 0000000..67f970e
--- /dev/null
+++ b/openapi3/visited.go
@@ -0,0 +1,41 @@
+package openapi3
+
+func newVisited() visitedComponent {
+	return visitedComponent{
+		header: make(map[*Header]struct{}),
+		schema: make(map[*Schema]struct{}),
+	}
+}
+
+type visitedComponent struct {
+	header map[*Header]struct{}
+	schema map[*Schema]struct{}
+}
+
+// resetVisited clears visitedComponent map
+// should be called before recursion over doc *T
+func (doc *T) resetVisited() {
+	doc.visited = newVisited()
+}
+
+// isVisitedHeader returns `true` if the *Header pointer was already visited
+// otherwise it returns `false`
+func (doc *T) isVisitedHeader(h *Header) bool {
+	if _, ok := doc.visited.header[h]; ok {
+		return true
+	}
+
+	doc.visited.header[h] = struct{}{}
+	return false
+}
+
+// isVisitedHeader returns `true` if the *Schema pointer was already visited
+// otherwise it returns `false`
+func (doc *T) isVisitedSchema(s *Schema) bool {
+	if _, ok := doc.visited.schema[s]; ok {
+		return true
+	}
+
+	doc.visited.schema[s] = struct{}{}
+	return false
+}


Test patch:
diff --git a/openapi3/load_cicular_ref_with_external_file_test.go b/openapi3/load_cicular_ref_with_external_file_test.go
index 85b127e..978ef8f 100644
--- a/openapi3/load_cicular_ref_with_external_file_test.go
+++ b/openapi3/load_cicular_ref_with_external_file_test.go
@@ -5,6 +5,7 @@ package openapi3_test
 
 import (
 	"embed"
+	"encoding/json"
 	"net/url"
 	"testing"
 
@@ -24,38 +25,24 @@ func TestLoadCircularRefFromFile(t *testing.T) {
 	}
 
 	got, err := loader.LoadFromFile("testdata/circularRef/base.yml")
-	if err != nil {
-		t.Error(err)
-	}
+	require.NoError(t, err)
 
 	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",
+				"foo2": {
+					Ref: "other.yml#/components/schemas/Foo2", // reference to an external file
 					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{}{}},
-								}},
+								Value: &openapi3.Schema{Type: "string"}},
 						},
 					},
 				},
 			},
 		},
 	}
-	bar := &openapi3.SchemaRef{
-		Ref: "",
-		Value: &openapi3.Schema{
-			ExtensionProps: openapi3.ExtensionProps{Extensions: map[string]interface{}{}},
-			Properties:     map[string]*openapi3.SchemaRef{},
-		},
-	}
+	bar := &openapi3.SchemaRef{Value: &openapi3.Schema{Properties: make(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}
@@ -65,18 +52,19 @@ func TestLoadCircularRefFromFile(t *testing.T) {
 		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)
+	jsoner := func(doc *openapi3.T) string {
+		data, err := json.Marshal(doc)
+		require.NoError(t, err)
+		return string(data)
+	}
+	require.JSONEq(t, jsoner(want), jsoner(got))
 }
diff --git a/openapi3/testdata/recursiveRef/components/Cat.yml b/openapi3/testdata/recursiveRef/components/Cat.yml
new file mode 100644
index 0000000..c476aa1
--- /dev/null
+++ b/openapi3/testdata/recursiveRef/components/Cat.yml
@@ -0,0 +1,4 @@
+type: object
+properties:
+  cat:
+    $ref: ../openapi.yml#/components/schemas/Cat
diff --git a/openapi3/testdata/recursiveRef/openapi.yml b/openapi3/testdata/recursiveRef/openapi.yml
index 3559c8e..675722a 100644
--- a/openapi3/testdata/recursiveRef/openapi.yml
+++ b/openapi3/testdata/recursiveRef/openapi.yml
@@ -13,3 +13,5 @@ components:
       $ref: ./components/Foo/Foo2.yml
     Bar:
       $ref: ./components/Bar.yml
+    Cat:
+      $ref: ./components/Cat.yml
diff --git a/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
index d1260eb..0730590 100644
--- a/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
+++ b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
@@ -29,6 +29,14 @@
           }
         },
         "type": "object"
+      },
+      "Cat": {
+        "properties": {
+          "cat": {
+            "$ref": "#/components/schemas/Cat"
+          }
+        },
+        "type": "object"
       }
     }
   },