From 7b543ac90909e7ef9301aaff3a8f2157cc6a69b1 Mon Sep 17 00:00:00 2001 From: bonny Date: Tue, 24 Sep 2019 10:48:33 -0700 Subject: [PATCH] added remote schema cache. Loaded schemas are stored in the cache once they are loaded and resolved. this avoids hundreds of roundtrips to remote server for schemas that contain multiple references --- openapi3/swagger_loader.go | 11 ++++-- openapi3/swagger_loader_test.go | 43 ++++++++++++++++++--- openapi3/testdata/test.refcache.openapi.yml | 15 +++++++ 3 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 openapi3/testdata/test.refcache.openapi.yml 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..0bf39b566 100644 --- a/openapi3/swagger_loader_test.go +++ b/openapi3/swagger_loader_test.go @@ -184,10 +184,10 @@ func TestResolveSchemaExternalRef(t *testing.T) { }, }, } - loader := &openapi3.SwaggerLoader{ - IsExternalRefsAllowed: true, - LoadSwaggerFromURIFunc: multipleSourceLoader.LoadSwaggerFromURI, - } + loader := openapi3.NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + loader.LoadSwaggerFromURIFunc = multipleSourceLoader.LoadSwaggerFromURI + doc, err := loader.LoadSwaggerFromURI(rootLocation) require.NoError(t, err) err = doc.Validate(loader.Context) @@ -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["/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