Removed some dead code that does not need to exist

A consequence of the old index design, now gone

Signed-off-by: quobix <dave@quobix.com>
This commit is contained in:
quobix
2023-11-01 13:07:53 -04:00
parent 33fc552c65
commit 2bc3c67776
3 changed files with 504 additions and 527 deletions

View File

@@ -4,393 +4,392 @@
package index package index
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"log/slog" "log/slog"
"runtime" "runtime"
"golang.org/x/sync/syncmap" "golang.org/x/sync/syncmap"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io" "io"
"io/fs" "io/fs"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
) )
type RemoteFS struct { type RemoteFS struct {
indexConfig *SpecIndexConfig indexConfig *SpecIndexConfig
rootURL string rootURL string
rootURLParsed *url.URL rootURLParsed *url.URL
RemoteHandlerFunc utils.RemoteURLHandler RemoteHandlerFunc utils.RemoteURLHandler
Files syncmap.Map Files syncmap.Map
ProcessingFiles syncmap.Map ProcessingFiles syncmap.Map
FetchTime int64 FetchTime int64
FetchChannel chan *RemoteFile FetchChannel chan *RemoteFile
remoteErrors []error remoteErrors []error
logger *slog.Logger logger *slog.Logger
defaultClient *http.Client extractedFiles map[string]RolodexFile
extractedFiles map[string]RolodexFile
} }
type RemoteFile struct { type RemoteFile struct {
filename string filename string
name string name string
extension FileExtension extension FileExtension
data []byte data []byte
fullPath string fullPath string
URL *url.URL URL *url.URL
lastModified time.Time lastModified time.Time
seekingErrors []error seekingErrors []error
index *SpecIndex index *SpecIndex
parsed *yaml.Node parsed *yaml.Node
offset int64 offset int64
} }
func (f *RemoteFile) GetFileName() string { func (f *RemoteFile) GetFileName() string {
return f.filename return f.filename
} }
func (f *RemoteFile) GetContent() string { func (f *RemoteFile) GetContent() string {
return string(f.data) return string(f.data)
} }
func (f *RemoteFile) GetContentAsYAMLNode() (*yaml.Node, error) { func (f *RemoteFile) GetContentAsYAMLNode() (*yaml.Node, error) {
if f.parsed != nil { if f.parsed != nil {
return f.parsed, nil return f.parsed, nil
} }
if f.index != nil && f.index.root != nil { if f.index != nil && f.index.root != nil {
return f.index.root, nil return f.index.root, nil
} }
if f.data == nil { if f.data == nil {
return nil, fmt.Errorf("no data to parse for file: %s", f.fullPath) return nil, fmt.Errorf("no data to parse for file: %s", f.fullPath)
} }
var root yaml.Node var root yaml.Node
err := yaml.Unmarshal(f.data, &root) err := yaml.Unmarshal(f.data, &root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if f.index != nil && f.index.root == nil { if f.index != nil && f.index.root == nil {
f.index.root = &root f.index.root = &root
} }
f.parsed = &root f.parsed = &root
return &root, nil return &root, nil
} }
func (f *RemoteFile) GetFileExtension() FileExtension { func (f *RemoteFile) GetFileExtension() FileExtension {
return f.extension return f.extension
} }
func (f *RemoteFile) GetLastModified() time.Time { func (f *RemoteFile) GetLastModified() time.Time {
return f.lastModified return f.lastModified
} }
func (f *RemoteFile) GetErrors() []error { func (f *RemoteFile) GetErrors() []error {
return f.seekingErrors return f.seekingErrors
} }
func (f *RemoteFile) GetFullPath() string { func (f *RemoteFile) GetFullPath() string {
return f.fullPath return f.fullPath
} }
// fs.FileInfo interfaces // fs.FileInfo interfaces
func (f *RemoteFile) Name() string { func (f *RemoteFile) Name() string {
return f.name return f.name
} }
func (f *RemoteFile) Size() int64 { func (f *RemoteFile) Size() int64 {
return int64(len(f.data)) return int64(len(f.data))
} }
func (f *RemoteFile) Mode() fs.FileMode { func (f *RemoteFile) Mode() fs.FileMode {
return fs.FileMode(0) return fs.FileMode(0)
} }
func (f *RemoteFile) ModTime() time.Time { func (f *RemoteFile) ModTime() time.Time {
return f.lastModified return f.lastModified
} }
func (f *RemoteFile) IsDir() bool { func (f *RemoteFile) IsDir() bool {
return false return false
} }
// fs.File interfaces // fs.File interfaces
func (f *RemoteFile) Sys() interface{} { func (f *RemoteFile) Sys() interface{} {
return nil return nil
} }
func (f *RemoteFile) Close() error { func (f *RemoteFile) Close() error {
return nil return nil
} }
func (f *RemoteFile) Stat() (fs.FileInfo, error) { func (f *RemoteFile) Stat() (fs.FileInfo, error) {
return f, nil return f, nil
} }
func (f *RemoteFile) Read(b []byte) (int, error) { func (f *RemoteFile) Read(b []byte) (int, error) {
if f.offset >= int64(len(f.data)) { if f.offset >= int64(len(f.data)) {
return 0, io.EOF return 0, io.EOF
} }
if f.offset < 0 { if f.offset < 0 {
return 0, &fs.PathError{Op: "read", Path: f.name, Err: fs.ErrInvalid} return 0, &fs.PathError{Op: "read", Path: f.name, Err: fs.ErrInvalid}
} }
n := copy(b, f.data[f.offset:]) n := copy(b, f.data[f.offset:])
f.offset += int64(n) f.offset += int64(n)
return n, nil return n, nil
} }
func (f *RemoteFile) Index(config *SpecIndexConfig) (*SpecIndex, error) { func (f *RemoteFile) Index(config *SpecIndexConfig) (*SpecIndex, error) {
if f.index != nil { if f.index != nil {
return f.index, nil return f.index, nil
} }
content := f.data content := f.data
// first, we must parse the content of the file // first, we must parse the content of the file
info, err := datamodel.ExtractSpecInfoWithDocumentCheck(content, true) info, err := datamodel.ExtractSpecInfoWithDocumentCheck(content, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
index := NewSpecIndexWithConfig(info.RootNode, config) index := NewSpecIndexWithConfig(info.RootNode, config)
index.specAbsolutePath = config.SpecAbsolutePath index.specAbsolutePath = config.SpecAbsolutePath
f.index = index f.index = index
return index, nil return index, nil
} }
func (f *RemoteFile) GetIndex() *SpecIndex { func (f *RemoteFile) GetIndex() *SpecIndex {
return f.index return f.index
} }
type FileExtension int type FileExtension int
const ( const (
YAML FileExtension = iota YAML FileExtension = iota
JSON JSON
UNSUPPORTED UNSUPPORTED
) )
func NewRemoteFSWithConfig(specIndexConfig *SpecIndexConfig) (*RemoteFS, error) { func NewRemoteFSWithConfig(specIndexConfig *SpecIndexConfig) (*RemoteFS, error) {
if specIndexConfig == nil { if specIndexConfig == nil {
return nil, errors.New("no spec index config provided") return nil, errors.New("no spec index config provided")
} }
remoteRootURL := specIndexConfig.BaseURL remoteRootURL := specIndexConfig.BaseURL
log := specIndexConfig.Logger log := specIndexConfig.Logger
if log == nil { if log == nil {
log = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ log = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelError, Level: slog.LevelError,
})) }))
} }
rfs := &RemoteFS{ rfs := &RemoteFS{
indexConfig: specIndexConfig, indexConfig: specIndexConfig,
logger: log, logger: log,
rootURLParsed: remoteRootURL, rootURLParsed: remoteRootURL,
FetchChannel: make(chan *RemoteFile), FetchChannel: make(chan *RemoteFile),
} }
if remoteRootURL != nil { if remoteRootURL != nil {
rfs.rootURL = remoteRootURL.String() rfs.rootURL = remoteRootURL.String()
} }
if specIndexConfig.RemoteURLHandler != nil { if specIndexConfig.RemoteURLHandler != nil {
rfs.RemoteHandlerFunc = specIndexConfig.RemoteURLHandler rfs.RemoteHandlerFunc = specIndexConfig.RemoteURLHandler
} else { } else {
// default http client // default http client
client := &http.Client{ client := &http.Client{
Timeout: time.Second * 120, Timeout: time.Second * 120,
} }
rfs.RemoteHandlerFunc = func(url string) (*http.Response, error) { rfs.RemoteHandlerFunc = func(url string) (*http.Response, error) {
return client.Get(url) return client.Get(url)
} }
} }
return rfs, nil return rfs, nil
} }
func NewRemoteFSWithRootURL(rootURL string) (*RemoteFS, error) { func NewRemoteFSWithRootURL(rootURL string) (*RemoteFS, error) {
remoteRootURL, err := url.Parse(rootURL) remoteRootURL, err := url.Parse(rootURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
config := CreateOpenAPIIndexConfig() config := CreateOpenAPIIndexConfig()
config.BaseURL = remoteRootURL config.BaseURL = remoteRootURL
return NewRemoteFSWithConfig(config) return NewRemoteFSWithConfig(config)
} }
func (i *RemoteFS) SetRemoteHandlerFunc(handlerFunc utils.RemoteURLHandler) { func (i *RemoteFS) SetRemoteHandlerFunc(handlerFunc utils.RemoteURLHandler) {
i.RemoteHandlerFunc = handlerFunc i.RemoteHandlerFunc = handlerFunc
} }
func (i *RemoteFS) SetIndexConfig(config *SpecIndexConfig) { func (i *RemoteFS) SetIndexConfig(config *SpecIndexConfig) {
i.indexConfig = config i.indexConfig = config
} }
func (i *RemoteFS) GetFiles() map[string]RolodexFile { func (i *RemoteFS) GetFiles() map[string]RolodexFile {
files := make(map[string]RolodexFile) files := make(map[string]RolodexFile)
i.Files.Range(func(key, value interface{}) bool { i.Files.Range(func(key, value interface{}) bool {
files[key.(string)] = value.(*RemoteFile) files[key.(string)] = value.(*RemoteFile)
return true return true
}) })
i.extractedFiles = files i.extractedFiles = files
return files return files
} }
func (i *RemoteFS) GetErrors() []error { func (i *RemoteFS) GetErrors() []error {
return i.remoteErrors return i.remoteErrors
} }
func (i *RemoteFS) Open(remoteURL string) (fs.File, error) { func (i *RemoteFS) Open(remoteURL string) (fs.File, error) {
if i.indexConfig != nil && !i.indexConfig.AllowRemoteLookup { if i.indexConfig != nil && !i.indexConfig.AllowRemoteLookup {
return nil, fmt.Errorf("remote lookup for '%s' is not allowed, please set "+ return nil, fmt.Errorf("remote lookup for '%s' is not allowed, please set "+
"AllowRemoteLookup to true as part of the index configuration", remoteURL) "AllowRemoteLookup to true as part of the index configuration", remoteURL)
} }
remoteParsedURL, err := url.Parse(remoteURL) remoteParsedURL, err := url.Parse(remoteURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
remoteParsedURLOriginal, _ := url.Parse(remoteURL) remoteParsedURLOriginal, _ := url.Parse(remoteURL)
// try path first // try path first
if r, ok := i.Files.Load(remoteParsedURL.Path); ok { if r, ok := i.Files.Load(remoteParsedURL.Path); ok {
return r.(*RemoteFile), nil return r.(*RemoteFile), nil
} }
// if we're processing, we need to block and wait for the file to be processed // if we're processing, we need to block and wait for the file to be processed
// try path first // try path first
if _, ok := i.ProcessingFiles.Load(remoteParsedURL.Path); ok { if _, ok := i.ProcessingFiles.Load(remoteParsedURL.Path); ok {
// we can't block if we only have a couple of CPUs, as we'll deadlock / run super slow, only when we're running in parallel // we can't block if we only have a couple of CPUs, as we'll deadlock / run super slow, only when we're running in parallel
// can we block threads. // can we block threads.
if runtime.GOMAXPROCS(-1) > 2 { if runtime.GOMAXPROCS(-1) > 2 {
i.logger.Debug("waiting for existing fetch to complete", "file", remoteURL, "remoteURL", remoteParsedURL.String()) i.logger.Debug("waiting for existing fetch to complete", "file", remoteURL, "remoteURL", remoteParsedURL.String())
f := make(chan *RemoteFile) f := make(chan *RemoteFile)
fwait := func(path string, c chan *RemoteFile) { fwait := func(path string, c chan *RemoteFile) {
for { for {
if wf, ko := i.Files.Load(remoteParsedURL.Path); ko { if wf, ko := i.Files.Load(remoteParsedURL.Path); ko {
c <- wf.(*RemoteFile) c <- wf.(*RemoteFile)
} }
} }
} }
go fwait(remoteParsedURL.Path, f) go fwait(remoteParsedURL.Path, f)
return <-f, nil return <-f, nil
} }
} }
// add to processing // add to processing
i.ProcessingFiles.Store(remoteParsedURL.Path, true) i.ProcessingFiles.Store(remoteParsedURL.Path, true)
fileExt := ExtractFileType(remoteParsedURL.Path) fileExt := ExtractFileType(remoteParsedURL.Path)
if fileExt == UNSUPPORTED { if fileExt == UNSUPPORTED {
return nil, &fs.PathError{Op: "open", Path: remoteURL, Err: fs.ErrInvalid} return nil, &fs.PathError{Op: "open", Path: remoteURL, Err: fs.ErrInvalid}
} }
// if the remote URL is absolute (http:// or https://), and we have a rootURL defined, we need to override // if the remote URL is absolute (http:// or https://), and we have a rootURL defined, we need to override
// the host being defined by this URL, and use the rootURL instead, but keep the path. // the host being defined by this URL, and use the rootURL instead, but keep the path.
if i.rootURLParsed != nil { if i.rootURLParsed != nil {
remoteParsedURL.Host = i.rootURLParsed.Host remoteParsedURL.Host = i.rootURLParsed.Host
remoteParsedURL.Scheme = i.rootURLParsed.Scheme remoteParsedURL.Scheme = i.rootURLParsed.Scheme
if !filepath.IsAbs(remoteParsedURL.Path) { if !filepath.IsAbs(remoteParsedURL.Path) {
remoteParsedURL.Path = filepath.Join(i.rootURLParsed.Path, remoteParsedURL.Path) remoteParsedURL.Path = filepath.Join(i.rootURLParsed.Path, remoteParsedURL.Path)
} }
} }
i.logger.Debug("loading remote file", "file", remoteURL, "remoteURL", remoteParsedURL.String()) i.logger.Debug("loading remote file", "file", remoteURL, "remoteURL", remoteParsedURL.String())
response, clientErr := i.RemoteHandlerFunc(remoteParsedURL.String()) response, clientErr := i.RemoteHandlerFunc(remoteParsedURL.String())
if clientErr != nil { if clientErr != nil {
i.remoteErrors = append(i.remoteErrors, clientErr) i.remoteErrors = append(i.remoteErrors, clientErr)
// remove from processing // remove from processing
i.ProcessingFiles.Delete(remoteParsedURL.Path) i.ProcessingFiles.Delete(remoteParsedURL.Path)
if response != nil { if response != nil {
i.logger.Error("client error", "error", clientErr, "status", response.StatusCode) i.logger.Error("client error", "error", clientErr, "status", response.StatusCode)
} else { } else {
i.logger.Error("client error", "error", clientErr.Error()) i.logger.Error("client error", "error", clientErr.Error())
} }
return nil, clientErr return nil, clientErr
} }
if response == nil { if response == nil {
return nil, fmt.Errorf("empty response from remote URL: %s", remoteParsedURL.String()) return nil, fmt.Errorf("empty response from remote URL: %s", remoteParsedURL.String())
} }
responseBytes, readError := io.ReadAll(response.Body) responseBytes, readError := io.ReadAll(response.Body)
if readError != nil { if readError != nil {
// remove from processing // remove from processing
i.ProcessingFiles.Delete(remoteParsedURL.Path) i.ProcessingFiles.Delete(remoteParsedURL.Path)
return nil, fmt.Errorf("error reading bytes from remote file '%s': [%s]", return nil, fmt.Errorf("error reading bytes from remote file '%s': [%s]",
remoteParsedURL.String(), readError.Error()) remoteParsedURL.String(), readError.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
// remove from processing // remove from processing
i.ProcessingFiles.Delete(remoteParsedURL.Path) i.ProcessingFiles.Delete(remoteParsedURL.Path)
i.logger.Error("unable to fetch remote document", i.logger.Error("unable to fetch remote document",
"file", remoteParsedURL.Path, "status", response.StatusCode, "resp", string(responseBytes)) "file", remoteParsedURL.Path, "status", response.StatusCode, "resp", string(responseBytes))
return nil, fmt.Errorf("unable to fetch remote document: %s", string(responseBytes)) return nil, fmt.Errorf("unable to fetch remote document: %s", string(responseBytes))
} }
absolutePath, _ := filepath.Abs(remoteParsedURL.Path) absolutePath, _ := filepath.Abs(remoteParsedURL.Path)
// extract last modified from response // extract last modified from response
lastModified := response.Header.Get("Last-Modified") lastModified := response.Header.Get("Last-Modified")
// parse the last modified date into a time object // parse the last modified date into a time object
lastModifiedTime, parseErr := time.Parse(time.RFC1123, lastModified) lastModifiedTime, parseErr := time.Parse(time.RFC1123, lastModified)
if parseErr != nil { if parseErr != nil {
// can't extract last modified, so use now // can't extract last modified, so use now
lastModifiedTime = time.Now() lastModifiedTime = time.Now()
} }
filename := filepath.Base(remoteParsedURL.Path) filename := filepath.Base(remoteParsedURL.Path)
remoteFile := &RemoteFile{ remoteFile := &RemoteFile{
filename: filename, filename: filename,
name: remoteParsedURL.Path, name: remoteParsedURL.Path,
extension: fileExt, extension: fileExt,
data: responseBytes, data: responseBytes,
fullPath: absolutePath, fullPath: absolutePath,
URL: remoteParsedURL, URL: remoteParsedURL,
lastModified: lastModifiedTime, lastModified: lastModifiedTime,
} }
copiedCfg := *i.indexConfig copiedCfg := *i.indexConfig
newBase := fmt.Sprintf("%s://%s%s", remoteParsedURLOriginal.Scheme, remoteParsedURLOriginal.Host, newBase := fmt.Sprintf("%s://%s%s", remoteParsedURLOriginal.Scheme, remoteParsedURLOriginal.Host,
filepath.Dir(remoteParsedURL.Path)) filepath.Dir(remoteParsedURL.Path))
newBaseURL, _ := url.Parse(newBase) newBaseURL, _ := url.Parse(newBase)
if newBaseURL != nil { if newBaseURL != nil {
copiedCfg.BaseURL = newBaseURL copiedCfg.BaseURL = newBaseURL
} }
copiedCfg.SpecAbsolutePath = remoteParsedURL.String() copiedCfg.SpecAbsolutePath = remoteParsedURL.String()
if len(remoteFile.data) > 0 { if len(remoteFile.data) > 0 {
i.logger.Debug("successfully loaded file", "file", absolutePath) i.logger.Debug("successfully loaded file", "file", absolutePath)
} }
//i.seekRelatives(remoteFile) //i.seekRelatives(remoteFile)
// remove from processing // remove from processing
i.ProcessingFiles.Delete(remoteParsedURL.Path) i.ProcessingFiles.Delete(remoteParsedURL.Path)
i.Files.Store(absolutePath, remoteFile) i.Files.Store(absolutePath, remoteFile)
idx, idxError := remoteFile.Index(&copiedCfg) idx, idxError := remoteFile.Index(&copiedCfg)
if idxError != nil && idx == nil { if idxError != nil && idx == nil {
i.remoteErrors = append(i.remoteErrors, idxError) i.remoteErrors = append(i.remoteErrors, idxError)
} else { } else {
// for each index, we need a resolver // for each index, we need a resolver
resolver := NewResolver(idx) resolver := NewResolver(idx)
idx.resolver = resolver idx.resolver = resolver
idx.BuildIndex() idx.BuildIndex()
} }
return remoteFile, errors.Join(i.remoteErrors...) return remoteFile, errors.Join(i.remoteErrors...)
} }

View File

@@ -4,383 +4,382 @@
package index package index
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"testing" "testing"
"time" "time"
) )
var test_httpClient = &http.Client{Timeout: time.Duration(60) * time.Second} var test_httpClient = &http.Client{Timeout: time.Duration(60) * time.Second}
func test_buildServer() *httptest.Server { func test_buildServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.URL.String() == "/file1.yaml" { if req.URL.String() == "/file1.yaml" {
rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT") rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
_, _ = rw.Write([]byte(`"$ref": "./deeper/file2.yaml#/components/schemas/Pet"`)) _, _ = rw.Write([]byte(`"$ref": "./deeper/file2.yaml#/components/schemas/Pet"`))
return return
} }
if req.URL.String() == "/deeper/file2.yaml" { if req.URL.String() == "/deeper/file2.yaml" {
rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 08:28:00 GMT") rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 08:28:00 GMT")
_, _ = rw.Write([]byte(`"$ref": "/deeper/even_deeper/file3.yaml#/components/schemas/Pet"`)) _, _ = rw.Write([]byte(`"$ref": "/deeper/even_deeper/file3.yaml#/components/schemas/Pet"`))
return return
} }
if req.URL.String() == "/deeper/even_deeper/file3.yaml" { if req.URL.String() == "/deeper/even_deeper/file3.yaml" {
rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 10:28:00 GMT") rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 10:28:00 GMT")
_, _ = rw.Write([]byte(`"$ref": "../file2.yaml#/components/schemas/Pet"`)) _, _ = rw.Write([]byte(`"$ref": "../file2.yaml#/components/schemas/Pet"`))
return return
} }
rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 12:28:00 GMT") rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 12:28:00 GMT")
if req.URL.String() == "/deeper/list.yaml" { if req.URL.String() == "/deeper/list.yaml" {
_, _ = rw.Write([]byte(`"$ref": "../file2.yaml"`)) _, _ = rw.Write([]byte(`"$ref": "../file2.yaml"`))
return return
} }
if req.URL.String() == "/bag/list.yaml" { if req.URL.String() == "/bag/list.yaml" {
_, _ = rw.Write([]byte(`"$ref": "pocket/list.yaml"\n\n"$ref": "zip/things.yaml"`)) _, _ = rw.Write([]byte(`"$ref": "pocket/list.yaml"\n\n"$ref": "zip/things.yaml"`))
return return
} }
if req.URL.String() == "/bag/pocket/list.yaml" { if req.URL.String() == "/bag/pocket/list.yaml" {
_, _ = rw.Write([]byte(`"$ref": "../list.yaml"\n\n"$ref": "../../file2.yaml"`)) _, _ = rw.Write([]byte(`"$ref": "../list.yaml"\n\n"$ref": "../../file2.yaml"`))
return return
} }
if req.URL.String() == "/bag/pocket/things.yaml" { if req.URL.String() == "/bag/pocket/things.yaml" {
_, _ = rw.Write([]byte(`"$ref": "list.yaml"`)) _, _ = rw.Write([]byte(`"$ref": "list.yaml"`))
return return
} }
if req.URL.String() == "/bag/zip/things.yaml" { if req.URL.String() == "/bag/zip/things.yaml" {
_, _ = rw.Write([]byte(`"$ref": "list.yaml"`)) _, _ = rw.Write([]byte(`"$ref": "list.yaml"`))
return return
} }
if req.URL.String() == "/bag/zip/list.yaml" { if req.URL.String() == "/bag/zip/list.yaml" {
_, _ = rw.Write([]byte(`"$ref": "../list.yaml"\n\n"$ref": "../../file1.yaml"\n\n"$ref": "more.yaml""`)) _, _ = rw.Write([]byte(`"$ref": "../list.yaml"\n\n"$ref": "../../file1.yaml"\n\n"$ref": "more.yaml""`))
return return
} }
if req.URL.String() == "/bag/zip/more.yaml" { if req.URL.String() == "/bag/zip/more.yaml" {
_, _ = rw.Write([]byte(`"$ref": "../../deeper/list.yaml"\n\n"$ref": "../../bad.yaml"`)) _, _ = rw.Write([]byte(`"$ref": "../../deeper/list.yaml"\n\n"$ref": "../../bad.yaml"`))
return return
} }
if req.URL.String() == "/bad.yaml" { if req.URL.String() == "/bad.yaml" {
rw.WriteHeader(http.StatusInternalServerError) rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write([]byte(`"error, cannot do the thing"`)) _, _ = rw.Write([]byte(`"error, cannot do the thing"`))
return return
} }
_, _ = rw.Write([]byte(`OK`)) _, _ = rw.Write([]byte(`OK`))
})) }))
} }
func TestNewRemoteFS_BasicCheck(t *testing.T) { func TestNewRemoteFS_BasicCheck(t *testing.T) {
server := test_buildServer() server := test_buildServer()
defer server.Close() defer server.Close()
//remoteFS := NewRemoteFS("https://raw.githubusercontent.com/digitalocean/openapi/main/specification/") //remoteFS := NewRemoteFS("https://raw.githubusercontent.com/digitalocean/openapi/main/specification/")
remoteFS, _ := NewRemoteFSWithRootURL(server.URL) remoteFS, _ := NewRemoteFSWithRootURL(server.URL)
remoteFS.RemoteHandlerFunc = test_httpClient.Get remoteFS.RemoteHandlerFunc = test_httpClient.Get
file, err := remoteFS.Open("/file1.yaml") file, err := remoteFS.Open("/file1.yaml")
assert.NoError(t, err) assert.NoError(t, err)
bytes, rErr := io.ReadAll(file) bytes, rErr := io.ReadAll(file)
assert.NoError(t, rErr) assert.NoError(t, rErr)
stat, _ := file.Stat() stat, _ := file.Stat()
assert.Equal(t, "/file1.yaml", stat.Name()) assert.Equal(t, "/file1.yaml", stat.Name())
assert.Equal(t, int64(53), stat.Size()) assert.Equal(t, int64(53), stat.Size())
assert.Len(t, bytes, 53) assert.Len(t, bytes, 53)
lastMod := stat.ModTime() lastMod := stat.ModTime()
assert.Equal(t, "2015-10-21 07:28:00 +0000 GMT", lastMod.String()) assert.Equal(t, "2015-10-21 07:28:00 +0000 GMT", lastMod.String())
} }
func TestNewRemoteFS_BasicCheck_Relative(t *testing.T) { func TestNewRemoteFS_BasicCheck_Relative(t *testing.T) {
server := test_buildServer() server := test_buildServer()
defer server.Close() defer server.Close()
remoteFS, _ := NewRemoteFSWithRootURL(server.URL) remoteFS, _ := NewRemoteFSWithRootURL(server.URL)
remoteFS.RemoteHandlerFunc = test_httpClient.Get remoteFS.RemoteHandlerFunc = test_httpClient.Get
file, err := remoteFS.Open("/deeper/file2.yaml") file, err := remoteFS.Open("/deeper/file2.yaml")
assert.NoError(t, err) assert.NoError(t, err)
bytes, rErr := io.ReadAll(file) bytes, rErr := io.ReadAll(file)
assert.NoError(t, rErr) assert.NoError(t, rErr)
assert.Len(t, bytes, 64) assert.Len(t, bytes, 64)
stat, _ := file.Stat() stat, _ := file.Stat()
assert.Equal(t, "/deeper/file2.yaml", stat.Name()) assert.Equal(t, "/deeper/file2.yaml", stat.Name())
assert.Equal(t, int64(64), stat.Size()) assert.Equal(t, int64(64), stat.Size())
lastMod := stat.ModTime() lastMod := stat.ModTime()
assert.Equal(t, "2015-10-21 08:28:00 +0000 GMT", lastMod.String()) assert.Equal(t, "2015-10-21 08:28:00 +0000 GMT", lastMod.String())
} }
func TestNewRemoteFS_BasicCheck_Relative_Deeper(t *testing.T) { func TestNewRemoteFS_BasicCheck_Relative_Deeper(t *testing.T) {
server := test_buildServer() server := test_buildServer()
defer server.Close() defer server.Close()
cf := CreateOpenAPIIndexConfig() cf := CreateOpenAPIIndexConfig()
u, _ := url.Parse(server.URL) u, _ := url.Parse(server.URL)
cf.BaseURL = u cf.BaseURL = u
remoteFS, _ := NewRemoteFSWithConfig(cf) remoteFS, _ := NewRemoteFSWithConfig(cf)
remoteFS.RemoteHandlerFunc = test_httpClient.Get remoteFS.RemoteHandlerFunc = test_httpClient.Get
file, err := remoteFS.Open("/deeper/even_deeper/file3.yaml") file, err := remoteFS.Open("/deeper/even_deeper/file3.yaml")
assert.NoError(t, err) assert.NoError(t, err)
bytes, rErr := io.ReadAll(file) bytes, rErr := io.ReadAll(file)
assert.NoError(t, rErr) assert.NoError(t, rErr)
assert.Len(t, bytes, 47) assert.Len(t, bytes, 47)
stat, _ := file.Stat() stat, _ := file.Stat()
assert.Equal(t, "/deeper/even_deeper/file3.yaml", stat.Name()) assert.Equal(t, "/deeper/even_deeper/file3.yaml", stat.Name())
assert.Equal(t, int64(47), stat.Size()) assert.Equal(t, int64(47), stat.Size())
assert.Equal(t, "/deeper/even_deeper/file3.yaml", file.(*RemoteFile).Name()) assert.Equal(t, "/deeper/even_deeper/file3.yaml", file.(*RemoteFile).Name())
assert.Equal(t, "file3.yaml", file.(*RemoteFile).GetFileName()) assert.Equal(t, "file3.yaml", file.(*RemoteFile).GetFileName())
assert.Len(t, file.(*RemoteFile).GetContent(), 47) assert.Len(t, file.(*RemoteFile).GetContent(), 47)
assert.Equal(t, YAML, file.(*RemoteFile).GetFileExtension()) assert.Equal(t, YAML, file.(*RemoteFile).GetFileExtension())
assert.NotNil(t, file.(*RemoteFile).GetLastModified()) assert.NotNil(t, file.(*RemoteFile).GetLastModified())
assert.Len(t, file.(*RemoteFile).GetErrors(), 0) assert.Len(t, file.(*RemoteFile).GetErrors(), 0)
assert.Equal(t, "/deeper/even_deeper/file3.yaml", file.(*RemoteFile).GetFullPath()) assert.Equal(t, "/deeper/even_deeper/file3.yaml", file.(*RemoteFile).GetFullPath())
assert.False(t, file.(*RemoteFile).IsDir()) assert.False(t, file.(*RemoteFile).IsDir())
assert.Nil(t, file.(*RemoteFile).Sys()) assert.Nil(t, file.(*RemoteFile).Sys())
assert.Nil(t, file.(*RemoteFile).Close()) assert.Nil(t, file.(*RemoteFile).Close())
lastMod := stat.ModTime() lastMod := stat.ModTime()
assert.Equal(t, "2015-10-21 10:28:00 +0000 GMT", lastMod.String()) assert.Equal(t, "2015-10-21 10:28:00 +0000 GMT", lastMod.String())
} }
func TestRemoteFile_NoContent(t *testing.T) { func TestRemoteFile_NoContent(t *testing.T) {
rf := &RemoteFile{} rf := &RemoteFile{}
x, y := rf.GetContentAsYAMLNode() x, y := rf.GetContentAsYAMLNode()
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestRemoteFile_BadContent(t *testing.T) { func TestRemoteFile_BadContent(t *testing.T) {
rf := &RemoteFile{data: []byte("bad: data: on: a single: line: makes: for: unhappy: yaml"), index: &SpecIndex{}} rf := &RemoteFile{data: []byte("bad: data: on: a single: line: makes: for: unhappy: yaml"), index: &SpecIndex{}}
x, y := rf.GetContentAsYAMLNode() x, y := rf.GetContentAsYAMLNode()
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestRemoteFile_GoodContent(t *testing.T) { func TestRemoteFile_GoodContent(t *testing.T) {
rf := &RemoteFile{data: []byte("good: data"), index: &SpecIndex{}} rf := &RemoteFile{data: []byte("good: data"), index: &SpecIndex{}}
x, y := rf.GetContentAsYAMLNode() x, y := rf.GetContentAsYAMLNode()
assert.NotNil(t, x) assert.NotNil(t, x)
assert.NoError(t, y) assert.NoError(t, y)
assert.NotNil(t, rf.index.root) assert.NotNil(t, rf.index.root)
// bad read // bad read
rf.offset = -1 rf.offset = -1
d, err := io.ReadAll(rf) d, err := io.ReadAll(rf)
assert.Empty(t, d) assert.Empty(t, d)
assert.Error(t, err) assert.Error(t, err)
} }
func TestRemoteFile_Index_AlreadySet(t *testing.T) { func TestRemoteFile_Index_AlreadySet(t *testing.T) {
rf := &RemoteFile{data: []byte("good: data"), index: &SpecIndex{}} rf := &RemoteFile{data: []byte("good: data"), index: &SpecIndex{}}
x, y := rf.Index(&SpecIndexConfig{}) x, y := rf.Index(&SpecIndexConfig{})
assert.NotNil(t, x) assert.NotNil(t, x)
assert.NoError(t, y) assert.NoError(t, y)
} }
func TestRemoteFile_Index_BadContent(t *testing.T) { func TestRemoteFile_Index_BadContent(t *testing.T) {
rf := &RemoteFile{data: []byte("no: sleep: until: the bugs: weep")} rf := &RemoteFile{data: []byte("no: sleep: until: the bugs: weep")}
x, y := rf.Index(&SpecIndexConfig{}) x, y := rf.Index(&SpecIndexConfig{})
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestRemoteFS_NoConfig(t *testing.T) { func TestRemoteFS_NoConfig(t *testing.T) {
x, y := NewRemoteFSWithConfig(nil) x, y := NewRemoteFSWithConfig(nil)
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestRemoteFS_SetRemoteHandler(t *testing.T) { func TestRemoteFS_SetRemoteHandler(t *testing.T) {
h := func(url string) (*http.Response, error) { h := func(url string) (*http.Response, error) {
return nil, errors.New("nope") return nil, errors.New("nope")
} }
cf := CreateClosedAPIIndexConfig() cf := CreateClosedAPIIndexConfig()
cf.RemoteURLHandler = h cf.RemoteURLHandler = h
x, y := NewRemoteFSWithConfig(cf) x, y := NewRemoteFSWithConfig(cf)
assert.NotNil(t, x) assert.NotNil(t, x)
assert.NoError(t, y) assert.NoError(t, y)
assert.NotNil(t, x.RemoteHandlerFunc) assert.NotNil(t, x.RemoteHandlerFunc)
cf = CreateClosedAPIIndexConfig() assert.NotNil(t, x.RemoteHandlerFunc)
assert.NotNil(t, x.RemoteHandlerFunc)
x.SetRemoteHandlerFunc(h) x.SetRemoteHandlerFunc(h)
assert.NotNil(t, x.RemoteHandlerFunc) assert.NotNil(t, x.RemoteHandlerFunc)
// run the handler // run the handler
i, n := x.RemoteHandlerFunc("http://www.google.com") i, n := x.RemoteHandlerFunc("http://www.google.com")
assert.Nil(t, i) assert.Nil(t, i)
assert.Error(t, n) assert.Error(t, n)
assert.Equal(t, "nope", n.Error()) assert.Equal(t, "nope", n.Error())
} }
func TestRemoteFS_NoConfigBadURL(t *testing.T) { func TestRemoteFS_NoConfigBadURL(t *testing.T) {
x, y := NewRemoteFSWithRootURL("I am not a URL. I am a potato.: no.... // no.") x, y := NewRemoteFSWithRootURL("I am not a URL. I am a potato.: no.... // no.")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestNewRemoteFS_Open_NoConfig(t *testing.T) { func TestNewRemoteFS_Open_NoConfig(t *testing.T) {
rfs := &RemoteFS{} rfs := &RemoteFS{}
x, y := rfs.Open("https://pb33f.io") x, y := rfs.Open("https://pb33f.io")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestNewRemoteFS_Open_ConfigNotAllowed(t *testing.T) { func TestNewRemoteFS_Open_ConfigNotAllowed(t *testing.T) {
rfs := &RemoteFS{indexConfig: CreateClosedAPIIndexConfig()} rfs := &RemoteFS{indexConfig: CreateClosedAPIIndexConfig()}
x, y := rfs.Open("https://pb33f.io") x, y := rfs.Open("https://pb33f.io")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestNewRemoteFS_Open_BadURL(t *testing.T) { func TestNewRemoteFS_Open_BadURL(t *testing.T) {
rfs := &RemoteFS{indexConfig: CreateOpenAPIIndexConfig()} rfs := &RemoteFS{indexConfig: CreateOpenAPIIndexConfig()}
x, y := rfs.Open("I am not a URL. I am a box of candy.. yum yum yum:: in my tum tum tum") x, y := rfs.Open("I am not a URL. I am a box of candy.. yum yum yum:: in my tum tum tum")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
} }
func TestNewRemoteFS_RemoteBaseURL_RelativeRequest(t *testing.T) { func TestNewRemoteFS_RemoteBaseURL_RelativeRequest(t *testing.T) {
cf := CreateOpenAPIIndexConfig() cf := CreateOpenAPIIndexConfig()
h := func(url string) (*http.Response, error) { h := func(url string) (*http.Response, error) {
return nil, fmt.Errorf("nope, not having it %s", url) return nil, fmt.Errorf("nope, not having it %s", url)
} }
cf.RemoteURLHandler = h cf.RemoteURLHandler = h
cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine") cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine")
rfs, _ := NewRemoteFSWithConfig(cf) rfs, _ := NewRemoteFSWithConfig(cf)
x, y := rfs.Open("gib/gab/jib/jab.yaml") x, y := rfs.Open("gib/gab/jib/jab.yaml")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
assert.Equal(t, "nope, not having it https://pb33f.io/the/love/machine/gib/gab/jib/jab.yaml", y.Error()) assert.Equal(t, "nope, not having it https://pb33f.io/the/love/machine/gib/gab/jib/jab.yaml", y.Error())
} }
func TestNewRemoteFS_RemoteBaseURL_BadRequestButContainsBody(t *testing.T) { func TestNewRemoteFS_RemoteBaseURL_BadRequestButContainsBody(t *testing.T) {
cf := CreateOpenAPIIndexConfig() cf := CreateOpenAPIIndexConfig()
h := func(url string) (*http.Response, error) { h := func(url string) (*http.Response, error) {
return &http.Response{}, fmt.Errorf("it's bad, but who cares %s", url) return &http.Response{}, fmt.Errorf("it's bad, but who cares %s", url)
} }
cf.RemoteURLHandler = h cf.RemoteURLHandler = h
cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine") cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine")
rfs, _ := NewRemoteFSWithConfig(cf) rfs, _ := NewRemoteFSWithConfig(cf)
x, y := rfs.Open("/woof.yaml") x, y := rfs.Open("/woof.yaml")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
assert.Equal(t, "it's bad, but who cares https://pb33f.io/woof.yaml", y.Error()) assert.Equal(t, "it's bad, but who cares https://pb33f.io/woof.yaml", y.Error())
} }
func TestNewRemoteFS_RemoteBaseURL_NoErrorNoResponse(t *testing.T) { func TestNewRemoteFS_RemoteBaseURL_NoErrorNoResponse(t *testing.T) {
cf := CreateOpenAPIIndexConfig() cf := CreateOpenAPIIndexConfig()
h := func(url string) (*http.Response, error) { h := func(url string) (*http.Response, error) {
return nil, nil // useless! return nil, nil // useless!
} }
cf.RemoteURLHandler = h cf.RemoteURLHandler = h
cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine") cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine")
rfs, _ := NewRemoteFSWithConfig(cf) rfs, _ := NewRemoteFSWithConfig(cf)
x, y := rfs.Open("/woof.yaml") x, y := rfs.Open("/woof.yaml")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
assert.Equal(t, "empty response from remote URL: https://pb33f.io/woof.yaml", y.Error()) assert.Equal(t, "empty response from remote URL: https://pb33f.io/woof.yaml", y.Error())
} }
func TestNewRemoteFS_RemoteBaseURL_ReadBodyFail(t *testing.T) { func TestNewRemoteFS_RemoteBaseURL_ReadBodyFail(t *testing.T) {
cf := CreateOpenAPIIndexConfig() cf := CreateOpenAPIIndexConfig()
h := func(url string) (*http.Response, error) { h := func(url string) (*http.Response, error) {
r := &http.Response{} r := &http.Response{}
r.Body = &LocalFile{offset: -1} // read will fail. r.Body = &LocalFile{offset: -1} // read will fail.
return r, nil return r, nil
} }
cf.RemoteURLHandler = h cf.RemoteURLHandler = h
cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine") cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine")
rfs, _ := NewRemoteFSWithConfig(cf) rfs, _ := NewRemoteFSWithConfig(cf)
x, y := rfs.Open("/woof.yaml") x, y := rfs.Open("/woof.yaml")
assert.Nil(t, x) assert.Nil(t, x)
assert.Error(t, y) assert.Error(t, y)
assert.Equal(t, "error reading bytes from remote file 'https://pb33f.io/woof.yaml': "+ assert.Equal(t, "error reading bytes from remote file 'https://pb33f.io/woof.yaml': "+
"[read : invalid argument]", y.Error()) "[read : invalid argument]", y.Error())
} }
func TestNewRemoteFS_RemoteBaseURL_EmptySpecFailIndex(t *testing.T) { func TestNewRemoteFS_RemoteBaseURL_EmptySpecFailIndex(t *testing.T) {
cf := CreateOpenAPIIndexConfig() cf := CreateOpenAPIIndexConfig()
h := func(url string) (*http.Response, error) { h := func(url string) (*http.Response, error) {
r := &http.Response{} r := &http.Response{}
r.Body = &LocalFile{data: []byte{}} // no bytes to read. r.Body = &LocalFile{data: []byte{}} // no bytes to read.
return r, nil return r, nil
} }
cf.RemoteURLHandler = h cf.RemoteURLHandler = h
cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine") cf.BaseURL, _ = url.Parse("https://pb33f.io/the/love/machine")
rfs, _ := NewRemoteFSWithConfig(cf) rfs, _ := NewRemoteFSWithConfig(cf)
x, y := rfs.Open("/woof.yaml") x, y := rfs.Open("/woof.yaml")
assert.NotNil(t, x) assert.NotNil(t, x)
assert.Error(t, y) assert.Error(t, y)
assert.Equal(t, "there is nothing in the spec, it's empty - so there is nothing to be done", y.Error()) assert.Equal(t, "there is nothing in the spec, it's empty - so there is nothing to be done", y.Error())
} }

View File

@@ -650,29 +650,8 @@ func (index *SpecIndex) GetGlobalCallbacksCount() int {
// look through method for callbacks // look through method for callbacks
callbacks, _ := yamlpath.NewPath("$..callbacks") callbacks, _ := yamlpath.NewPath("$..callbacks")
// Channel used to receive the result from doSomething function
ch := make(chan string, 1)
// Create a context with a timeout of 5 seconds
ctxTimeout, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
var res []*yaml.Node var res []*yaml.Node
res, _ = callbacks.Find(m.Node)
doSomething := func(ctx context.Context, ch chan<- string) {
res, _ = callbacks.Find(m.Node)
ch <- m.Definition
}
// Start the doSomething function
go doSomething(ctxTimeout, ch)
select {
case <-ctxTimeout.Done():
fmt.Printf("Callback %d: Context cancelled: %v\n", m.Node.Line, ctxTimeout.Err())
case <-ch:
}
if len(res) > 0 { if len(res) > 0 {
for _, callback := range res[0].Content { for _, callback := range res[0].Content {
if utils.IsNodeMap(callback) { if utils.IsNodeMap(callback) {