diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go index d6674622e..d5869518f 100644 --- a/openapi3/swagger_loader.go +++ b/openapi3/swagger_loader.go @@ -30,10 +30,11 @@ type SwaggerLoader struct { Context context.Context LoadSwaggerFromURIFunc func(loader *SwaggerLoader, url *url.URL) (*Swagger, error) visited map[interface{}]struct{} + loadedRemoteSchemas map[url.URL]*Swagger } func NewSwaggerLoader() *SwaggerLoader { - return &SwaggerLoader{} + return &SwaggerLoader{loadedRemoteSchemas: map[url.URL]*Swagger{}} } func (swaggerLoader *SwaggerLoader) LoadSwaggerFromURI(location *url.URL) (*Swagger, error) { @@ -222,9 +223,13 @@ func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref strin return nil, "", nil, fmt.Errorf("Error while resolving path: %v", err) } - if swagger, err = swaggerLoader.LoadSwaggerFromURI(resolvedPath); err != nil { - return nil, "", nil, fmt.Errorf("Error while resolving reference '%s': %v", ref, err) + if swg2, ok := swaggerLoader.loadedRemoteSchemas[*parsedURL]; !ok { + if swg2, err = swaggerLoader.LoadSwaggerFromURI(resolvedPath); err != nil { + return nil, "", nil, fmt.Errorf("Error while resolving reference '%s': %v", ref, err) + } + swaggerLoader.loadedRemoteSchemas[*parsedURL] = swg2 } + swagger = swaggerLoader.loadedRemoteSchemas[*parsedURL] ref = fmt.Sprintf("#%s", fragment) componentPath = resolvedPath } diff --git a/openapi3/swagger_loader_test.go b/openapi3/swagger_loader_test.go index 1e1bcaedd..c704a056b 100644 --- a/openapi3/swagger_loader_test.go +++ b/openapi3/swagger_loader_test.go @@ -307,10 +307,10 @@ func TestLoadFromRemoteURL(t *testing.T) { loader := openapi3.NewSwaggerLoader() loader.IsExternalRefsAllowed = true - url, err := url.Parse("http://" + addr + "/test.openapi.json") + remote, err := url.Parse("http://" + addr + "/test.openapi.json") require.NoError(t, err) - swagger, err := loader.LoadSwaggerFromURI(url) + swagger, err := loader.LoadSwaggerFromURI(remote) require.NoError(t, err) require.Equal(t, "string", swagger.Components.Schemas["TestSchema"].Value.Type) @@ -423,3 +423,34 @@ func TestLoadYamlFileWithExternalSchemaRef(t *testing.T) { require.NotNil(t, swagger.Components.Schemas["AnotherTestSchema"].Value.Type) } + +type hitCntFS struct { + fs http.Dir + hits map[string]int +} + +func (fs hitCntFS) Open(fn string) (http.File, error) { + fs.hits[fn] = fs.hits[fn] + 1 + return fs.fs.Open(fn) +} + +func TestRemoteURLCaching(t *testing.T) { + + sfs := hitCntFS{fs: "testdata", hits: map[string]int{}} + fs := http.FileServer(sfs) + ts := createTestServer(fs) + ts.Start() + defer ts.Close() + + loader := openapi3.NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + remote, err := url.Parse("http://" + addr + "/test.refcache.openapi.yml") + require.NoError(t, err) + + _, err = loader.LoadSwaggerFromURI(remote) + require.NoError(t, err) + + require.Contains(t, sfs.hits, "/test.refcache.openapi.yml") + require.Contains(t, sfs.hits, "/components.openapi.yml") + require.Equal(t, 1, sfs.hits["http://localhost:7965/components.openapi.yml"], "expcting 1 load of referenced schema") +} diff --git a/openapi3/testdata/test.refcache.openapi.yml b/openapi3/testdata/test.refcache.openapi.yml new file mode 100644 index 000000000..7f0744ae0 --- /dev/null +++ b/openapi3/testdata/test.refcache.openapi.yml @@ -0,0 +1,15 @@ +--- +openapi: 3.0.0 +info: + title: 'OAI Specification w/ refs in YAML. Multiple refs to the same remote schema' + version: '1' +paths: {} +components: + schemas: + AnotherTestSchema: + type: object + properties: + ref1: + "$ref": http://localhost:7965/components.openapi.yml#/components/schemas/CustomTestSchema + ref2: + "$ref": http://localhost:7965/components.openapi.yml#/components/schemas/Name