diff --git a/document_test.go b/document_test.go index dcd33c8..1f305ac 100644 --- a/document_test.go +++ b/document_test.go @@ -504,6 +504,35 @@ paths: assert.Equal(t, d, strings.TrimSpace(string(rend))) } + +func TestDocument_RemoteWithoutBaseURL(t *testing.T) { + + // This test will push the index to do try and locate remote references that use relative references + spec := `openapi: 3.0.2 +info: + title: Test + version: 1.0.0 +paths: + /test: + get: + parameters: + - $ref: "https://schemas.opengis.net/ogcapi/features/part2/1.0/openapi/ogcapi-features-2.yaml#/components/parameters/crs"` + + config := datamodel.NewOpenDocumentConfiguration() + + doc, err := NewDocumentWithConfiguration([]byte(spec), config) + if err != nil { + panic(err) + } + + result, errs := doc.BuildV3Model() + if len(errs) > 0 { + panic(errs) + } + + assert.Equal(t, "crs", result.Model.Paths.PathItems["/test"].Get.Parameters[0].Name) +} + func TestDocument_ExampleMap(t *testing.T) { var d = `openapi: "3.1" components: diff --git a/index/find_component.go b/index/find_component.go index 9ab66eb..0916062 100644 --- a/index/find_component.go +++ b/index/find_component.go @@ -4,369 +4,379 @@ package index import ( - "fmt" - "github.com/pb33f/libopenapi/utils" - "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" - "gopkg.in/yaml.v3" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "time" + "fmt" + "github.com/pb33f/libopenapi/utils" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "gopkg.in/yaml.v3" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" ) // FindComponent will locate a component by its reference, returns nil if nothing is found. // This method will recurse through remote, local and file references. For each new external reference // a new index will be created. These indexes can then be traversed recursively. func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference { - if index.root == nil { - return nil - } + if index.root == nil { + return nil + } - remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { - if index.config.AllowRemoteLookup { - return index.lookupRemoteReference(id) - } else { - return nil, nil, fmt.Errorf("remote lookups are not permitted, " + - "please set AllowRemoteLookup to true in the configuration") - } - } + remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { + if index.config.AllowRemoteLookup { + return index.lookupRemoteReference(id) + } else { + return nil, nil, fmt.Errorf("remote lookups are not permitted, " + + "please set AllowRemoteLookup to true in the configuration") + } + } - fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) { - if index.config.AllowFileLookup { - return index.lookupFileReference(id) - } else { - return nil, nil, fmt.Errorf("local lookups are not permitted, " + - "please set AllowFileLookup to true in the configuration") - } - } + fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) { + if index.config.AllowFileLookup { + return index.lookupFileReference(id) + } else { + return nil, nil, fmt.Errorf("local lookups are not permitted, " + + "please set AllowFileLookup to true in the configuration") + } + } - switch DetermineReferenceResolveType(componentId) { - case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. - return index.FindComponentInRoot(componentId) + switch DetermineReferenceResolveType(componentId) { + case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. + return index.FindComponentInRoot(componentId) - case HttpResolve: - uri := strings.Split(componentId, "#") - if len(uri) >= 2 { - return index.performExternalLookup(uri, componentId, remoteLookup, parent) - } - if len(uri) == 1 { - // if there is no reference, second segment is empty / has no name - // this means there is no component to look-up and the entire file should be pulled in. - // to stop all the other code from breaking (that is expecting a component), let's just post-pend - // a hash to the end of the componentId and ensure the uri slice is as expected. - // described in https://github.com/pb33f/libopenapi/issues/37 - componentId = fmt.Sprintf("%s#", componentId) - uri = append(uri, "") - return index.performExternalLookup(uri, componentId, remoteLookup, parent) - } + case HttpResolve: + uri := strings.Split(componentId, "#") + if len(uri) >= 2 { + return index.performExternalLookup(uri, componentId, remoteLookup, parent) + } + if len(uri) == 1 { + // if there is no reference, second segment is empty / has no name + // this means there is no component to look-up and the entire file should be pulled in. + // to stop all the other code from breaking (that is expecting a component), let's just post-pend + // a hash to the end of the componentId and ensure the uri slice is as expected. + // described in https://github.com/pb33f/libopenapi/issues/37 + componentId = fmt.Sprintf("%s#", componentId) + uri = append(uri, "") + return index.performExternalLookup(uri, componentId, remoteLookup, parent) + } - case FileResolve: - uri := strings.Split(componentId, "#") - if len(uri) == 2 { - return index.performExternalLookup(uri, componentId, fileLookup, parent) - } - if len(uri) == 1 { - // if there is no reference, second segment is empty / has no name - // this means there is no component to look-up and the entire file should be pulled in. - // to stop all the other code from breaking (that is expecting a component), let's just post-pend - // a hash to the end of the componentId and ensure the uri slice is as expected. - // described in https://github.com/pb33f/libopenapi/issues/37 - // - // ^^ this same issue was re-reported in file based lookups in vacuum. - // more info here: https://github.com/daveshanley/vacuum/issues/225 - componentId = fmt.Sprintf("%s#", componentId) - uri = append(uri, "") - return index.performExternalLookup(uri, componentId, fileLookup, parent) - } - } - return nil + case FileResolve: + uri := strings.Split(componentId, "#") + if len(uri) == 2 { + return index.performExternalLookup(uri, componentId, fileLookup, parent) + } + if len(uri) == 1 { + // if there is no reference, second segment is empty / has no name + // this means there is no component to look-up and the entire file should be pulled in. + // to stop all the other code from breaking (that is expecting a component), let's just post-pend + // a hash to the end of the componentId and ensure the uri slice is as expected. + // described in https://github.com/pb33f/libopenapi/issues/37 + // + // ^^ this same issue was re-reported in file based lookups in vacuum. + // more info here: https://github.com/daveshanley/vacuum/issues/225 + componentId = fmt.Sprintf("%s#", componentId) + uri = append(uri, "") + return index.performExternalLookup(uri, componentId, fileLookup, parent) + } + } + return nil } var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second} func getRemoteDoc(u string, d chan []byte, e chan error) { - resp, err := httpClient.Get(u) - if err != nil { - e <- err - close(e) - close(d) - return - } - var body []byte - body, _ = ioutil.ReadAll(resp.Body) - d <- body - close(e) - close(d) + resp, err := httpClient.Get(u) + if err != nil { + e <- err + close(e) + close(d) + return + } + var body []byte + body, _ = ioutil.ReadAll(resp.Body) + d <- body + close(e) + close(d) } func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) { - // split string to remove file reference - uri := strings.Split(ref, "#") + // split string to remove file reference + uri := strings.Split(ref, "#") - // have we already seen this remote source? - var parsedRemoteDocument *yaml.Node - alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0]) + // have we already seen this remote source? + var parsedRemoteDocument *yaml.Node + alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0]) - if alreadySeen { - parsedRemoteDocument = foundDocument - } else { + if alreadySeen { + parsedRemoteDocument = foundDocument + } else { - d := make(chan bool) - var body []byte - var err error + d := make(chan bool) + var body []byte + var err error - go func(uri string) { - bc := make(chan []byte) - ec := make(chan error) - go getRemoteDoc(uri, bc, ec) - select { - case v := <-bc: - body = v - break - case er := <-ec: - err = er - break - } - var remoteDoc yaml.Node - er := yaml.Unmarshal(body, &remoteDoc) - if er != nil { - err = er - d <- true - return - } - parsedRemoteDocument = &remoteDoc - if index.config != nil { - index.config.seenRemoteSources.Store(uri, &remoteDoc) - } - d <- true - }(uri[0]) + go func(uri string) { + bc := make(chan []byte) + ec := make(chan error) + go getRemoteDoc(uri, bc, ec) + select { + case v := <-bc: + body = v + break + case er := <-ec: + err = er + break + } + var remoteDoc yaml.Node + er := yaml.Unmarshal(body, &remoteDoc) + if er != nil { + err = er + d <- true + return + } + parsedRemoteDocument = &remoteDoc + if index.config != nil { + index.config.seenRemoteSources.Store(uri, &remoteDoc) + } + d <- true + }(uri[0]) - // wait for double go fun. - <-d - if err != nil { - // no bueno. - return nil, nil, err - } - } + // wait for double go fun. + <-d + if err != nil { + // no bueno. + return nil, nil, err + } + } - // lookup item from reference by using a path query. - var query string - if len(uri) >= 2 { - query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) - } else { - query = "$" - } + // lookup item from reference by using a path query. + var query string + if len(uri) >= 2 { + query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) + } else { + query = "$" + } - // remove any URL encoding - query = strings.Replace(query, "~1", "./", 1) - query = strings.ReplaceAll(query, "~1", "/") + // remove any URL encoding + query = strings.Replace(query, "~1", "./", 1) + query = strings.ReplaceAll(query, "~1", "/") - path, err := yamlpath.NewPath(query) - if err != nil { - return nil, nil, err - } - result, _ := path.Find(parsedRemoteDocument) - if len(result) == 1 { - return result[0], parsedRemoteDocument, nil - } - return nil, nil, nil + path, err := yamlpath.NewPath(query) + if err != nil { + return nil, nil, err + } + result, _ := path.Find(parsedRemoteDocument) + if len(result) == 1 { + return result[0], parsedRemoteDocument, nil + } + return nil, nil, nil } func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) { - // split string to remove file reference - uri := strings.Split(ref, "#") - file := strings.ReplaceAll(uri[0], "file:", "") - filePath := filepath.Dir(file) - fileName := filepath.Base(file) + // split string to remove file reference + uri := strings.Split(ref, "#") + file := strings.ReplaceAll(uri[0], "file:", "") + filePath := filepath.Dir(file) + fileName := filepath.Base(file) - var parsedRemoteDocument *yaml.Node + var parsedRemoteDocument *yaml.Node - if index.seenRemoteSources[file] != nil { - parsedRemoteDocument = index.seenRemoteSources[file] - } else { + if index.seenRemoteSources[file] != nil { + parsedRemoteDocument = index.seenRemoteSources[file] + } else { - base := index.config.BasePath - fileToRead := filepath.Join(base, filePath, fileName) + base := index.config.BasePath + fileToRead := filepath.Join(base, filePath, fileName) - // try and read the file off the local file system, if it fails - // check for a baseURL and then ask our remote lookup function to go try and get it. - body, err := os.ReadFile(fileToRead) + // try and read the file off the local file system, if it fails + // check for a baseURL and then ask our remote lookup function to go try and get it. + body, err := os.ReadFile(fileToRead) - if err != nil { + if err != nil { - // if we have a baseURL, then we can try and get the file from there. - if index.config != nil && index.config.BaseURL != nil { + // if we have a baseURL, then we can try and get the file from there. + if index.config != nil && index.config.BaseURL != nil { - u := index.config.BaseURL - remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true) - a, b, e := index.lookupRemoteReference(remoteRef) - if e != nil { - // give up, we can't find the file, not locally, not remotely. It's toast. - return nil, nil, e - } - return a, b, nil + u := index.config.BaseURL + remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true) + a, b, e := index.lookupRemoteReference(remoteRef) + if e != nil { + // give up, we can't find the file, not locally, not remotely. It's toast. + return nil, nil, e + } + return a, b, nil - } else { - // no baseURL? then we can't do anything, give up. - return nil, nil, err - } - } + } else { + // no baseURL? then we can't do anything, give up. + return nil, nil, err + } + } - var remoteDoc yaml.Node - err = yaml.Unmarshal(body, &remoteDoc) - if err != nil { - return nil, nil, err - } - parsedRemoteDocument = &remoteDoc - if index.seenLocalSources != nil { - index.sourceLock.Lock() - index.seenLocalSources[file] = &remoteDoc - index.sourceLock.Unlock() - } - } + var remoteDoc yaml.Node + err = yaml.Unmarshal(body, &remoteDoc) + if err != nil { + return nil, nil, err + } + parsedRemoteDocument = &remoteDoc + if index.seenLocalSources != nil { + index.sourceLock.Lock() + index.seenLocalSources[file] = &remoteDoc + index.sourceLock.Unlock() + } + } - // lookup item from reference by using a path query. - var query string - if len(uri) >= 2 { - query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) - } else { - query = "$" - } + // lookup item from reference by using a path query. + var query string + if len(uri) >= 2 { + query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) + } else { + query = "$" + } - // remove any URL encoding - query = strings.Replace(query, "~1", "./", 1) - query = strings.ReplaceAll(query, "~1", "/") + // remove any URL encoding + query = strings.Replace(query, "~1", "./", 1) + query = strings.ReplaceAll(query, "~1", "/") - path, err := yamlpath.NewPath(query) - if err != nil { - return nil, nil, err - } - result, _ := path.Find(parsedRemoteDocument) - if len(result) == 1 { - return result[0], parsedRemoteDocument, nil - } + path, err := yamlpath.NewPath(query) + if err != nil { + return nil, nil, err + } + result, _ := path.Find(parsedRemoteDocument) + if len(result) == 1 { + return result[0], parsedRemoteDocument, nil + } - return nil, parsedRemoteDocument, nil + return nil, parsedRemoteDocument, nil } func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { - if index.root != nil { + if index.root != nil { - // check component for url encoding. - if strings.Contains(componentId, "%") { - // decode the url. - componentId, _ = url.QueryUnescape(componentId) - } + // check component for url encoding. + if strings.Contains(componentId, "%") { + // decode the url. + componentId, _ = url.QueryUnescape(componentId) + } - name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) - path, err := yamlpath.NewPath(friendlySearch) - if path == nil || err != nil { - return nil // no component found - } - res, _ := path.Find(index.root) - if len(res) == 1 { - ref := &Reference{ - Definition: componentId, - Name: name, - Node: res[0], - Path: friendlySearch, - RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}), - } + name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) + path, err := yamlpath.NewPath(friendlySearch) + if path == nil || err != nil { + return nil // no component found + } + res, _ := path.Find(index.root) + if len(res) == 1 { + ref := &Reference{ + Definition: componentId, + Name: name, + Node: res[0], + Path: friendlySearch, + RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}), + } - return ref - } - } - return nil + return ref + } + } + return nil } func (index *SpecIndex) performExternalLookup(uri []string, componentId string, - lookupFunction ExternalLookupFunction, parent *yaml.Node, + lookupFunction ExternalLookupFunction, parent *yaml.Node, ) *Reference { - if len(uri) > 0 { - index.externalLock.RLock() - externalSpecIndex := index.externalSpecIndex[uri[0]] - index.externalLock.RUnlock() + if len(uri) > 0 { + index.externalLock.RLock() + externalSpecIndex := index.externalSpecIndex[uri[0]] + index.externalLock.RUnlock() - if externalSpecIndex == nil { - _, newRoot, err := lookupFunction(componentId) - if err != nil { - indexError := &IndexingError{ - Err: err, - Node: parent, - Path: componentId, - } - index.errorLock.Lock() - index.refErrors = append(index.refErrors, indexError) - index.errorLock.Unlock() - return nil - } + if externalSpecIndex == nil { + _, newRoot, err := lookupFunction(componentId) + if err != nil { + indexError := &IndexingError{ + Err: err, + Node: parent, + Path: componentId, + } + index.errorLock.Lock() + index.refErrors = append(index.refErrors, indexError) + index.errorLock.Unlock() + return nil + } - // cool, cool, lets index this spec also. This is a recursive action and will keep going - // until all remote references have been found. - var bp *url.URL - var bd string + // cool, cool, lets index this spec also. This is a recursive action and will keep going + // until all remote references have been found. + var bp *url.URL + var bd string - if index.config.BaseURL != nil { - bp = index.config.BaseURL - } - if index.config.BasePath != "" { - bd = index.config.BasePath - } + if index.config.BaseURL != nil { + bp = index.config.BaseURL + } + if index.config.BasePath != "" { + bd = index.config.BasePath + } - var path, newBasePath string - var newUrl *url.URL + var path, newBasePath string + var newUrl *url.URL - if bp != nil { - path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false) - newUrl, _ = url.Parse(path) - newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path))) - } - if bd != "" { - newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) - } + if bp != nil { + path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false) + newUrl, _ = url.Parse(path) + newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path))) + } + if bd != "" { + if len(uri[0]) > 0 { + // if there is no base url defined, but we can know we have been requested remotely, + // set the base url to the remote url base path. + if newUrl == nil || newUrl.String() != uri[0] { + ur, _ := url.Parse(uri[0]) + newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", ur.Scheme, ur.Host, filepath.Dir(ur.Path))) + } + newBasePath = filepath.Dir(filepath.Join(bd, uri[1])) + } else { + newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) + } + } - if newUrl != nil || newBasePath != "" { - newConfig := &SpecIndexConfig{ - BaseURL: newUrl, - BasePath: newBasePath, - AllowRemoteLookup: index.config.AllowRemoteLookup, - AllowFileLookup: index.config.AllowFileLookup, - seenRemoteSources: index.config.seenRemoteSources, - remoteLock: index.config.remoteLock, - } - - var newIndex *SpecIndex - newIndex = NewSpecIndexWithConfig(newRoot, newConfig) - index.refLock.Lock() - index.externalLock.Lock() - index.externalSpecIndex[uri[0]] = newIndex - index.externalLock.Unlock() - newIndex.relativePath = path - newIndex.parentIndex = index - index.AddChild(newIndex) - index.refLock.Unlock() - externalSpecIndex = newIndex - } - } + if newUrl != nil || newBasePath != "" { + newConfig := &SpecIndexConfig{ + BaseURL: newUrl, + BasePath: newBasePath, + AllowRemoteLookup: index.config.AllowRemoteLookup, + AllowFileLookup: index.config.AllowFileLookup, + seenRemoteSources: index.config.seenRemoteSources, + remoteLock: index.config.remoteLock, + } - if externalSpecIndex != nil { - foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) - if foundRef != nil { - nameSegs := strings.Split(uri[1], "/") - ref := &Reference{ - Definition: componentId, - Name: nameSegs[len(nameSegs)-1], - Node: foundRef.Node, - IsRemote: true, - RemoteLocation: componentId, - Path: foundRef.Path, - } - return ref - } - } - } - return nil + var newIndex *SpecIndex + newIndex = NewSpecIndexWithConfig(newRoot, newConfig) + index.refLock.Lock() + index.externalLock.Lock() + index.externalSpecIndex[uri[0]] = newIndex + index.externalLock.Unlock() + newIndex.relativePath = path + newIndex.parentIndex = index + index.AddChild(newIndex) + index.refLock.Unlock() + externalSpecIndex = newIndex + } + } + + if externalSpecIndex != nil { + foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) + if foundRef != nil { + nameSegs := strings.Split(uri[1], "/") + ref := &Reference{ + Definition: componentId, + Name: nameSegs[len(nameSegs)-1], + Node: foundRef.Node, + IsRemote: true, + RemoteLocation: componentId, + Path: foundRef.Path, + } + return ref + } + } + } + return nil } diff --git a/index/find_component_test.go b/index/find_component_test.go index cd6ddce..82ac5e2 100644 --- a/index/find_component_test.go +++ b/index/find_component_test.go @@ -4,13 +4,13 @@ package index import ( - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" - "testing" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" ) func TestSpecIndex_performExternalLookup(t *testing.T) { - yml := `{ + yml := `{ "openapi": "3.1.0", "paths": [ {"/": { @@ -18,50 +18,50 @@ func TestSpecIndex_performExternalLookup(t *testing.T) { }} ] }` - var rootNode yaml.Node - _ = yaml.Unmarshal([]byte(yml), &rootNode) + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &rootNode) - c := CreateOpenAPIIndexConfig() - index := NewSpecIndexWithConfig(&rootNode, c) - assert.Len(t, index.GetPathsNode().Content, 1) + c := CreateOpenAPIIndexConfig() + index := NewSpecIndexWithConfig(&rootNode, c) + assert.Len(t, index.GetPathsNode().Content, 1) } func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) { - yml := `openapi: 3.1.0 + yml := `openapi: 3.1.0 components: schemas: thing: properties: thong: $ref: 'httpssss://not-gonna-work.com'` - var rootNode yaml.Node - _ = yaml.Unmarshal([]byte(yml), &rootNode) + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &rootNode) - c := CreateOpenAPIIndexConfig() - index := NewSpecIndexWithConfig(&rootNode, c) - assert.Len(t, index.GetReferenceIndexErrors(), 2) + c := CreateOpenAPIIndexConfig() + index := NewSpecIndexWithConfig(&rootNode, c) + assert.Len(t, index.GetReferenceIndexErrors(), 2) } func TestSpecIndex_FindComponentInRoot(t *testing.T) { - yml := `openapi: 3.1.0 + yml := `openapi: 3.1.0 components: schemas: thing: properties: thong: hi!` - var rootNode yaml.Node - _ = yaml.Unmarshal([]byte(yml), &rootNode) + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &rootNode) - c := CreateOpenAPIIndexConfig() - index := NewSpecIndexWithConfig(&rootNode, c) + c := CreateOpenAPIIndexConfig() + index := NewSpecIndexWithConfig(&rootNode, c) - thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./") - assert.Nil(t, thing) - assert.Len(t, index.GetReferenceIndexErrors(), 0) + thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./") + assert.Nil(t, thing) + assert.Len(t, index.GetReferenceIndexErrors(), 0) } func TestSpecIndex_FailLookupRemoteComponent_badPath(t *testing.T) { - yml := `openapi: 3.1.0 + yml := `openapi: 3.1.0 components: schemas: thing: @@ -69,19 +69,19 @@ components: thong: $ref: 'https://pb33f.io/site.webmanifest#/....$.ok../oh#/$$_-'` - var rootNode yaml.Node - _ = yaml.Unmarshal([]byte(yml), &rootNode) + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &rootNode) - c := CreateOpenAPIIndexConfig() - index := NewSpecIndexWithConfig(&rootNode, c) + c := CreateOpenAPIIndexConfig() + index := NewSpecIndexWithConfig(&rootNode, c) - thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./") - assert.Nil(t, thing) - assert.Len(t, index.GetReferenceIndexErrors(), 2) + thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./") + assert.Nil(t, thing) + assert.Len(t, index.GetReferenceIndexErrors(), 2) } func TestSpecIndex_FailLookupRemoteComponent_Ok_butNotFound(t *testing.T) { - yml := `openapi: 3.1.0 + yml := `openapi: 3.1.0 components: schemas: thing: @@ -89,13 +89,40 @@ components: thong: $ref: 'https://pb33f.io/site.webmanifest#/valid-but-missing'` - var rootNode yaml.Node - _ = yaml.Unmarshal([]byte(yml), &rootNode) + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &rootNode) - c := CreateOpenAPIIndexConfig() - index := NewSpecIndexWithConfig(&rootNode, c) + c := CreateOpenAPIIndexConfig() + index := NewSpecIndexWithConfig(&rootNode, c) - thing := index.FindComponentInRoot("#/valid-but-missing") - assert.Nil(t, thing) - assert.Len(t, index.GetReferenceIndexErrors(), 1) + thing := index.FindComponentInRoot("#/valid-but-missing") + assert.Nil(t, thing) + assert.Len(t, index.GetReferenceIndexErrors(), 1) +} + +func TestSpecIndex_LocateRemoteDocsWithNoBaseURLSupplied(t *testing.T) { + // This test will push the index to do try and locate remote references that use relative references + spec := `openapi: 3.0.2 +info: + title: Test + version: 1.0.0 +paths: + /test: + get: + parameters: + - $ref: "https://schemas.opengis.net/ogcapi/features/part2/1.0/openapi/ogcapi-features-2.yaml#/components/parameters/crs"` + + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(spec), &rootNode) + + c := CreateOpenAPIIndexConfig() + index := NewSpecIndexWithConfig(&rootNode, c) + + // extract crs param from index + crsParam := index.GetMappedReferences()["https://schemas.opengis.net/ogcapi/features/part2/1.0/openapi/ogcapi-features-2.yaml#/components/parameters/crs"] + assert.NotNil(t, crsParam) + assert.True(t, crsParam.IsRemote) + assert.Equal(t, "crs", crsParam.Node.Content[1].Value) + assert.Equal(t, "query", crsParam.Node.Content[3].Value) + assert.Equal(t, "form", crsParam.Node.Content[9].Value) }