Repository: getkin/kin-openapi (go)

Issue:
Resolving reference error happens with directories of different depths
Hi. I found the recursive lookup will fail when there are files in directories of different depths.

reporduce: https://github.com/stakme/kin-openapi/commit/cefedea2d71ad4ce07f2739ecbaea4c4f113f9cd

```
> go test github.com/getkin/kin-openapi/openapi3
--- FAIL: TestLoaderReadFromURIFunc (0.00s)
    loader_read_from_uri_func_test.go:20: 
                Error Trace:    loader_read_from_uri_func_test.go:20
                Error:          Received unexpected error:
                                error resolving reference "../openapi.yml#/components/schemas/Bar": open testdata/recursiveRef/components/openapi.yml: no such file or directory
                Test:           TestLoaderReadFromURIFunc
--- FAIL: TestLoaderSupportsRecursiveReference (0.00s)
    loader_recursive_ref_test.go:13: 
                Error Trace:    loader_recursive_ref_test.go:13
                Error:          Received unexpected error:
                                error resolving reference "../openapi.yml#/components/schemas/Bar": open testdata/recursiveRef/components/openapi.yml: no such file or directory
                Test:           TestLoaderSupportsRecursiveReference
FAIL
FAIL    github.com/getkin/kin-openapi/openapi3  0.192s
FAIL
```

It seems that, each time it resolve the reference, it needs to update the `documentPath`.


Pull request:
Fix handling recursive refs
Fix #402

Patch:
diff --git a/openapi3/loader.go b/openapi3/loader.go
index da34797..c42d8ba 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -35,6 +35,8 @@ type Loader struct {
 
 	Context context.Context
 
+	rootDir string
+
 	visitedPathItemRefs map[string]struct{}
 
 	visitedDocuments map[string]*T
@@ -66,6 +68,7 @@ func (loader *Loader) LoadFromURI(location *url.URL) (*T, error) {
 
 // LoadFromFile loads a spec from a local file path
 func (loader *Loader) LoadFromFile(location string) (*T, error) {
+	loader.rootDir = path.Dir(location)
 	return loader.LoadFromURI(&url.URL{Path: filepath.ToSlash(location)})
 }
 
@@ -415,6 +418,14 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
 	}
 }
 
+func (loader *Loader) documentPathForRecursiveRef(current *url.URL, resolvedRef string) *url.URL {
+	if loader.rootDir == "" {
+		return current
+	}
+	return &url.URL{Path: path.Join(loader.rootDir, resolvedRef)}
+
+}
+
 func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) {
 	if ref != "" && ref[0] == '#' {
 		return doc, ref, path, nil
@@ -474,6 +485,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	value := component.Value
@@ -521,6 +533,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	value := component.Value
@@ -577,6 +590,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	value := component.Value
@@ -632,6 +646,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	value := component.Value
@@ -701,6 +716,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	value := component.Value
@@ -778,6 +794,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	return nil
@@ -814,6 +831,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	return nil
@@ -841,6 +859,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	value := component.Value
@@ -938,6 +957,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
 				return err
 			}
 			component.Value = resolved.Value
+			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
 		}
 	}
 	return nil


Test patch:
diff --git a/openapi3/loader_read_from_uri_func_test.go b/openapi3/loader_read_from_uri_func_test.go
index 72d4a95..8fee2f4 100644
--- a/openapi3/loader_read_from_uri_func_test.go
+++ b/openapi3/loader_read_from_uri_func_test.go
@@ -20,7 +20,7 @@ func TestLoaderReadFromURIFunc(t *testing.T) {
 	require.NoError(t, err)
 	require.NotNil(t, doc)
 	require.NoError(t, doc.Validate(loader.Context))
-	require.Equal(t, "bar", doc.Paths["/foo"].Get.Responses.Get(200).Value.Content.Get("application/json").Schema.Value.Properties["foo"].Value.Properties["bar"].Value.Items.Value.Example)
+	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)
 }
 
 type multipleSourceLoaderExample struct {
diff --git a/openapi3/loader_recursive_ref_test.go b/openapi3/loader_recursive_ref_test.go
index bc1f24b..bd65903 100644
--- a/openapi3/loader_recursive_ref_test.go
+++ b/openapi3/loader_recursive_ref_test.go
@@ -13,5 +13,5 @@ func TestLoaderSupportsRecursiveReference(t *testing.T) {
 	require.NoError(t, err)
 	require.NotNil(t, doc)
 	require.NoError(t, doc.Validate(loader.Context))
-	require.Equal(t, "bar", doc.Paths["/foo"].Get.Responses.Get(200).Value.Content.Get("application/json").Schema.Value.Properties["foo"].Value.Properties["bar"].Value.Items.Value.Example)
+	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)
 }
diff --git a/openapi3/testdata/load_with_go_embed_test.go b/openapi3/testdata/load_with_go_embed_test.go
index a5993dc..56b274c 100644
--- a/openapi3/testdata/load_with_go_embed_test.go
+++ b/openapi3/testdata/load_with_go_embed_test.go
@@ -27,6 +27,6 @@ func Example() {
 		panic(err)
 	}
 
-	fmt.Println(doc.Paths["/foo"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties["foo"].Value.Properties["bar"].Value.Type)
-	// Output: array
+	fmt.Println(doc.Paths["/foo"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Type)
+	// Output: string
 }
diff --git a/openapi3/testdata/recursiveRef/components/Foo.yml b/openapi3/testdata/recursiveRef/components/Foo.yml
index 0c08992..53a2336 100644
--- a/openapi3/testdata/recursiveRef/components/Foo.yml
+++ b/openapi3/testdata/recursiveRef/components/Foo.yml
@@ -1,6 +1,4 @@
 type: object
 properties:
   bar:
-    type: array
-    items:
-      $ref: ../openapi.yml#/components/schemas/Bar
+    $ref: ../openapi.yml#/components/schemas/Bar
diff --git a/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml b/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml
new file mode 100644
index 0000000..aeac81f
--- /dev/null
+++ b/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml
@@ -0,0 +1,4 @@
+type: object
+properties:
+  foo:
+    $ref: ../../openapi.yml#/components/schemas/Foo
diff --git a/openapi3/testdata/recursiveRef/openapi.yml b/openapi3/testdata/recursiveRef/openapi.yml
index 5dfcfbf..3559c8e 100644
--- a/openapi3/testdata/recursiveRef/openapi.yml
+++ b/openapi3/testdata/recursiveRef/openapi.yml
@@ -9,5 +9,7 @@ components:
   schemas:
     Foo:
       $ref: ./components/Foo.yml
+    Foo2:
+      $ref: ./components/Foo/Foo2.yml
     Bar:
       $ref: ./components/Bar.yml
diff --git a/openapi3/testdata/recursiveRef/paths/foo.yml b/openapi3/testdata/recursiveRef/paths/foo.yml
index dd6c15d..1653c7a 100644
--- a/openapi3/testdata/recursiveRef/paths/foo.yml
+++ b/openapi3/testdata/recursiveRef/paths/foo.yml
@@ -7,5 +7,5 @@ get:
           schema:
             type: object
             properties:
-              foo:
-                $ref: ../openapi.yml#/components/schemas/Foo
+              foo2:
+                $ref: ../openapi.yml#/components/schemas/Foo2