mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 12:37:49 +00:00
a first working engine of the new design.
There is a horrible amount of work to be done to clean this up, and wire in remote support. but so far, this is working as expected and is now a much cleaner design, (once everything has been cleaned up that is) Signed-off-by: quobix <dave@quobix.com>
This commit is contained in:
@@ -89,7 +89,11 @@ type Document struct {
|
|||||||
//
|
//
|
||||||
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
|
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
|
||||||
Index *index.SpecIndex `json:"-" yaml:"-"`
|
Index *index.SpecIndex `json:"-" yaml:"-"`
|
||||||
low *low.Document
|
|
||||||
|
// Rolodex is the low-level rolodex used when creating this document.
|
||||||
|
// This in an internal structure and not part of the OpenAPI schema.
|
||||||
|
Rolodex *index.Rolodex `json:"-" yaml:"-"`
|
||||||
|
low *low.Document
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDocument will create a new high-level Document from a low-level one.
|
// NewDocument will create a new high-level Document from a low-level one.
|
||||||
|
|||||||
@@ -142,10 +142,10 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
|
|||||||
|
|
||||||
// build an index
|
// build an index
|
||||||
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
|
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
|
||||||
BaseURL: config.BaseURL,
|
BaseURL: config.BaseURL,
|
||||||
RemoteURLHandler: config.RemoteURLHandler,
|
RemoteURLHandler: config.RemoteURLHandler,
|
||||||
AllowRemoteLookup: config.AllowRemoteReferences,
|
//AllowRemoteLookup: config.AllowRemoteReferences,
|
||||||
AllowFileLookup: config.AllowFileReferences,
|
//AllowFileLookup: config.AllowFileReferences,
|
||||||
})
|
})
|
||||||
doc.Index = idx
|
doc.Index = idx
|
||||||
doc.SpecInfo = info
|
doc.SpecInfo = info
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package v3
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pb33f/libopenapi/datamodel"
|
"github.com/pb33f/libopenapi/datamodel"
|
||||||
"github.com/pb33f/libopenapi/datamodel/low"
|
"github.com/pb33f/libopenapi/datamodel/low"
|
||||||
"github.com/pb33f/libopenapi/datamodel/low/base"
|
"github.com/pb33f/libopenapi/datamodel/low/base"
|
||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
"github.com/pb33f/libopenapi/resolver"
|
|
||||||
"github.com/pb33f/libopenapi/utils"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Deprecated: Use CreateDocumentFromConfig instead. This function will be removed in a later version, it
|
// Deprecated: Use CreateDocumentFromConfig instead. This function will be removed in a later version, it
|
||||||
// defaults to allowing file and remote references, and does not support relative file references.
|
// defaults to allowing file and remote references, and does not support relative file references.
|
||||||
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
func CreateDocument(info *datamodel.SpecInfo) (*Document, error) {
|
||||||
config := datamodel.DocumentConfiguration{
|
config := datamodel.DocumentConfiguration{
|
||||||
AllowFileReferences: true,
|
AllowFileReferences: true,
|
||||||
AllowRemoteReferences: true,
|
AllowRemoteReferences: true,
|
||||||
@@ -26,61 +26,92 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateDocumentFromConfig Create a new document from the provided SpecInfo and DocumentConfiguration pointer.
|
// CreateDocumentFromConfig Create a new document from the provided SpecInfo and DocumentConfiguration pointer.
|
||||||
func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) {
|
func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, error) {
|
||||||
return createDocument(info, config)
|
return createDocument(info, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) {
|
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, error) {
|
||||||
_, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content)
|
_, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content)
|
||||||
var version low.NodeReference[string]
|
var version low.NodeReference[string]
|
||||||
if versionNode == nil {
|
if versionNode == nil {
|
||||||
return nil, []error{errors.New("no openapi version/tag found, cannot create document")}
|
return nil, errors.New("no openapi version/tag found, cannot create document")
|
||||||
}
|
}
|
||||||
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
|
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
|
||||||
doc := Document{Version: version}
|
doc := Document{Version: version}
|
||||||
|
|
||||||
// get current working directory as a basePath
|
// get current working directory as a basePath
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
|
|
||||||
// If basePath is provided override it
|
|
||||||
if config.BasePath != "" {
|
if config.BasePath != "" {
|
||||||
cwd = config.BasePath
|
cwd = config.BasePath
|
||||||
}
|
}
|
||||||
// build an index
|
|
||||||
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
|
|
||||||
BaseURL: config.BaseURL,
|
|
||||||
RemoteURLHandler: config.RemoteURLHandler,
|
|
||||||
BasePath: cwd,
|
|
||||||
AllowFileLookup: config.AllowFileReferences,
|
|
||||||
AllowRemoteLookup: config.AllowRemoteReferences,
|
|
||||||
AvoidBuildIndex: config.AvoidIndexBuild,
|
|
||||||
SpecInfo: info,
|
|
||||||
})
|
|
||||||
doc.Index = idx
|
|
||||||
|
|
||||||
|
// TODO: configure allowFileReferences and allowRemoteReferences stuff
|
||||||
|
|
||||||
|
// create an index config and shadow the document configuration.
|
||||||
|
idxConfig := index.CreateOpenAPIIndexConfig()
|
||||||
|
idxConfig.SpecInfo = info
|
||||||
|
idxConfig.BasePath = cwd
|
||||||
|
idxConfig.IgnoreArrayCircularReferences = config.IgnoreArrayCircularReferences
|
||||||
|
idxConfig.IgnorePolymorphicCircularReferences = config.IgnorePolymorphicCircularReferences
|
||||||
|
idxConfig.AvoidCircularReferenceCheck = config.SkipCircularReferenceCheck
|
||||||
|
|
||||||
|
rolodex := index.NewRolodex(idxConfig)
|
||||||
|
doc.Rolodex = rolodex
|
||||||
|
|
||||||
|
// If basePath is provided override it
|
||||||
|
if config.BasePath != "" {
|
||||||
|
var absError error
|
||||||
|
cwd, absError = filepath.Abs(config.BasePath)
|
||||||
|
if absError != nil {
|
||||||
|
return nil, absError
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a local filesystem
|
||||||
|
fileFS, err := index.NewLocalFS(cwd, os.DirFS(cwd))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the filesystem to the rolodex
|
||||||
|
rolodex.AddLocalFS(cwd, fileFS)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remote filesystem
|
||||||
|
|
||||||
|
// index the rolodex
|
||||||
|
err := rolodex.IndexTheRolodex()
|
||||||
var errs []error
|
var errs []error
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, rolodex.GetCaughtErrors()...)
|
||||||
|
}
|
||||||
|
|
||||||
errs = idx.GetReferenceIndexErrors()
|
doc.Index = rolodex.GetRootIndex()
|
||||||
|
|
||||||
|
//errs = idx.GetReferenceIndexErrors()
|
||||||
|
|
||||||
// create resolver and check for circular references.
|
// create resolver and check for circular references.
|
||||||
resolve := resolver.NewResolver(idx)
|
|
||||||
|
|
||||||
// if configured, ignore circular references in arrays and polymorphic schemas
|
//resolve := resolver.NewResolver(idx)
|
||||||
if config.IgnoreArrayCircularReferences {
|
//
|
||||||
resolve.IgnoreArrayCircularReferences()
|
//// if configured, ignore circular references in arrays and polymorphic schemas
|
||||||
}
|
//if config.IgnoreArrayCircularReferences {
|
||||||
if config.IgnorePolymorphicCircularReferences {
|
// resolve.IgnoreArrayCircularReferences()
|
||||||
resolve.IgnorePolymorphicCircularReferences()
|
//}
|
||||||
}
|
//if config.IgnorePolymorphicCircularReferences {
|
||||||
|
// resolve.IgnorePolymorphicCircularReferences()
|
||||||
// check for circular references.
|
//}
|
||||||
resolvingErrors := resolve.CheckForCircularReferences()
|
//
|
||||||
|
//if !config.AvoidIndexBuild {
|
||||||
if len(resolvingErrors) > 0 {
|
// // check for circular references.
|
||||||
for r := range resolvingErrors {
|
// resolvingErrors := resolve.CheckForCircularReferences()
|
||||||
errs = append(errs, resolvingErrors[r])
|
//
|
||||||
}
|
// if len(resolvingErrors) > 0 {
|
||||||
}
|
// for r := range resolvingErrors {
|
||||||
|
// errs = append(errs, resolvingErrors[r])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
@@ -117,10 +148,10 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
|
|||||||
|
|
||||||
wg.Add(len(extractionFuncs))
|
wg.Add(len(extractionFuncs))
|
||||||
for _, f := range extractionFuncs {
|
for _, f := range extractionFuncs {
|
||||||
go runExtraction(info, &doc, idx, f, &errs, &wg)
|
go runExtraction(info, &doc, rolodex.GetRootIndex(), f, &errs, &wg)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return &doc, errs
|
return &doc, errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ type Document struct {
|
|||||||
//
|
//
|
||||||
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
|
// This property is not a part of the OpenAPI schema, this is custom to libopenapi.
|
||||||
Index *index.SpecIndex
|
Index *index.SpecIndex
|
||||||
|
|
||||||
|
// Rolodex is a reference to the rolodex used when creating this document.
|
||||||
|
Rolodex *index.Rolodex
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindSecurityRequirement will attempt to locate a security requirement string from a supplied name.
|
// FindSecurityRequirement will attempt to locate a security requirement string from a supplied name.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package index
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pb33f/libopenapi/utils"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
@@ -157,11 +158,31 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
segs := strings.Split(value, "/")
|
segs := strings.Split(value, "/")
|
||||||
name := segs[len(segs)-1]
|
name := segs[len(segs)-1]
|
||||||
_, p := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
_, p := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
||||||
|
|
||||||
|
// determine absolute path to this definition
|
||||||
|
iroot := filepath.Dir(index.specAbsolutePath)
|
||||||
|
uri := strings.Split(value, "#/")
|
||||||
|
var componentName string
|
||||||
|
var fullDefinitionPath string
|
||||||
|
if len(uri) == 2 {
|
||||||
|
if uri[0] == "" {
|
||||||
|
fullDefinitionPath = fmt.Sprintf("%s#/%s", index.specAbsolutePath, uri[1])
|
||||||
|
} else {
|
||||||
|
abs, _ := filepath.Abs(filepath.Join(iroot, uri[0]))
|
||||||
|
fullDefinitionPath = fmt.Sprintf("%s#/%s", abs, uri[1])
|
||||||
|
}
|
||||||
|
componentName = fmt.Sprintf("#/%s", uri[1])
|
||||||
|
} else {
|
||||||
|
fullDefinitionPath = fmt.Sprintf("%s#/%s", iroot, uri[0])
|
||||||
|
componentName = fmt.Sprintf("#/%s", uri[0])
|
||||||
|
}
|
||||||
|
|
||||||
ref := &Reference{
|
ref := &Reference{
|
||||||
Definition: value,
|
FullDefinition: fullDefinitionPath,
|
||||||
Name: name,
|
Definition: componentName,
|
||||||
Node: node,
|
Name: name,
|
||||||
Path: p,
|
Node: node,
|
||||||
|
Path: p,
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to raw sequenced refs
|
// add to raw sequenced refs
|
||||||
@@ -183,10 +204,11 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
|
|||||||
if len(node.Content) > 2 {
|
if len(node.Content) > 2 {
|
||||||
copiedNode := *node
|
copiedNode := *node
|
||||||
copied := Reference{
|
copied := Reference{
|
||||||
Definition: ref.Definition,
|
FullDefinition: fullDefinitionPath,
|
||||||
Name: ref.Name,
|
Definition: ref.Definition,
|
||||||
Node: &copiedNode,
|
Name: ref.Name,
|
||||||
Path: p,
|
Node: &copiedNode,
|
||||||
|
Path: p,
|
||||||
}
|
}
|
||||||
// protect this data using a copy, prevent the resolver from destroying things.
|
// protect this data using a copy, prevent the resolver from destroying things.
|
||||||
index.refsWithSiblings[value] = copied
|
index.refsWithSiblings[value] = copied
|
||||||
@@ -413,19 +435,22 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc
|
|||||||
var found []*Reference
|
var found []*Reference
|
||||||
|
|
||||||
// run this async because when things get recursive, it can take a while
|
// run this async because when things get recursive, it can take a while
|
||||||
c := make(chan bool)
|
//c := make(chan bool)
|
||||||
|
|
||||||
locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) {
|
locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) {
|
||||||
located := index.FindComponent(ref.Definition, ref.Node)
|
located := index.FindComponent(ref.FullDefinition, ref.Node)
|
||||||
if located != nil {
|
if located != nil {
|
||||||
index.refLock.Lock()
|
index.refLock.Lock()
|
||||||
if index.allMappedRefs[ref.Definition] == nil {
|
if index.allMappedRefs[ref.Definition] == nil {
|
||||||
found = append(found, located)
|
found = append(found, located)
|
||||||
index.allMappedRefs[ref.Definition] = located
|
index.allMappedRefs[ref.Definition] = located
|
||||||
sequence[refIndex] = &ReferenceMapped{
|
rm := &ReferenceMapped{
|
||||||
Reference: located,
|
Reference: located,
|
||||||
Definition: ref.Definition,
|
Definition: ref.Definition,
|
||||||
|
FullDefinition: ref.FullDefinition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sequence[refIndex] = rm
|
||||||
}
|
}
|
||||||
index.refLock.Unlock()
|
index.refLock.Unlock()
|
||||||
} else {
|
} else {
|
||||||
@@ -440,7 +465,7 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc
|
|||||||
index.refErrors = append(index.refErrors, indexError)
|
index.refErrors = append(index.refErrors, indexError)
|
||||||
index.errorLock.Unlock()
|
index.errorLock.Unlock()
|
||||||
}
|
}
|
||||||
c <- true
|
//c <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
var refsToCheck []*Reference
|
var refsToCheck []*Reference
|
||||||
@@ -464,17 +489,17 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc
|
|||||||
|
|
||||||
for r := range refsToCheck {
|
for r := range refsToCheck {
|
||||||
// expand our index of all mapped refs
|
// expand our index of all mapped refs
|
||||||
go locate(refsToCheck[r], r, mappedRefsInSequence)
|
//go locate(refsToCheck[r], r, mappedRefsInSequence)
|
||||||
// locate(refsToCheck[r], r, mappedRefsInSequence) // used for sync testing.
|
locate(refsToCheck[r], r, mappedRefsInSequence) // used for sync testing.
|
||||||
}
|
}
|
||||||
|
|
||||||
completedRefs := 0
|
//completedRefs := 0
|
||||||
for completedRefs < len(refsToCheck) {
|
//for completedRefs < len(refsToCheck) {
|
||||||
select {
|
// select {
|
||||||
case <-c:
|
// case <-c:
|
||||||
completedRefs++
|
// completedRefs++
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
for m := range mappedRefsInSequence {
|
for m := range mappedRefsInSequence {
|
||||||
if mappedRefsInSequence[m] != nil {
|
if mappedRefsInSequence[m] != nil {
|
||||||
index.allMappedRefsSequenced = append(index.allMappedRefsSequenced, mappedRefsInSequence[m])
|
index.allMappedRefsSequenced = append(index.allMappedRefsSequenced, mappedRefsInSequence[m])
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -26,64 +25,33 @@ func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Re
|
|||||||
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) {
|
||||||
|
// if index.config.AllowFileLookup {
|
||||||
|
// return index.lookupFileReference(id)
|
||||||
|
// } else {
|
||||||
|
// return nil, nil, fmt.Errorf("local lookups are not permitted, " +
|
||||||
|
// "please set AllowFileLookup to true in the configuration")
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
|
//witch DetermineReferenceResolveType(componentId) {
|
||||||
if index.config.AllowFileLookup {
|
//case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case.
|
||||||
return index.lookupFileReference(id)
|
// return index.FindComponentInRoot(componentId)
|
||||||
} else {
|
|
||||||
return nil, nil, fmt.Errorf("local lookups are not permitted, " +
|
|
||||||
"please set AllowFileLookup to true in the configuration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch DetermineReferenceResolveType(componentId) {
|
//case HttpResolve, FileResolve:
|
||||||
case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case.
|
return index.performExternalLookup(strings.Split(componentId, "#/"))
|
||||||
return index.FindComponentInRoot(componentId)
|
|
||||||
|
|
||||||
case HttpResolve:
|
//}
|
||||||
uri := strings.Split(componentId, "#")
|
//return nil
|
||||||
if len(uri) >= 2 {
|
|
||||||
return index.performExternalLookup(uri, componentId, remoteLookup, parent)
|
|
||||||
}
|
|
||||||
if len(uri) == 1 {
|
|
||||||
// if there is no reference, second segment is empty / has no name
|
|
||||||
// this means there is no component to look-up and the entire file should be pulled in.
|
|
||||||
// to stop all the other code from breaking (that is expecting a component), let's just post-pend
|
|
||||||
// a hash to the end of the componentId and ensure the uri slice is as expected.
|
|
||||||
// described in https://github.com/pb33f/libopenapi/issues/37
|
|
||||||
componentId = fmt.Sprintf("%s#", componentId)
|
|
||||||
uri = append(uri, "")
|
|
||||||
return index.performExternalLookup(uri, componentId, remoteLookup, parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
case FileResolve:
|
|
||||||
uri := strings.Split(componentId, "#")
|
|
||||||
if len(uri) == 2 {
|
|
||||||
return index.performExternalLookup(uri, componentId, fileLookup, parent)
|
|
||||||
}
|
|
||||||
if len(uri) == 1 {
|
|
||||||
// if there is no reference, second segment is empty / has no name
|
|
||||||
// this means there is no component to look-up and the entire file should be pulled in.
|
|
||||||
// to stop all the other code from breaking (that is expecting a component), let's just post-pend
|
|
||||||
// a hash to the end of the componentId and ensure the uri slice is as expected.
|
|
||||||
// described in https://github.com/pb33f/libopenapi/issues/37
|
|
||||||
//
|
|
||||||
// ^^ this same issue was re-reported in file based lookups in vacuum.
|
|
||||||
// more info here: https://github.com/daveshanley/vacuum/issues/225
|
|
||||||
componentId = fmt.Sprintf("%s#", componentId)
|
|
||||||
uri = append(uri, "")
|
|
||||||
return index.performExternalLookup(uri, componentId, fileLookup, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second}
|
var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second}
|
||||||
@@ -107,106 +75,106 @@ func getRemoteDoc(g RemoteURLHandler, u string, d chan []byte, e chan error) {
|
|||||||
|
|
||||||
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)
|
||||||
var getter RemoteURLHandler = httpClient.Get
|
// var getter = httpClient.Get
|
||||||
if index.config != nil && index.config.RemoteURLHandler != nil {
|
// if index.config != nil && index.config.RemoteURLHandler != nil {
|
||||||
getter = index.config.RemoteURLHandler
|
// getter = index.config.RemoteURLHandler
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// if we have a remote handler, use it instead of the default.
|
// // if we have a remote handler, use it instead of the default.
|
||||||
if index.config != nil && index.config.FSHandler != nil {
|
// if index.config != nil && index.config.FSHandler != nil {
|
||||||
go func() {
|
// go func() {
|
||||||
remoteFS := index.config.FSHandler
|
// remoteFS := index.config.FSHandler
|
||||||
remoteFile, rErr := remoteFS.Open(uri)
|
// remoteFile, rErr := remoteFS.Open(uri)
|
||||||
if rErr != nil {
|
// if rErr != nil {
|
||||||
e := fmt.Errorf("unable to open remote file: %s", rErr)
|
// e := fmt.Errorf("unable to open remote file: %s", rErr)
|
||||||
ec <- e
|
// ec <- e
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
b, ioErr := io.ReadAll(remoteFile)
|
// b, ioErr := io.ReadAll(remoteFile)
|
||||||
if ioErr != nil {
|
// if ioErr != nil {
|
||||||
e := fmt.Errorf("unable to read remote file bytes: %s", ioErr)
|
// e := fmt.Errorf("unable to read remote file bytes: %s", ioErr)
|
||||||
ec <- e
|
// ec <- e
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
bc <- b
|
// bc <- b
|
||||||
}()
|
// }()
|
||||||
} else {
|
// } else {
|
||||||
go getRemoteDoc(getter, uri, bc, ec)
|
// go getRemoteDoc(getter, 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
|
||||||
}
|
// }
|
||||||
if len(body) > 0 {
|
// if len(body) > 0 {
|
||||||
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 = "$"
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
query, err := url.PathUnescape(query)
|
//query, err := url.PathUnescape(query)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return nil, nil, err
|
// return nil, nil, err
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,73 +182,83 @@ func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node,
|
|||||||
// 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)
|
||||||
|
absoluteFileLocation, _ := filepath.Abs(filepath.Join(filepath.Dir(index.specAbsolutePath), file))
|
||||||
|
|
||||||
var parsedRemoteDocument *yaml.Node
|
// extract the document from the rolodex.
|
||||||
|
rFile, rError := index.rolodex.Open(absoluteFileLocation)
|
||||||
if index.seenRemoteSources[file] != nil {
|
if rError != nil {
|
||||||
parsedRemoteDocument = index.seenRemoteSources[file]
|
return nil, nil, rError
|
||||||
} else {
|
|
||||||
|
|
||||||
base := index.config.BasePath
|
|
||||||
fileToRead := filepath.Join(base, filePath, fileName)
|
|
||||||
var body []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// if we have an FS handler, use it instead of the default behavior
|
|
||||||
if index.config != nil && index.config.FSHandler != nil {
|
|
||||||
remoteFS := index.config.FSHandler
|
|
||||||
remoteFile, rErr := remoteFS.Open(fileToRead)
|
|
||||||
if rErr != nil {
|
|
||||||
e := fmt.Errorf("unable to open file: %s", rErr)
|
|
||||||
return nil, nil, e
|
|
||||||
}
|
|
||||||
body, err = io.ReadAll(remoteFile)
|
|
||||||
if err != nil {
|
|
||||||
e := fmt.Errorf("unable to read file bytes: %s", err)
|
|
||||||
return nil, nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// try and read the file off the local file system, if it fails
|
|
||||||
// check for a baseURL and then ask our remote lookup function to go try and get it.
|
|
||||||
body, err = os.ReadFile(fileToRead)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
// if we have a baseURL, then we can try and get the file from there.
|
|
||||||
if index.config != nil && index.config.BaseURL != nil {
|
|
||||||
|
|
||||||
u := index.config.BaseURL
|
|
||||||
remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true)
|
|
||||||
a, b, e := index.lookupRemoteReference(remoteRef)
|
|
||||||
if e != nil {
|
|
||||||
// give up, we can't find the file, not locally, not remotely. It's toast.
|
|
||||||
return nil, nil, e
|
|
||||||
}
|
|
||||||
return a, b, nil
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// no baseURL? then we can't do anything, give up.
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var remoteDoc yaml.Node
|
|
||||||
err = yaml.Unmarshal(body, &remoteDoc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
parsedRemoteDocument = &remoteDoc
|
|
||||||
if index.seenLocalSources != nil {
|
|
||||||
index.sourceLock.Lock()
|
|
||||||
index.seenLocalSources[file] = &remoteDoc
|
|
||||||
index.sourceLock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsedDocument, err := rFile.GetContentAsYAMLNode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//if index.seenRemoteSources[file] != nil {
|
||||||
|
// parsedDocument = index.seenRemoteSources[file]
|
||||||
|
//} else {
|
||||||
|
//
|
||||||
|
// base := index.config.BasePath
|
||||||
|
// fileToRead := filepath.Join(base, filePath, fileName)
|
||||||
|
// var body []byte
|
||||||
|
// var err error
|
||||||
|
//
|
||||||
|
// // if we have an FS handler, use it instead of the default behavior
|
||||||
|
// if index.config != nil && index.config.FSHandler != nil {
|
||||||
|
// remoteFS := index.config.FSHandler
|
||||||
|
// remoteFile, rErr := remoteFS.Open(fileToRead)
|
||||||
|
// if rErr != nil {
|
||||||
|
// e := fmt.Errorf("unable to open file: %s", rErr)
|
||||||
|
// return nil, nil, e
|
||||||
|
// }
|
||||||
|
// body, err = io.ReadAll(remoteFile)
|
||||||
|
// if err != nil {
|
||||||
|
// e := fmt.Errorf("unable to read file bytes: %s", err)
|
||||||
|
// return nil, nil, e
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// // try and read the file off the local file system, if it fails
|
||||||
|
// // check for a baseURL and then ask our remote lookup function to go try and get it.
|
||||||
|
// body, err = os.ReadFile(fileToRead)
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
//
|
||||||
|
// // if we have a baseURL, then we can try and get the file from there.
|
||||||
|
// if index.config != nil && index.config.BaseURL != nil {
|
||||||
|
//
|
||||||
|
// u := index.config.BaseURL
|
||||||
|
// remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true)
|
||||||
|
// a, b, e := index.lookupRemoteReference(remoteRef)
|
||||||
|
// if e != nil {
|
||||||
|
// // give up, we can't find the file, not locally, not remotely. It's toast.
|
||||||
|
// return nil, nil, e
|
||||||
|
// }
|
||||||
|
// return a, b, nil
|
||||||
|
//
|
||||||
|
// } else {
|
||||||
|
// // no baseURL? then we can't do anything, give up.
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// var remoteDoc yaml.Node
|
||||||
|
// err = yaml.Unmarshal(body, &remoteDoc)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
// parsedDocument = &remoteDoc
|
||||||
|
// if index.seenLocalSources != nil {
|
||||||
|
// index.sourceLock.Lock()
|
||||||
|
// index.seenLocalSources[file] = &remoteDoc
|
||||||
|
// 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 {
|
||||||
@@ -289,7 +267,7 @@ func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node,
|
|||||||
query = "$"
|
query = "$"
|
||||||
}
|
}
|
||||||
|
|
||||||
query, err := url.PathUnescape(query)
|
query, err = url.PathUnescape(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -302,154 +280,214 @@ func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
result, _ := path.Find(parsedRemoteDocument)
|
result, _ := path.Find(parsedDocument)
|
||||||
if len(result) == 1 {
|
if len(result) == 1 {
|
||||||
return result[0], parsedRemoteDocument, nil
|
return result[0], parsedDocument, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, parsedRemoteDocument, nil
|
return nil, parsedDocument, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
|
func FindComponent(root *yaml.Node, componentId, absoluteFilePath string) *Reference {
|
||||||
if index.root != nil {
|
// check component for url encoding.
|
||||||
|
if strings.Contains(componentId, "%") {
|
||||||
|
// decode the url.
|
||||||
|
componentId, _ = url.QueryUnescape(componentId)
|
||||||
|
}
|
||||||
|
|
||||||
// check component for url encoding.
|
name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId)
|
||||||
if strings.Contains(componentId, "%") {
|
path, err := yamlpath.NewPath(friendlySearch)
|
||||||
// decode the url.
|
if path == nil || err != nil {
|
||||||
componentId, _ = url.QueryUnescape(componentId)
|
return nil // no component found
|
||||||
|
}
|
||||||
|
res, _ := path.Find(root)
|
||||||
|
|
||||||
|
if len(res) == 1 {
|
||||||
|
resNode := res[0]
|
||||||
|
if res[0].Kind == yaml.DocumentNode {
|
||||||
|
resNode = res[0].Content[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId)
|
fullDef := fmt.Sprintf("%s%s", absoluteFilePath, componentId)
|
||||||
path, err := yamlpath.NewPath(friendlySearch)
|
|
||||||
if path == nil || err != nil {
|
|
||||||
return nil // no component found
|
|
||||||
}
|
|
||||||
res, _ := path.Find(index.root)
|
|
||||||
|
|
||||||
if len(res) == 1 {
|
// extract properties
|
||||||
resNode := res[0]
|
|
||||||
if res[0].Kind == yaml.DocumentNode {
|
|
||||||
resNode = res[0].Content[0]
|
|
||||||
}
|
|
||||||
ref := &Reference{
|
|
||||||
Definition: componentId,
|
|
||||||
Name: name,
|
|
||||||
Node: resNode,
|
|
||||||
Path: friendlySearch,
|
|
||||||
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
return ref
|
ref := &Reference{
|
||||||
|
FullDefinition: fullDef,
|
||||||
|
Definition: componentId,
|
||||||
|
Name: name,
|
||||||
|
Node: resNode,
|
||||||
|
Path: friendlySearch,
|
||||||
|
RequiredRefProperties: extractDefinitionRequiredRefProperties(resNode, map[string][]string{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ref
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
|
//func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
|
||||||
lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference {
|
// if index.root != nil {
|
||||||
|
// return FindComponent(index.root, componentId, )
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (index *SpecIndex) performExternalLookup(uri []string) *Reference {
|
||||||
|
|
||||||
if len(uri) > 0 {
|
if len(uri) > 0 {
|
||||||
index.externalLock.RLock()
|
|
||||||
externalSpecIndex := index.externalSpecIndex[uri[0]]
|
|
||||||
index.externalLock.RUnlock()
|
|
||||||
|
|
||||||
if externalSpecIndex == nil {
|
// split string to remove file reference
|
||||||
_, newRoot, err := lookupFunction(componentId)
|
file := strings.ReplaceAll(uri[0], "file:", "")
|
||||||
if err != nil {
|
fileName := filepath.Base(file)
|
||||||
indexError := &IndexingError{
|
|
||||||
Err: err,
|
|
||||||
Node: parent,
|
|
||||||
Path: componentId,
|
|
||||||
}
|
|
||||||
index.errorLock.Lock()
|
|
||||||
index.refErrors = append(index.refErrors, indexError)
|
|
||||||
index.errorLock.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cool, cool, lets index this spec also. This is a recursive action and will keep going
|
var absoluteFileLocation string
|
||||||
// until all remote references have been found.
|
if filepath.IsAbs(file) {
|
||||||
var bp *url.URL
|
absoluteFileLocation = file
|
||||||
var bd string
|
} else {
|
||||||
|
absoluteFileLocation, _ = filepath.Abs(filepath.Join(filepath.Dir(index.specAbsolutePath), file))
|
||||||
if index.config.BaseURL != nil {
|
|
||||||
bp = index.config.BaseURL
|
|
||||||
}
|
|
||||||
if index.config.BasePath != "" {
|
|
||||||
bd = index.config.BasePath
|
|
||||||
}
|
|
||||||
|
|
||||||
var path, newBasePath string
|
|
||||||
var newUrl *url.URL
|
|
||||||
|
|
||||||
if bp != nil {
|
|
||||||
path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false)
|
|
||||||
newUrl, _ = url.Parse(path)
|
|
||||||
newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path)))
|
|
||||||
}
|
|
||||||
if bd != "" {
|
|
||||||
if len(uri[0]) > 0 {
|
|
||||||
// if there is no base url defined, but we can know we have been requested remotely,
|
|
||||||
// set the base url to the remote url base path.
|
|
||||||
// first check if the first param is actually a URL
|
|
||||||
io, er := url.ParseRequestURI(uri[0])
|
|
||||||
if er != nil {
|
|
||||||
newBasePath = filepath.Dir(filepath.Join(bd, uri[0]))
|
|
||||||
} else {
|
|
||||||
if newUrl == nil || newUrl.String() != io.String() {
|
|
||||||
newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", io.Scheme, io.Host, filepath.Dir(io.Path)))
|
|
||||||
}
|
|
||||||
newBasePath = filepath.Dir(filepath.Join(bd, uri[1]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newBasePath = filepath.Dir(filepath.Join(bd, uri[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if newUrl != nil || newBasePath != "" {
|
|
||||||
newConfig := &SpecIndexConfig{
|
|
||||||
BaseURL: newUrl,
|
|
||||||
BasePath: newBasePath,
|
|
||||||
AllowRemoteLookup: index.config.AllowRemoteLookup,
|
|
||||||
AllowFileLookup: index.config.AllowFileLookup,
|
|
||||||
ParentIndex: index,
|
|
||||||
seenRemoteSources: index.config.seenRemoteSources,
|
|
||||||
remoteLock: index.config.remoteLock,
|
|
||||||
uri: uri,
|
|
||||||
}
|
|
||||||
|
|
||||||
var newIndex *SpecIndex
|
|
||||||
seen := index.SearchAncestryForSeenURI(uri[0])
|
|
||||||
if seen == nil {
|
|
||||||
|
|
||||||
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
|
|
||||||
} else {
|
|
||||||
externalSpecIndex = seen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if externalSpecIndex != nil {
|
// extract the document from the rolodex.
|
||||||
foundRef := externalSpecIndex.FindComponentInRoot(uri[1])
|
rFile, rError := index.rolodex.Open(absoluteFileLocation)
|
||||||
|
if rError != nil {
|
||||||
|
logger.Error("unable to open rolodex file", "file", absoluteFileLocation, "error", rError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedDocument, err := rFile.GetContentAsYAMLNode()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("unable to parse rolodex file", "file", absoluteFileLocation, "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("parsedDocument: %v\n", parsedDocument)
|
||||||
|
|
||||||
|
//index.externalLock.RLock()
|
||||||
|
//externalSpecIndex := index.externalSpecIndex[uri[0]]
|
||||||
|
//index.externalLock.RUnlock()
|
||||||
|
|
||||||
|
//if externalSpecIndex == nil {
|
||||||
|
// _, newRoot, err := lookupFunction(componentId)
|
||||||
|
// if err != nil {
|
||||||
|
// indexError := &IndexingError{
|
||||||
|
// Err: err,
|
||||||
|
// Node: parent,
|
||||||
|
// Path: componentId,
|
||||||
|
// }
|
||||||
|
// index.errorLock.Lock()
|
||||||
|
// index.refErrors = append(index.refErrors, indexError)
|
||||||
|
// index.errorLock.Unlock()
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // cool, cool, lets index this spec also. This is a recursive action and will keep going
|
||||||
|
// // until all remote references have been found.
|
||||||
|
// var bp *url.URL
|
||||||
|
// var bd string
|
||||||
|
//
|
||||||
|
// if index.config.BaseURL != nil {
|
||||||
|
// bp = index.config.BaseURL
|
||||||
|
// }
|
||||||
|
// if index.config.BasePath != "" {
|
||||||
|
// bd = index.config.BasePath
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var path, newBasePath string
|
||||||
|
// var newUrl *url.URL
|
||||||
|
//
|
||||||
|
// if bp != nil {
|
||||||
|
// path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false)
|
||||||
|
// newUrl, _ = url.Parse(path)
|
||||||
|
// newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path)))
|
||||||
|
// }
|
||||||
|
// if bd != "" {
|
||||||
|
// 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.
|
||||||
|
// // first check if the first param is actually a URL
|
||||||
|
// io, er := url.ParseRequestURI(uri[0])
|
||||||
|
// if er != nil {
|
||||||
|
// newBasePath = filepath.Dir(filepath.Join(bd, uri[0]))
|
||||||
|
// } else {
|
||||||
|
// if newUrl == nil || newUrl.String() != io.String() {
|
||||||
|
// newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", io.Scheme, io.Host, filepath.Dir(io.Path)))
|
||||||
|
// }
|
||||||
|
// newBasePath = filepath.Dir(filepath.Join(bd, uri[1]))
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// newBasePath = filepath.Dir(filepath.Join(bd, uri[0]))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if newUrl != nil || newBasePath != "" {
|
||||||
|
// newConfig := &SpecIndexConfig{
|
||||||
|
// BaseURL: newUrl,
|
||||||
|
// BasePath: newBasePath,
|
||||||
|
// AllowRemoteLookup: index.config.AllowRemoteLookup,
|
||||||
|
// AllowFileLookup: index.config.AllowFileLookup,
|
||||||
|
// ParentIndex: index,
|
||||||
|
// seenRemoteSources: index.config.seenRemoteSources,
|
||||||
|
// remoteLock: index.config.remoteLock,
|
||||||
|
// uri: uri,
|
||||||
|
// AvoidBuildIndex: index.config.AvoidBuildIndex,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var newIndex *SpecIndex
|
||||||
|
// seen := index.SearchAncestryForSeenURI(uri[0])
|
||||||
|
// if seen == nil {
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// } else {
|
||||||
|
// externalSpecIndex = seen
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
wholeFile := false
|
||||||
|
query := ""
|
||||||
|
if len(uri) < 2 {
|
||||||
|
wholeFile = true
|
||||||
|
} else {
|
||||||
|
query = fmt.Sprintf("#/%s", strings.Replace(uri[1], "~1", "./", 1))
|
||||||
|
query = strings.ReplaceAll(query, "~1", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there is a component we want to suck in, or if the
|
||||||
|
// entire root needs to come in.
|
||||||
|
var foundRef *Reference
|
||||||
|
if wholeFile {
|
||||||
|
if parsedDocument.Kind == yaml.DocumentNode {
|
||||||
|
parsedDocument = parsedDocument.Content[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remote locations
|
||||||
|
|
||||||
|
foundRef = &Reference{
|
||||||
|
Definition: fileName,
|
||||||
|
Name: fileName,
|
||||||
|
Node: parsedDocument,
|
||||||
|
IsRemote: true,
|
||||||
|
RemoteLocation: absoluteFileLocation,
|
||||||
|
Path: "$",
|
||||||
|
RequiredRefProperties: extractDefinitionRequiredRefProperties(parsedDocument, map[string][]string{}),
|
||||||
|
}
|
||||||
|
return foundRef
|
||||||
|
} else {
|
||||||
|
foundRef = FindComponent(parsedDocument, query, absoluteFileLocation)
|
||||||
if foundRef != nil {
|
if foundRef != nil {
|
||||||
nameSegs := strings.Split(uri[1], "/")
|
foundRef.IsRemote = true
|
||||||
ref := &Reference{
|
foundRef.RemoteLocation = absoluteFileLocation
|
||||||
Definition: componentId,
|
return foundRef
|
||||||
Name: nameSegs[len(nameSegs)-1],
|
|
||||||
Node: foundRef.Node,
|
|
||||||
IsRemote: true,
|
|
||||||
RemoteLocation: componentId,
|
|
||||||
Path: foundRef.Path,
|
|
||||||
}
|
|
||||||
return ref
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ func TestSpecIndex_CheckCircularIndex(t *testing.T) {
|
|||||||
c.BasePath = "../test_specs"
|
c.BasePath = "../test_specs"
|
||||||
index := NewSpecIndexWithConfig(&rootNode, c)
|
index := NewSpecIndexWithConfig(&rootNode, c)
|
||||||
assert.Nil(t, index.uri)
|
assert.Nil(t, index.uri)
|
||||||
assert.NotNil(t, index.children[0].uri)
|
|
||||||
assert.NotNil(t, index.children[0].children[0].uri)
|
|
||||||
assert.NotNil(t, index.SearchIndexForReference("second.yaml#/properties/property2"))
|
assert.NotNil(t, index.SearchIndexForReference("second.yaml#/properties/property2"))
|
||||||
assert.NotNil(t, index.SearchIndexForReference("second.yaml"))
|
assert.NotNil(t, index.SearchIndexForReference("second.yaml"))
|
||||||
assert.Nil(t, index.SearchIndexForReference("fourth.yaml"))
|
assert.Nil(t, index.SearchIndexForReference("fourth.yaml"))
|
||||||
@@ -504,3 +502,70 @@ paths:
|
|||||||
assert.Equal(t, `invalid URL escape "%$p"`, index.GetReferenceIndexErrors()[0].Error())
|
assert.Equal(t, `invalid URL escape "%$p"`, index.GetReferenceIndexErrors()[0].Error())
|
||||||
assert.Equal(t, "component 'exisiting.yaml#/paths/~1pet~1%$petId%7D/get/parameters' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
|
assert.Equal(t, "component 'exisiting.yaml#/paths/~1pet~1%$petId%7D/get/parameters' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSpecIndex_Complex_Local_File_Design(t *testing.T) {
|
||||||
|
|
||||||
|
main := `openapi: 3.1.0
|
||||||
|
paths:
|
||||||
|
/anything/circularReference:
|
||||||
|
get:
|
||||||
|
operationId: circularReferenceGet
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "components.yaml#/components/schemas/validCircularReferenceObject"
|
||||||
|
/anything/oneOfCircularReference:
|
||||||
|
get:
|
||||||
|
operationId: oneOfCircularReferenceGet
|
||||||
|
tags:
|
||||||
|
- generation
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "components.yaml#/components/schemas/oneOfCircularReferenceObject"`
|
||||||
|
|
||||||
|
components := `components:
|
||||||
|
schemas:
|
||||||
|
validCircularReferenceObject:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
circular:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/validCircularReferenceObject"
|
||||||
|
oneOfCircularReferenceObject:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
child:
|
||||||
|
oneOf:
|
||||||
|
- $ref: "#/components/schemas/oneOfCircularReferenceObject"
|
||||||
|
- $ref: "#/components/schemas/simpleObject"
|
||||||
|
required:
|
||||||
|
- child
|
||||||
|
simpleObject:
|
||||||
|
description: "simple"
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
str:
|
||||||
|
type: string
|
||||||
|
description: "A string property."
|
||||||
|
example: "example" `
|
||||||
|
|
||||||
|
_ = os.WriteFile("components.yaml", []byte(components), 0644)
|
||||||
|
|
||||||
|
var rootNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(main), &rootNode)
|
||||||
|
|
||||||
|
c := CreateOpenAPIIndexConfig()
|
||||||
|
index := NewSpecIndexWithConfig(&rootNode, c)
|
||||||
|
|
||||||
|
assert.Len(t, index.GetReferenceIndexErrors(), 2)
|
||||||
|
assert.Equal(t, `invalid URL escape "%$p"`, index.GetReferenceIndexErrors()[0].Error())
|
||||||
|
assert.Equal(t, "component 'exisiting.yaml#/paths/~1pet~1%$petId%7D/get/parameters' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/sync/syncmap"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,15 +24,17 @@ const (
|
|||||||
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
|
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
|
||||||
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
|
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
|
||||||
type Reference struct {
|
type Reference struct {
|
||||||
Definition string
|
FullDefinition string
|
||||||
Name string
|
Definition string
|
||||||
Node *yaml.Node
|
Name string
|
||||||
ParentNode *yaml.Node
|
Node *yaml.Node
|
||||||
ParentNodeSchemaType string // used to determine if the parent node is an array or not.
|
ParentNode *yaml.Node
|
||||||
Resolved bool
|
ParentNodeSchemaType string // used to determine if the parent node is an array or not.
|
||||||
Circular bool
|
Resolved bool
|
||||||
Seen bool
|
Circular bool
|
||||||
IsRemote bool
|
Seen bool
|
||||||
|
IsRemote bool
|
||||||
|
//FileLocation string
|
||||||
RemoteLocation string
|
RemoteLocation string
|
||||||
Path string // this won't always be available.
|
Path string // this won't always be available.
|
||||||
RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
|
RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
|
||||||
@@ -41,8 +42,9 @@ type Reference struct {
|
|||||||
|
|
||||||
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
|
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
|
||||||
type ReferenceMapped struct {
|
type ReferenceMapped struct {
|
||||||
Reference *Reference
|
Reference *Reference
|
||||||
Definition string
|
Definition string
|
||||||
|
FullDefinition string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable
|
// SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable
|
||||||
@@ -67,6 +69,7 @@ type SpecIndexConfig struct {
|
|||||||
// If resolving remotely, the RemoteURLHandler will be used to fetch the remote document.
|
// If resolving remotely, the RemoteURLHandler will be used to fetch the remote document.
|
||||||
// If not set, the default http client will be used.
|
// If not set, the default http client will be used.
|
||||||
// Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132
|
// Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132
|
||||||
|
// deprecated: Use the Rolodex instead
|
||||||
RemoteURLHandler func(url string) (*http.Response, error)
|
RemoteURLHandler func(url string) (*http.Response, error)
|
||||||
|
|
||||||
// FSHandler is an entity that implements the `fs.FS` interface that will be used to fetch local or remote documents.
|
// FSHandler is an entity that implements the `fs.FS` interface that will be used to fetch local or remote documents.
|
||||||
@@ -81,6 +84,7 @@ type SpecIndexConfig struct {
|
|||||||
// it also overrides the RemoteURLHandler if set.
|
// it also overrides the RemoteURLHandler if set.
|
||||||
//
|
//
|
||||||
// Resolves[#85] https://github.com/pb33f/libopenapi/issues/85
|
// Resolves[#85] https://github.com/pb33f/libopenapi/issues/85
|
||||||
|
// deprecated: Use the Rolodex instead
|
||||||
FSHandler fs.FS
|
FSHandler fs.FS
|
||||||
|
|
||||||
// If resolving locally, the BasePath will be the root from which relative references will be resolved from
|
// If resolving locally, the BasePath will be the root from which relative references will be resolved from
|
||||||
@@ -92,12 +96,13 @@ type SpecIndexConfig struct {
|
|||||||
// exploits, but it's better to be safe than sorry.
|
// exploits, but it's better to be safe than sorry.
|
||||||
//
|
//
|
||||||
// To read more about this, you can find a discussion here: https://github.com/pb33f/libopenapi/pull/64
|
// To read more about this, you can find a discussion here: https://github.com/pb33f/libopenapi/pull/64
|
||||||
AllowRemoteLookup bool // Allow remote lookups for references. Defaults to false
|
//AllowRemoteLookup bool // Allow remote lookups for references. Defaults to false
|
||||||
AllowFileLookup bool // Allow file lookups for references. Defaults to false
|
//AllowFileLookup bool // Allow file lookups for references. Defaults to false
|
||||||
|
|
||||||
// ParentIndex allows the index to be created with knowledge of a parent, before being parsed. This allows
|
// ParentIndex allows the index to be created with knowledge of a parent, before being parsed. This allows
|
||||||
// a breakglass to be used to prevent loops, checking the tree before recursing down.
|
// a breakglass to be used to prevent loops, checking the tree before cursing down.
|
||||||
ParentIndex *SpecIndex
|
// deprecated: Use the Rolodex instead, this is no longer needed, indexes are finite and do not have children.
|
||||||
|
//ParentIndex *SpecIndex
|
||||||
|
|
||||||
// If set to true, the index will not be built out, which means only the foundational elements will be
|
// If set to true, the index will not be built out, which means only the foundational elements will be
|
||||||
// parsed and added to the index. This is useful to avoid building out an index if the specification is
|
// parsed and added to the index. This is useful to avoid building out an index if the specification is
|
||||||
@@ -106,14 +111,40 @@ type SpecIndexConfig struct {
|
|||||||
// Use the `BuildIndex()` method on the index to build it out once resolved/ready.
|
// Use the `BuildIndex()` method on the index to build it out once resolved/ready.
|
||||||
AvoidBuildIndex bool
|
AvoidBuildIndex bool
|
||||||
|
|
||||||
|
AvoidCircularReferenceCheck bool
|
||||||
|
|
||||||
// SpecInfo is a pointer to the SpecInfo struct that contains the root node and the spec version. It's the
|
// SpecInfo is a pointer to the SpecInfo struct that contains the root node and the spec version. It's the
|
||||||
// struct that was used to create this index.
|
// struct that was used to create this index.
|
||||||
SpecInfo *datamodel.SpecInfo
|
SpecInfo *datamodel.SpecInfo
|
||||||
|
|
||||||
|
// Rolodex is what provides all file and remote based lookups. Without the rolodex, no remote or file lookups
|
||||||
|
// can be used. Normally you won't need to worry about setting this as each root document gets a rolodex
|
||||||
|
// of its own automatically.
|
||||||
|
Rolodex *Rolodex
|
||||||
|
|
||||||
|
SpecAbsolutePath string
|
||||||
|
|
||||||
|
// IgnorePolymorphicCircularReferences will skip over checking for circular references in polymorphic schemas.
|
||||||
|
// A polymorphic schema is any schema that is composed other schemas using references via `oneOf`, `anyOf` of `allOf`.
|
||||||
|
// This is disabled by default, which means polymorphic circular references will be checked.
|
||||||
|
IgnorePolymorphicCircularReferences bool
|
||||||
|
|
||||||
|
// IgnoreArrayCircularReferences will skip over checking for circular references in arrays. Sometimes a circular
|
||||||
|
// reference is required to describe a data-shape correctly. Often those shapes are valid circles if the
|
||||||
|
// type of the schema implementing the loop is an array. An empty array would technically break the loop.
|
||||||
|
// So if libopenapi is returning circular references for this use case, then this option should be enabled.
|
||||||
|
// this is disabled by default, which means array circular references will be checked.
|
||||||
|
IgnoreArrayCircularReferences bool
|
||||||
|
|
||||||
|
// SkipCircularReferenceCheck will skip over checking for circular references. This is disabled by default, which
|
||||||
|
// means circular references will be checked. This is useful for developers building out models that should be
|
||||||
|
// indexed later on.
|
||||||
|
//SkipCircularReferenceCheck bool
|
||||||
|
|
||||||
// private fields
|
// private fields
|
||||||
seenRemoteSources *syncmap.Map
|
//seenRemoteSources *syncmap.Map
|
||||||
remoteLock *sync.Mutex
|
//remoteLock *sync.Mutex
|
||||||
uri []string
|
uri []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
||||||
@@ -123,10 +154,10 @@ type SpecIndexConfig struct {
|
|||||||
func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
||||||
cw, _ := os.Getwd()
|
cw, _ := os.Getwd()
|
||||||
return &SpecIndexConfig{
|
return &SpecIndexConfig{
|
||||||
BasePath: cw,
|
BasePath: cw,
|
||||||
AllowRemoteLookup: true,
|
//AllowRemoteLookup: true,
|
||||||
AllowFileLookup: true,
|
//AllowFileLookup: true,
|
||||||
seenRemoteSources: &syncmap.Map{},
|
//seenRemoteSources: &syncmap.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +168,10 @@ func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
|||||||
func CreateClosedAPIIndexConfig() *SpecIndexConfig {
|
func CreateClosedAPIIndexConfig() *SpecIndexConfig {
|
||||||
cw, _ := os.Getwd()
|
cw, _ := os.Getwd()
|
||||||
return &SpecIndexConfig{
|
return &SpecIndexConfig{
|
||||||
BasePath: cw,
|
BasePath: cw,
|
||||||
AllowRemoteLookup: false,
|
//AllowRemoteLookup: false,
|
||||||
AllowFileLookup: false,
|
//AllowFileLookup: false,
|
||||||
seenRemoteSources: &syncmap.Map{},
|
//seenRemoteSources: &syncmap.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +179,7 @@ func CreateClosedAPIIndexConfig() *SpecIndexConfig {
|
|||||||
// quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules,
|
// quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules,
|
||||||
// everything is pre-walked if you need it.
|
// everything is pre-walked if you need it.
|
||||||
type SpecIndex struct {
|
type SpecIndex struct {
|
||||||
|
rolodex *Rolodex // the rolodex is used to fetch remote and file based documents.
|
||||||
allRefs map[string]*Reference // all (deduplicated) refs
|
allRefs map[string]*Reference // all (deduplicated) refs
|
||||||
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
|
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
|
||||||
linesWithRefs map[int]bool // lines that link to references.
|
linesWithRefs map[int]bool // lines that link to references.
|
||||||
@@ -183,7 +215,8 @@ type SpecIndex struct {
|
|||||||
rootSecurity []*Reference // root security definitions.
|
rootSecurity []*Reference // root security definitions.
|
||||||
rootSecurityNode *yaml.Node // root security node.
|
rootSecurityNode *yaml.Node // root security node.
|
||||||
refsWithSiblings map[string]Reference // references with sibling elements next to them
|
refsWithSiblings map[string]Reference // references with sibling elements next to them
|
||||||
pathRefsLock sync.Mutex // create lock for all refs maps, we want to build data as fast as we can
|
pathRefsLock sync.RWMutex // create lock for all refs maps, we want to build data as fast as we can
|
||||||
|
operationLock sync.Mutex // create lock for operations
|
||||||
externalDocumentsCount int // number of externalDocument nodes found
|
externalDocumentsCount int // number of externalDocument nodes found
|
||||||
operationTagsCount int // number of unique tags in operations
|
operationTagsCount int // number of unique tags in operations
|
||||||
globalTagsCount int // number of global tags defined
|
globalTagsCount int // number of global tags defined
|
||||||
@@ -248,12 +281,17 @@ type SpecIndex struct {
|
|||||||
componentIndexChan chan bool
|
componentIndexChan chan bool
|
||||||
polyComponentIndexChan chan bool
|
polyComponentIndexChan chan bool
|
||||||
|
|
||||||
// when things get complex (looking at you digital ocean) then we need to know
|
specAbsolutePath string
|
||||||
// what we have seen across indexes, so we need to be able to travel back up to the root
|
resolver *Resolver
|
||||||
// cto avoid re-downloading sources.
|
|
||||||
parentIndex *SpecIndex
|
//parentIndex *SpecIndex
|
||||||
uri []string
|
uri []string
|
||||||
children []*SpecIndex
|
//children []*SpecIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResolver returns the resolver for this index.
|
||||||
|
func (index *SpecIndex) GetResolver() *Resolver {
|
||||||
|
return index.resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig returns the SpecIndexConfig for this index.
|
// GetConfig returns the SpecIndexConfig for this index.
|
||||||
@@ -261,15 +299,15 @@ func (index *SpecIndex) GetConfig() *SpecIndexConfig {
|
|||||||
return index.config
|
return index.config
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddChild adds a child index to this index, a child index is an index created from a remote or file reference.
|
//// AddChild adds a child index to this index, a child index is an index created from a remote or file reference.
|
||||||
func (index *SpecIndex) AddChild(child *SpecIndex) {
|
//func (index *SpecIndex) AddChild(child *SpecIndex) {
|
||||||
index.children = append(index.children, child)
|
// index.children = append(index.children, child)
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// GetChildren returns the children of this index.
|
//// GetChildren returns the children of this index.
|
||||||
func (index *SpecIndex) GetChildren() []*SpecIndex {
|
//func (index *SpecIndex) GetChildren() []*SpecIndex {
|
||||||
return index.children
|
// return index.children
|
||||||
}
|
//}
|
||||||
|
|
||||||
// ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the
|
// ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the
|
||||||
// URI based document. Decides if the reference is local, remote or in a file.
|
// URI based document. Decides if the reference is local, remote or in a file.
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSpecIndex_Children(t *testing.T) {
|
//func TestSpecIndex_Children(t *testing.T) {
|
||||||
idx1 := new(SpecIndex)
|
// idx1 := new(SpecIndex)
|
||||||
idx2 := new(SpecIndex)
|
// idx2 := new(SpecIndex)
|
||||||
idx3 := new(SpecIndex)
|
// idx3 := new(SpecIndex)
|
||||||
idx4 := new(SpecIndex)
|
// idx4 := new(SpecIndex)
|
||||||
idx5 := new(SpecIndex)
|
// idx5 := new(SpecIndex)
|
||||||
idx1.AddChild(idx2)
|
// idx1.AddChild(idx2)
|
||||||
idx1.AddChild(idx3)
|
// idx1.AddChild(idx3)
|
||||||
idx3.AddChild(idx4)
|
// idx3.AddChild(idx4)
|
||||||
idx4.AddChild(idx5)
|
// idx4.AddChild(idx5)
|
||||||
assert.Equal(t, 2, len(idx1.GetChildren()))
|
// assert.Equal(t, 2, len(idx1.GetChildren()))
|
||||||
assert.Equal(t, 1, len(idx3.GetChildren()))
|
// assert.Equal(t, 1, len(idx3.GetChildren()))
|
||||||
assert.Equal(t, 1, len(idx4.GetChildren()))
|
// assert.Equal(t, 1, len(idx4.GetChildren()))
|
||||||
assert.Equal(t, 0, len(idx5.GetChildren()))
|
// assert.Equal(t, 0, len(idx5.GetChildren()))
|
||||||
}
|
//}
|
||||||
|
|
||||||
func TestSpecIndex_GetConfig(t *testing.T) {
|
func TestSpecIndex_GetConfig(t *testing.T) {
|
||||||
idx1 := new(SpecIndex)
|
idx1 := new(SpecIndex)
|
||||||
|
|||||||
@@ -4,11 +4,9 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"github.com/pb33f/libopenapi/utils"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResolvingError represents an issue the resolver had trying to stitch the tree together.
|
// ResolvingError represents an issue the resolver had trying to stitch the tree together.
|
||||||
@@ -259,7 +257,7 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j
|
|||||||
}
|
}
|
||||||
|
|
||||||
journey = append(journey, ref)
|
journey = append(journey, ref)
|
||||||
relatives := resolver.extractRelatives(ref.Node, nil, seen, journey, resolve)
|
relatives := resolver.extractRelatives(ref, ref.Node, nil, seen, journey, resolve)
|
||||||
|
|
||||||
seen = make(map[string]bool)
|
seen = make(map[string]bool)
|
||||||
|
|
||||||
@@ -362,7 +360,7 @@ func (resolver *Resolver) isInfiniteCircularDependency(ref *Reference, visitedDe
|
|||||||
return false, visitedDefinitions
|
return false, visitedDefinitions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) extractRelatives(node, parent *yaml.Node,
|
func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.Node,
|
||||||
foundRelatives map[string]bool,
|
foundRelatives map[string]bool,
|
||||||
journey []*Reference, resolve bool) []*Reference {
|
journey []*Reference, resolve bool) []*Reference {
|
||||||
|
|
||||||
@@ -400,7 +398,7 @@ func (resolver *Resolver) extractRelatives(node, parent *yaml.Node,
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
found = append(found, resolver.extractRelatives(n, node, foundRelatives, journey, resolve)...)
|
found = append(found, resolver.extractRelatives(ref, n, node, foundRelatives, journey, resolve)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if i%2 == 0 && n.Value == "$ref" {
|
if i%2 == 0 && n.Value == "$ref" {
|
||||||
@@ -411,9 +409,9 @@ func (resolver *Resolver) extractRelatives(node, parent *yaml.Node,
|
|||||||
|
|
||||||
value := node.Content[i+1].Value
|
value := node.Content[i+1].Value
|
||||||
|
|
||||||
ref := resolver.specIndex.SearchIndexForReference(value)
|
locatedRef := resolver.specIndex.SearchIndexForReference(value)
|
||||||
|
|
||||||
if ref == nil {
|
if locatedRef == nil {
|
||||||
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
||||||
err := &ResolvingError{
|
err := &ResolvingError{
|
||||||
ErrorRef: fmt.Errorf("cannot resolve reference `%s`, it's missing", value),
|
ErrorRef: fmt.Errorf("cannot resolve reference `%s`, it's missing", value),
|
||||||
@@ -434,15 +432,9 @@ func (resolver *Resolver) extractRelatives(node, parent *yaml.Node,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Reference{
|
locatedRef[0].ParentNodeSchemaType = schemaType
|
||||||
Definition: value,
|
|
||||||
Name: value,
|
|
||||||
Node: node,
|
|
||||||
ParentNode: parent,
|
|
||||||
ParentNodeSchemaType: schemaType,
|
|
||||||
}
|
|
||||||
|
|
||||||
found = append(found, r)
|
found = append(found, locatedRef[0])
|
||||||
|
|
||||||
foundRelatives[value] = true
|
foundRelatives[value] = true
|
||||||
}
|
}
|
||||||
@@ -459,30 +451,30 @@ func (resolver *Resolver) extractRelatives(node, parent *yaml.Node,
|
|||||||
if _, v := utils.FindKeyNodeTop("items", node.Content[i+1].Content); v != nil {
|
if _, v := utils.FindKeyNodeTop("items", node.Content[i+1].Content); v != nil {
|
||||||
if utils.IsNodeMap(v) {
|
if utils.IsNodeMap(v) {
|
||||||
if d, _, l := utils.IsNodeRefValue(v); d {
|
if d, _, l := utils.IsNodeRefValue(v); d {
|
||||||
ref := resolver.specIndex.GetMappedReferences()[l]
|
mappedRefs := resolver.specIndex.GetMappedReferences()[l]
|
||||||
if ref != nil && !ref.Circular {
|
if mappedRefs != nil && !mappedRefs.Circular {
|
||||||
circ := false
|
circ := false
|
||||||
for f := range journey {
|
for f := range journey {
|
||||||
if journey[f].Definition == ref.Definition {
|
if journey[f].Definition == mappedRefs.Definition {
|
||||||
circ = true
|
circ = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !circ {
|
if !circ {
|
||||||
resolver.VisitReference(ref, foundRelatives, journey, resolve)
|
resolver.VisitReference(mappedRefs, foundRelatives, journey, resolve)
|
||||||
} else {
|
} else {
|
||||||
loop := append(journey, ref)
|
loop := append(journey, mappedRefs)
|
||||||
circRef := &CircularReferenceResult{
|
circRef := &CircularReferenceResult{
|
||||||
Journey: loop,
|
Journey: loop,
|
||||||
Start: ref,
|
Start: mappedRefs,
|
||||||
LoopIndex: i,
|
LoopIndex: i,
|
||||||
LoopPoint: ref,
|
LoopPoint: mappedRefs,
|
||||||
PolymorphicType: n.Value,
|
PolymorphicType: n.Value,
|
||||||
IsPolymorphicResult: true,
|
IsPolymorphicResult: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.Seen = true
|
mappedRefs.Seen = true
|
||||||
ref.Circular = true
|
mappedRefs.Circular = true
|
||||||
if resolver.IgnorePoly {
|
if resolver.IgnorePoly {
|
||||||
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
||||||
} else {
|
} else {
|
||||||
@@ -501,36 +493,31 @@ func (resolver *Resolver) extractRelatives(node, parent *yaml.Node,
|
|||||||
v := node.Content[i+1].Content[q]
|
v := node.Content[i+1].Content[q]
|
||||||
if utils.IsNodeMap(v) {
|
if utils.IsNodeMap(v) {
|
||||||
if d, _, l := utils.IsNodeRefValue(v); d {
|
if d, _, l := utils.IsNodeRefValue(v); d {
|
||||||
strangs := strings.Split(l, "/#")
|
mappedRefs := resolver.specIndex.GetMappedReferences()[l]
|
||||||
if len(strangs) == 2 {
|
if mappedRefs != nil && !mappedRefs.Circular {
|
||||||
fmt.Println("wank")
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := resolver.specIndex.GetMappedReferences()[l]
|
|
||||||
if ref != nil && !ref.Circular {
|
|
||||||
circ := false
|
circ := false
|
||||||
for f := range journey {
|
for f := range journey {
|
||||||
if journey[f].Definition == ref.Definition {
|
if journey[f].Definition == mappedRefs.Definition {
|
||||||
circ = true
|
circ = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !circ {
|
if !circ {
|
||||||
resolver.VisitReference(ref, foundRelatives, journey, resolve)
|
resolver.VisitReference(mappedRefs, foundRelatives, journey, resolve)
|
||||||
} else {
|
} else {
|
||||||
loop := append(journey, ref)
|
loop := append(journey, mappedRefs)
|
||||||
|
|
||||||
circRef := &CircularReferenceResult{
|
circRef := &CircularReferenceResult{
|
||||||
Journey: loop,
|
Journey: loop,
|
||||||
Start: ref,
|
Start: mappedRefs,
|
||||||
LoopIndex: i,
|
LoopIndex: i,
|
||||||
LoopPoint: ref,
|
LoopPoint: mappedRefs,
|
||||||
PolymorphicType: n.Value,
|
PolymorphicType: n.Value,
|
||||||
IsPolymorphicResult: true,
|
IsPolymorphicResult: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.Seen = true
|
mappedRefs.Seen = true
|
||||||
ref.Circular = true
|
mappedRefs.Circular = true
|
||||||
if resolver.IgnorePoly {
|
if resolver.IgnorePoly {
|
||||||
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -51,17 +51,18 @@ type RolodexFS interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Rolodex struct {
|
type Rolodex struct {
|
||||||
localFS map[string]fs.FS
|
localFS map[string]fs.FS
|
||||||
remoteFS map[string]fs.FS
|
remoteFS map[string]fs.FS
|
||||||
indexed bool
|
indexed bool
|
||||||
built bool
|
built bool
|
||||||
resolved bool
|
resolved bool
|
||||||
circChecked bool
|
circChecked bool
|
||||||
indexConfig *SpecIndexConfig
|
indexConfig *SpecIndexConfig
|
||||||
indexingDuration time.Duration
|
indexingDuration time.Duration
|
||||||
indexes []*SpecIndex
|
indexes []*SpecIndex
|
||||||
rootIndex *SpecIndex
|
rootIndex *SpecIndex
|
||||||
caughtErrors []error
|
caughtErrors []error
|
||||||
|
ignoredCircularReferences []*CircularReferenceResult
|
||||||
}
|
}
|
||||||
|
|
||||||
type rolodexFile struct {
|
type rolodexFile struct {
|
||||||
@@ -209,6 +210,10 @@ func NewRolodex(indexConfig *SpecIndexConfig) *Rolodex {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Rolodex) GetIgnoredCircularReferences() []*CircularReferenceResult {
|
||||||
|
return r.ignoredCircularReferences
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Rolodex) GetIndexingDuration() time.Duration {
|
func (r *Rolodex) GetIndexingDuration() time.Duration {
|
||||||
return r.indexingDuration
|
return r.indexingDuration
|
||||||
}
|
}
|
||||||
@@ -275,10 +280,12 @@ func (r *Rolodex) IndexTheRolodex() error {
|
|||||||
if copiedConfig.IgnorePolymorphicCircularReferences {
|
if copiedConfig.IgnorePolymorphicCircularReferences {
|
||||||
resolver.IgnorePolymorphicCircularReferences()
|
resolver.IgnorePolymorphicCircularReferences()
|
||||||
}
|
}
|
||||||
resolvingErrors := resolver.CheckForCircularReferences()
|
//if !copiedConfig.AvoidCircularReferenceCheck {
|
||||||
for e := range resolvingErrors {
|
// resolvingErrors := resolver.CheckForCircularReferences()
|
||||||
caughtErrors = append(caughtErrors, resolvingErrors[e])
|
// for e := range resolvingErrors {
|
||||||
}
|
// caughtErrors = append(caughtErrors, resolvingErrors[e])
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
@@ -331,6 +338,13 @@ func (r *Rolodex) IndexTheRolodex() error {
|
|||||||
if !r.indexConfig.AvoidBuildIndex {
|
if !r.indexConfig.AvoidBuildIndex {
|
||||||
for _, idx := range indexBuildQueue {
|
for _, idx := range indexBuildQueue {
|
||||||
idx.BuildIndex()
|
idx.BuildIndex()
|
||||||
|
if r.indexConfig.AvoidCircularReferenceCheck {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errs := idx.resolver.CheckForCircularReferences()
|
||||||
|
for e := range errs {
|
||||||
|
caughtErrors = append(caughtErrors, errs[e])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +383,27 @@ func (r *Rolodex) CheckForCircularReferences() {
|
|||||||
for e := range resolvingErrors {
|
for e := range resolvingErrors {
|
||||||
r.caughtErrors = append(r.caughtErrors, resolvingErrors[e])
|
r.caughtErrors = append(r.caughtErrors, resolvingErrors[e])
|
||||||
}
|
}
|
||||||
|
if len(r.rootIndex.resolver.ignoredPolyReferences) > 0 {
|
||||||
|
r.ignoredCircularReferences = append(r.ignoredCircularReferences, r.rootIndex.resolver.ignoredPolyReferences...)
|
||||||
|
}
|
||||||
|
if len(r.rootIndex.resolver.ignoredArrayReferences) > 0 {
|
||||||
|
r.ignoredCircularReferences = append(r.ignoredCircularReferences, r.rootIndex.resolver.ignoredArrayReferences...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rolodex) Resolve() {
|
||||||
|
if r.rootIndex != nil && r.rootIndex.resolver != nil {
|
||||||
|
resolvingErrors := r.rootIndex.resolver.Resolve()
|
||||||
|
for e := range resolvingErrors {
|
||||||
|
r.caughtErrors = append(r.caughtErrors, resolvingErrors[e])
|
||||||
|
}
|
||||||
|
if len(r.rootIndex.resolver.ignoredPolyReferences) > 0 {
|
||||||
|
r.ignoredCircularReferences = append(r.ignoredCircularReferences, r.rootIndex.resolver.ignoredPolyReferences...)
|
||||||
|
}
|
||||||
|
if len(r.rootIndex.resolver.ignoredArrayReferences) > 0 {
|
||||||
|
r.ignoredCircularReferences = append(r.ignoredCircularReferences, r.rootIndex.resolver.ignoredArrayReferences...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,39 @@
|
|||||||
|
|
||||||
package index
|
package index
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// SearchIndexForReference searches the index for a reference, first looking through the mapped references
|
// SearchIndexForReference searches the index for a reference, first looking through the mapped references
|
||||||
// and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes
|
// and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes
|
||||||
// extracted when parsing the OpenAPI Spec.
|
// extracted when parsing the OpenAPI Spec.
|
||||||
func (index *SpecIndex) SearchIndexForReference(ref string) []*Reference {
|
func (index *SpecIndex) SearchIndexForReference(ref string) []*Reference {
|
||||||
|
|
||||||
|
absPath := index.specAbsolutePath
|
||||||
|
var roloLookup string
|
||||||
|
uri := strings.Split(ref, "#/")
|
||||||
|
if len(uri) == 2 {
|
||||||
|
if uri[0] != "" {
|
||||||
|
roloLookup, _ = filepath.Abs(filepath.Join(absPath, uri[0]))
|
||||||
|
}
|
||||||
|
ref = fmt.Sprintf("#/%s", uri[1])
|
||||||
|
}
|
||||||
|
|
||||||
if r, ok := index.allMappedRefs[ref]; ok {
|
if r, ok := index.allMappedRefs[ref]; ok {
|
||||||
return []*Reference{r}
|
return []*Reference{r}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: look in the rolodex.
|
// TODO: look in the rolodex.
|
||||||
|
|
||||||
|
panic("should not be here")
|
||||||
|
fmt.Println(roloLookup)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
//if r, ok := index.allMappedRefs[ref]; ok {
|
//if r, ok := index.allMappedRefs[ref]; ok {
|
||||||
// return []*Reference{r}
|
// return []*Reference{r}jh
|
||||||
//}
|
//}
|
||||||
//for c := range index.children {
|
//for c := range index.children {
|
||||||
// found := goFindMeSomething(index.children[c], ref)
|
// found := goFindMeSomething(index.children[c], ref)
|
||||||
|
|||||||
Reference in New Issue
Block a user