mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-09 04:20:17 +00:00
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:
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user