Corrected remote document lookup behavior

When remote documents are requested and there is no way to know the base URL, the index will now try and determine the base path from the way the spec was loaded (if it comes in remote, we use the base URL of the spec)
This commit is contained in:
Dave Shanley
2023-04-02 12:19:46 -04:00
parent ec83a9ca9b
commit 24d094ca7b
3 changed files with 410 additions and 344 deletions

View File

@@ -504,6 +504,35 @@ paths:
assert.Equal(t, d, strings.TrimSpace(string(rend))) 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) { func TestDocument_ExampleMap(t *testing.T) {
var d = `openapi: "3.1" var d = `openapi: "3.1"
components: components:

View File

@@ -4,369 +4,379 @@
package index package index
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
) )
// FindComponent will locate a component by its reference, returns nil if nothing is found. // 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 // 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. // a new index will be created. These indexes can then be traversed recursively.
func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference { func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference {
if index.root == nil { if index.root == nil {
return nil return nil
} }
remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
if index.config.AllowRemoteLookup { if index.config.AllowRemoteLookup {
return index.lookupRemoteReference(id) return index.lookupRemoteReference(id)
} else { } else {
return nil, nil, fmt.Errorf("remote lookups are not permitted, " + return nil, nil, fmt.Errorf("remote lookups are not permitted, " +
"please set AllowRemoteLookup to true in the configuration") "please set AllowRemoteLookup to true in the configuration")
} }
} }
fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) { fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
if index.config.AllowFileLookup { if index.config.AllowFileLookup {
return index.lookupFileReference(id) return index.lookupFileReference(id)
} else { } else {
return nil, nil, fmt.Errorf("local lookups are not permitted, " + return nil, nil, fmt.Errorf("local lookups are not permitted, " +
"please set AllowFileLookup to true in the configuration") "please set AllowFileLookup to true in the configuration")
} }
} }
switch DetermineReferenceResolveType(componentId) { switch DetermineReferenceResolveType(componentId) {
case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case.
return index.FindComponentInRoot(componentId) return index.FindComponentInRoot(componentId)
case HttpResolve: case HttpResolve:
uri := strings.Split(componentId, "#") uri := strings.Split(componentId, "#")
if len(uri) >= 2 { if len(uri) >= 2 {
return index.performExternalLookup(uri, componentId, remoteLookup, parent) return index.performExternalLookup(uri, componentId, remoteLookup, parent)
} }
if len(uri) == 1 { if len(uri) == 1 {
// if there is no reference, second segment is empty / has no name // 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. // 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 // 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. // 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 // described in https://github.com/pb33f/libopenapi/issues/37
componentId = fmt.Sprintf("%s#", componentId) componentId = fmt.Sprintf("%s#", componentId)
uri = append(uri, "") uri = append(uri, "")
return index.performExternalLookup(uri, componentId, remoteLookup, parent) return index.performExternalLookup(uri, componentId, remoteLookup, parent)
} }
case FileResolve: case FileResolve:
uri := strings.Split(componentId, "#") uri := strings.Split(componentId, "#")
if len(uri) == 2 { if len(uri) == 2 {
return index.performExternalLookup(uri, componentId, fileLookup, parent) return index.performExternalLookup(uri, componentId, fileLookup, parent)
} }
if len(uri) == 1 { if len(uri) == 1 {
// if there is no reference, second segment is empty / has no name // 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. // 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 // 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. // 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 // described in https://github.com/pb33f/libopenapi/issues/37
// //
// ^^ this same issue was re-reported in file based lookups in vacuum. // ^^ this same issue was re-reported in file based lookups in vacuum.
// more info here: https://github.com/daveshanley/vacuum/issues/225 // more info here: https://github.com/daveshanley/vacuum/issues/225
componentId = fmt.Sprintf("%s#", componentId) componentId = fmt.Sprintf("%s#", componentId)
uri = append(uri, "") uri = append(uri, "")
return index.performExternalLookup(uri, componentId, fileLookup, parent) return index.performExternalLookup(uri, componentId, fileLookup, parent)
} }
} }
return nil return nil
} }
var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second} var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second}
func getRemoteDoc(u string, d chan []byte, e chan error) { func getRemoteDoc(u string, d chan []byte, e chan error) {
resp, err := httpClient.Get(u) resp, err := httpClient.Get(u)
if err != nil { if err != nil {
e <- err e <- err
close(e) close(e)
close(d) close(d)
return return
} }
var body []byte var body []byte
body, _ = ioutil.ReadAll(resp.Body) body, _ = ioutil.ReadAll(resp.Body)
d <- body d <- body
close(e) close(e)
close(d) close(d)
} }
func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) { func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) {
// split string to remove file reference // split string to remove file reference
uri := strings.Split(ref, "#") uri := strings.Split(ref, "#")
// have we already seen this remote source? // have we already seen this remote source?
var parsedRemoteDocument *yaml.Node var parsedRemoteDocument *yaml.Node
alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0]) alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0])
if alreadySeen { if alreadySeen {
parsedRemoteDocument = foundDocument parsedRemoteDocument = foundDocument
} else { } else {
d := make(chan bool) d := make(chan bool)
var body []byte var body []byte
var err error var err error
go func(uri string) { go func(uri string) {
bc := make(chan []byte) bc := make(chan []byte)
ec := make(chan error) ec := make(chan error)
go getRemoteDoc(uri, bc, ec) go getRemoteDoc(uri, bc, ec)
select { select {
case v := <-bc: case v := <-bc:
body = v body = v
break break
case er := <-ec: case er := <-ec:
err = er err = er
break break
} }
var remoteDoc yaml.Node var remoteDoc yaml.Node
er := yaml.Unmarshal(body, &remoteDoc) er := yaml.Unmarshal(body, &remoteDoc)
if er != nil { if er != nil {
err = er err = er
d <- true d <- true
return return
} }
parsedRemoteDocument = &remoteDoc parsedRemoteDocument = &remoteDoc
if index.config != nil { if index.config != nil {
index.config.seenRemoteSources.Store(uri, &remoteDoc) index.config.seenRemoteSources.Store(uri, &remoteDoc)
} }
d <- true d <- true
}(uri[0]) }(uri[0])
// wait for double go fun. // wait for double go fun.
<-d <-d
if err != nil { if err != nil {
// no bueno. // no bueno.
return nil, nil, err return nil, nil, err
} }
} }
// lookup item from reference by using a path query. // lookup item from reference by using a path query.
var query string var query string
if len(uri) >= 2 { if len(uri) >= 2 {
query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", "."))
} else { } else {
query = "$" query = "$"
} }
// remove any URL encoding // remove any URL encoding
query = strings.Replace(query, "~1", "./", 1) query = strings.Replace(query, "~1", "./", 1)
query = strings.ReplaceAll(query, "~1", "/") query = strings.ReplaceAll(query, "~1", "/")
path, err := yamlpath.NewPath(query) path, err := yamlpath.NewPath(query)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
result, _ := path.Find(parsedRemoteDocument) result, _ := path.Find(parsedRemoteDocument)
if len(result) == 1 { if len(result) == 1 {
return result[0], parsedRemoteDocument, nil return result[0], parsedRemoteDocument, nil
} }
return nil, nil, nil return nil, nil, nil
} }
func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) { func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) {
// split string to remove file reference // split string to remove file reference
uri := strings.Split(ref, "#") uri := strings.Split(ref, "#")
file := strings.ReplaceAll(uri[0], "file:", "") file := strings.ReplaceAll(uri[0], "file:", "")
filePath := filepath.Dir(file) filePath := filepath.Dir(file)
fileName := filepath.Base(file) fileName := filepath.Base(file)
var parsedRemoteDocument *yaml.Node var parsedRemoteDocument *yaml.Node
if index.seenRemoteSources[file] != nil { if index.seenRemoteSources[file] != nil {
parsedRemoteDocument = index.seenRemoteSources[file] parsedRemoteDocument = index.seenRemoteSources[file]
} else { } else {
base := index.config.BasePath base := index.config.BasePath
fileToRead := filepath.Join(base, filePath, fileName) fileToRead := filepath.Join(base, filePath, fileName)
// try and read the file off the local file system, if it fails // 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. // check for a baseURL and then ask our remote lookup function to go try and get it.
body, err := os.ReadFile(fileToRead) 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 we have a baseURL, then we can try and get the file from there.
if index.config != nil && index.config.BaseURL != nil { if index.config != nil && index.config.BaseURL != nil {
u := index.config.BaseURL u := index.config.BaseURL
remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true) remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true)
a, b, e := index.lookupRemoteReference(remoteRef) a, b, e := index.lookupRemoteReference(remoteRef)
if e != nil { if e != nil {
// give up, we can't find the file, not locally, not remotely. It's toast. // give up, we can't find the file, not locally, not remotely. It's toast.
return nil, nil, e return nil, nil, e
} }
return a, b, nil return a, b, nil
} else { } else {
// no baseURL? then we can't do anything, give up. // no baseURL? then we can't do anything, give up.
return nil, nil, err return nil, nil, err
} }
} }
var remoteDoc yaml.Node var remoteDoc yaml.Node
err = yaml.Unmarshal(body, &remoteDoc) err = yaml.Unmarshal(body, &remoteDoc)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
parsedRemoteDocument = &remoteDoc parsedRemoteDocument = &remoteDoc
if index.seenLocalSources != nil { if index.seenLocalSources != nil {
index.sourceLock.Lock() index.sourceLock.Lock()
index.seenLocalSources[file] = &remoteDoc index.seenLocalSources[file] = &remoteDoc
index.sourceLock.Unlock() index.sourceLock.Unlock()
} }
} }
// lookup item from reference by using a path query. // lookup item from reference by using a path query.
var query string var query string
if len(uri) >= 2 { if len(uri) >= 2 {
query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", "."))
} else { } else {
query = "$" query = "$"
} }
// remove any URL encoding // remove any URL encoding
query = strings.Replace(query, "~1", "./", 1) query = strings.Replace(query, "~1", "./", 1)
query = strings.ReplaceAll(query, "~1", "/") query = strings.ReplaceAll(query, "~1", "/")
path, err := yamlpath.NewPath(query) path, err := yamlpath.NewPath(query)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
result, _ := path.Find(parsedRemoteDocument) result, _ := path.Find(parsedRemoteDocument)
if len(result) == 1 { if len(result) == 1 {
return result[0], parsedRemoteDocument, nil return result[0], parsedRemoteDocument, nil
} }
return nil, parsedRemoteDocument, nil return nil, parsedRemoteDocument, nil
} }
func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
if index.root != nil { if index.root != nil {
// check component for url encoding. // check component for url encoding.
if strings.Contains(componentId, "%") { if strings.Contains(componentId, "%") {
// decode the url. // decode the url.
componentId, _ = url.QueryUnescape(componentId) componentId, _ = url.QueryUnescape(componentId)
} }
name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId)
path, err := yamlpath.NewPath(friendlySearch) path, err := yamlpath.NewPath(friendlySearch)
if path == nil || err != nil { if path == nil || err != nil {
return nil // no component found return nil // no component found
} }
res, _ := path.Find(index.root) res, _ := path.Find(index.root)
if len(res) == 1 { if len(res) == 1 {
ref := &Reference{ ref := &Reference{
Definition: componentId, Definition: componentId,
Name: name, Name: name,
Node: res[0], Node: res[0],
Path: friendlySearch, Path: friendlySearch,
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}), RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}),
} }
return ref return ref
} }
} }
return nil return nil
} }
func (index *SpecIndex) performExternalLookup(uri []string, componentId string, func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
lookupFunction ExternalLookupFunction, parent *yaml.Node, lookupFunction ExternalLookupFunction, parent *yaml.Node,
) *Reference { ) *Reference {
if len(uri) > 0 { if len(uri) > 0 {
index.externalLock.RLock() index.externalLock.RLock()
externalSpecIndex := index.externalSpecIndex[uri[0]] externalSpecIndex := index.externalSpecIndex[uri[0]]
index.externalLock.RUnlock() index.externalLock.RUnlock()
if externalSpecIndex == nil { if externalSpecIndex == nil {
_, newRoot, err := lookupFunction(componentId) _, newRoot, err := lookupFunction(componentId)
if err != nil { if err != nil {
indexError := &IndexingError{ indexError := &IndexingError{
Err: err, Err: err,
Node: parent, Node: parent,
Path: componentId, Path: componentId,
} }
index.errorLock.Lock() index.errorLock.Lock()
index.refErrors = append(index.refErrors, indexError) index.refErrors = append(index.refErrors, indexError)
index.errorLock.Unlock() index.errorLock.Unlock()
return nil return nil
} }
// cool, cool, lets index this spec also. This is a recursive action and will keep going // cool, cool, lets index this spec also. This is a recursive action and will keep going
// until all remote references have been found. // until all remote references have been found.
var bp *url.URL var bp *url.URL
var bd string var bd string
if index.config.BaseURL != nil { if index.config.BaseURL != nil {
bp = index.config.BaseURL bp = index.config.BaseURL
} }
if index.config.BasePath != "" { if index.config.BasePath != "" {
bd = index.config.BasePath bd = index.config.BasePath
} }
var path, newBasePath string var path, newBasePath string
var newUrl *url.URL var newUrl *url.URL
if bp != nil { if bp != nil {
path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false) path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false)
newUrl, _ = url.Parse(path) newUrl, _ = url.Parse(path)
newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path))) newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path)))
} }
if bd != "" { if bd != "" {
newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) 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 != "" { if newUrl != nil || newBasePath != "" {
newConfig := &SpecIndexConfig{ newConfig := &SpecIndexConfig{
BaseURL: newUrl, BaseURL: newUrl,
BasePath: newBasePath, BasePath: newBasePath,
AllowRemoteLookup: index.config.AllowRemoteLookup, AllowRemoteLookup: index.config.AllowRemoteLookup,
AllowFileLookup: index.config.AllowFileLookup, AllowFileLookup: index.config.AllowFileLookup,
seenRemoteSources: index.config.seenRemoteSources, seenRemoteSources: index.config.seenRemoteSources,
remoteLock: index.config.remoteLock, 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 externalSpecIndex != nil { var newIndex *SpecIndex
foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) newIndex = NewSpecIndexWithConfig(newRoot, newConfig)
if foundRef != nil { index.refLock.Lock()
nameSegs := strings.Split(uri[1], "/") index.externalLock.Lock()
ref := &Reference{ index.externalSpecIndex[uri[0]] = newIndex
Definition: componentId, index.externalLock.Unlock()
Name: nameSegs[len(nameSegs)-1], newIndex.relativePath = path
Node: foundRef.Node, newIndex.parentIndex = index
IsRemote: true, index.AddChild(newIndex)
RemoteLocation: componentId, index.refLock.Unlock()
Path: foundRef.Path, externalSpecIndex = newIndex
} }
return ref }
}
} if externalSpecIndex != nil {
} foundRef := externalSpecIndex.FindComponentInRoot(uri[1])
return nil 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
} }

View File

@@ -4,13 +4,13 @@
package index package index
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing" "testing"
) )
func TestSpecIndex_performExternalLookup(t *testing.T) { func TestSpecIndex_performExternalLookup(t *testing.T) {
yml := `{ yml := `{
"openapi": "3.1.0", "openapi": "3.1.0",
"paths": [ "paths": [
{"/": { {"/": {
@@ -18,50 +18,50 @@ func TestSpecIndex_performExternalLookup(t *testing.T) {
}} }}
] ]
}` }`
var rootNode yaml.Node var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode) _ = yaml.Unmarshal([]byte(yml), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
assert.Len(t, index.GetPathsNode().Content, 1) assert.Len(t, index.GetPathsNode().Content, 1)
} }
func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) { func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) {
yml := `openapi: 3.1.0 yml := `openapi: 3.1.0
components: components:
schemas: schemas:
thing: thing:
properties: properties:
thong: thong:
$ref: 'httpssss://not-gonna-work.com'` $ref: 'httpssss://not-gonna-work.com'`
var rootNode yaml.Node var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode) _ = yaml.Unmarshal([]byte(yml), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
assert.Len(t, index.GetReferenceIndexErrors(), 2) assert.Len(t, index.GetReferenceIndexErrors(), 2)
} }
func TestSpecIndex_FindComponentInRoot(t *testing.T) { func TestSpecIndex_FindComponentInRoot(t *testing.T) {
yml := `openapi: 3.1.0 yml := `openapi: 3.1.0
components: components:
schemas: schemas:
thing: thing:
properties: properties:
thong: hi!` thong: hi!`
var rootNode yaml.Node var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode) _ = yaml.Unmarshal([]byte(yml), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./") thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./")
assert.Nil(t, thing) assert.Nil(t, thing)
assert.Len(t, index.GetReferenceIndexErrors(), 0) assert.Len(t, index.GetReferenceIndexErrors(), 0)
} }
func TestSpecIndex_FailLookupRemoteComponent_badPath(t *testing.T) { func TestSpecIndex_FailLookupRemoteComponent_badPath(t *testing.T) {
yml := `openapi: 3.1.0 yml := `openapi: 3.1.0
components: components:
schemas: schemas:
thing: thing:
@@ -69,19 +69,19 @@ components:
thong: thong:
$ref: 'https://pb33f.io/site.webmanifest#/....$.ok../oh#/$$_-'` $ref: 'https://pb33f.io/site.webmanifest#/....$.ok../oh#/$$_-'`
var rootNode yaml.Node var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode) _ = yaml.Unmarshal([]byte(yml), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./") thing := index.FindComponentInRoot("#/$splish/$.../slash#$///./")
assert.Nil(t, thing) assert.Nil(t, thing)
assert.Len(t, index.GetReferenceIndexErrors(), 2) assert.Len(t, index.GetReferenceIndexErrors(), 2)
} }
func TestSpecIndex_FailLookupRemoteComponent_Ok_butNotFound(t *testing.T) { func TestSpecIndex_FailLookupRemoteComponent_Ok_butNotFound(t *testing.T) {
yml := `openapi: 3.1.0 yml := `openapi: 3.1.0
components: components:
schemas: schemas:
thing: thing:
@@ -89,13 +89,40 @@ components:
thong: thong:
$ref: 'https://pb33f.io/site.webmanifest#/valid-but-missing'` $ref: 'https://pb33f.io/site.webmanifest#/valid-but-missing'`
var rootNode yaml.Node var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode) _ = yaml.Unmarshal([]byte(yml), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
thing := index.FindComponentInRoot("#/valid-but-missing") thing := index.FindComponentInRoot("#/valid-but-missing")
assert.Nil(t, thing) assert.Nil(t, thing)
assert.Len(t, index.GetReferenceIndexErrors(), 1) 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)
} }