mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-07 04:20:14 +00:00
sibling nodes with references were being destroyed when resolved. Copies are now made instead of using pointers for this specific item, as the fidelity is lost when using the resolver (the refs are resolved and there is no way to re-locate the original positon of the reference.
1921 lines
74 KiB
Go
1921 lines
74 KiB
Go
// Copyright 2022 Dave Shanley / Quobix
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package index
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/pb33f/libopenapi/utils"
|
|
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
|
|
"gopkg.in/yaml.v3"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
localResolve int = 0
|
|
httpResolve int = 1
|
|
fileResolve int = 2
|
|
)
|
|
|
|
// 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.
|
|
type Reference struct {
|
|
Definition string
|
|
Name string
|
|
Node *yaml.Node
|
|
ParentNode *yaml.Node
|
|
Resolved bool
|
|
Circular bool
|
|
Seen bool
|
|
Path string // this won't always be available.
|
|
}
|
|
|
|
// CircularReferenceResult contains a circular reference found when traversing the graph.
|
|
type CircularReferenceResult struct {
|
|
Journey []*Reference
|
|
JourneyString string
|
|
Start *Reference
|
|
LoopIndex int
|
|
LoopPoint *Reference
|
|
}
|
|
|
|
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
|
|
type ReferenceMapped struct {
|
|
Reference *Reference
|
|
Definition string
|
|
}
|
|
|
|
// SpecIndex is a complete pre-computed index of the entire specification. Numbers are pre-calculated and
|
|
// 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.
|
|
type SpecIndex struct {
|
|
allRefs map[string]*Reference // all (deduplicated) refs
|
|
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
|
|
linesWithRefs map[int]bool // lines that link to references.
|
|
allMappedRefs map[string]*Reference // these are the located mapped refs
|
|
allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs
|
|
refsByLine map[string]map[int]bool // every reference and the lines it's referenced from
|
|
pathRefs map[string]map[string]*Reference // all path references
|
|
paramOpRefs map[string]map[string]map[string]*Reference // params in operations.
|
|
paramCompRefs map[string]*Reference // params in components
|
|
paramAllRefs map[string]*Reference // combined components and ops
|
|
paramInlineDuplicates map[string][]*Reference // inline params all with the same name
|
|
globalTagRefs map[string]*Reference // top level global tags
|
|
securitySchemeRefs map[string]*Reference // top level security schemes
|
|
requestBodiesRefs map[string]*Reference // top level request bodies
|
|
responsesRefs map[string]*Reference // top level responses
|
|
headersRefs map[string]*Reference // top level responses
|
|
examplesRefs map[string]*Reference // top level examples
|
|
linksRefs map[string]map[string][]*Reference // all links
|
|
operationTagsRefs map[string]map[string][]*Reference // tags found in operations
|
|
operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations.
|
|
operationSummaryRefs map[string]map[string]*Reference // summaries in operations
|
|
callbackRefs map[string]*Reference // top level callback refs
|
|
serversRefs []*Reference // all top level server refs
|
|
rootServersNode *yaml.Node // servers root node
|
|
opServersRefs map[string]map[string][]*Reference // all operation level server overrides.
|
|
polymorphicRefs map[string]*Reference // every reference to a polymorphic ref
|
|
polymorphicAllOfRefs []*Reference // every reference to 'allOf' references
|
|
polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references
|
|
polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references
|
|
externalDocumentsRef []*Reference // all external documents in spec
|
|
rootSecurity []*Reference // root security definitions.
|
|
rootSecurityNode *yaml.Node // root security node.
|
|
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
|
|
externalDocumentsCount int // number of externalDocument nodes found
|
|
operationTagsCount int // number of unique tags in operations
|
|
globalTagsCount int // number of global tags defined
|
|
totalTagsCount int // number unique tags in spec
|
|
securitySchemesCount int // security schemes
|
|
globalRequestBodiesCount int // component request bodies
|
|
globalResponsesCount int // component responses
|
|
globalHeadersCount int // component headers
|
|
globalExamplesCount int // component examples
|
|
globalLinksCount int // component links
|
|
globalCallbacks int // component callbacks.
|
|
pathCount int // number of paths
|
|
operationCount int // number of operations
|
|
operationParamCount int // number of params defined in operations
|
|
componentParamCount int // number of params defined in components
|
|
componentsInlineParamUniqueCount int // number of inline params with unique names
|
|
componentsInlineParamDuplicateCount int // number of inline params with duplicate names
|
|
schemaCount int // number of schemas
|
|
refCount int // total ref count
|
|
root *yaml.Node // the root document
|
|
pathsNode *yaml.Node // paths node
|
|
tagsNode *yaml.Node // tags node
|
|
componentsNode *yaml.Node // components node
|
|
parametersNode *yaml.Node // components/parameters node
|
|
allParametersNode map[string]*Reference // all parameters node
|
|
allParameters map[string]*Reference // all parameters (components/defs)
|
|
schemasNode *yaml.Node // components/schemas node
|
|
allSchemas map[string]*Reference // all schemas
|
|
securitySchemesNode *yaml.Node // components/securitySchemes node
|
|
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
|
|
requestBodiesNode *yaml.Node // components/requestBodies node
|
|
allRequestBodies map[string]*Reference // all request bodies
|
|
responsesNode *yaml.Node // components/responses node
|
|
allResponses map[string]*Reference // all responses
|
|
headersNode *yaml.Node // components/headers node
|
|
allHeaders map[string]*Reference // all headers
|
|
examplesNode *yaml.Node // components/examples node
|
|
allExamples map[string]*Reference // all components examples
|
|
linksNode *yaml.Node // components/links node
|
|
allLinks map[string]*Reference // all links
|
|
callbacksNode *yaml.Node // components/callbacks node
|
|
allCallbacks map[string]*Reference // all components examples
|
|
externalDocumentsNode *yaml.Node // external documents node
|
|
allExternalDocuments map[string]*Reference // all external documents
|
|
externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds
|
|
refErrors []*IndexingError // errors when indexing references
|
|
operationParamErrors []*IndexingError // errors when indexing parameters
|
|
allDescriptions []*DescriptionReference // every single description found in the spec.
|
|
allSummaries []*DescriptionReference // every single summary found in the spec.
|
|
allEnums []*EnumReference // every single enum found in the spec.
|
|
enumCount int
|
|
descriptionCount int
|
|
summaryCount int
|
|
seenRemoteSources map[string]*yaml.Node
|
|
remoteLock sync.Mutex
|
|
}
|
|
|
|
// 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.
|
|
type ExternalLookupFunction func(id string) (foundNode *yaml.Node, rootNode *yaml.Node, lookupError error)
|
|
|
|
// IndexingError holds data about something that went wrong during indexing.
|
|
type IndexingError struct {
|
|
Error error
|
|
Node *yaml.Node
|
|
Path string
|
|
}
|
|
|
|
// DescriptionReference holds data about a description that was found and where it was found.
|
|
type DescriptionReference struct {
|
|
Content string
|
|
Path string
|
|
Node *yaml.Node
|
|
IsSummary bool
|
|
}
|
|
|
|
type EnumReference struct {
|
|
Node *yaml.Node
|
|
Type *yaml.Node
|
|
Path string
|
|
}
|
|
|
|
var methodTypes = []string{"get", "post", "put", "patch", "options", "head", "delete"}
|
|
|
|
func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) {
|
|
for _, cFunc := range funcs {
|
|
go func(wg *sync.WaitGroup, cf func() int) {
|
|
cf()
|
|
wg.Done()
|
|
}(wg, cFunc)
|
|
}
|
|
}
|
|
|
|
// NewSpecIndex will create a new index of an OpenAPI or Swagger spec. It's not resolved or converted into anything
|
|
// other than a raw index of every node for every content type in the specification. This process runs as fast as
|
|
// possible so dependencies looking through the tree, don't need to walk the entire thing over, and over.
|
|
func NewSpecIndex(rootNode *yaml.Node) *SpecIndex {
|
|
|
|
index := new(SpecIndex)
|
|
index.root = rootNode
|
|
index.allRefs = make(map[string]*Reference)
|
|
index.allMappedRefs = make(map[string]*Reference)
|
|
index.refsByLine = make(map[string]map[int]bool)
|
|
index.linesWithRefs = make(map[int]bool)
|
|
index.pathRefs = make(map[string]map[string]*Reference)
|
|
index.paramOpRefs = make(map[string]map[string]map[string]*Reference)
|
|
index.operationTagsRefs = make(map[string]map[string][]*Reference)
|
|
index.operationDescriptionRefs = make(map[string]map[string]*Reference)
|
|
index.operationSummaryRefs = make(map[string]map[string]*Reference)
|
|
index.paramCompRefs = make(map[string]*Reference)
|
|
index.paramAllRefs = make(map[string]*Reference)
|
|
index.paramInlineDuplicates = make(map[string][]*Reference)
|
|
index.globalTagRefs = make(map[string]*Reference)
|
|
index.securitySchemeRefs = make(map[string]*Reference)
|
|
index.requestBodiesRefs = make(map[string]*Reference)
|
|
index.responsesRefs = make(map[string]*Reference)
|
|
index.headersRefs = make(map[string]*Reference)
|
|
index.examplesRefs = make(map[string]*Reference)
|
|
index.linksRefs = make(map[string]map[string][]*Reference)
|
|
index.callbackRefs = make(map[string]*Reference)
|
|
index.externalSpecIndex = make(map[string]*SpecIndex)
|
|
index.allSchemas = make(map[string]*Reference)
|
|
index.allParameters = make(map[string]*Reference)
|
|
index.allSecuritySchemes = make(map[string]*Reference)
|
|
index.allRequestBodies = make(map[string]*Reference)
|
|
index.allResponses = make(map[string]*Reference)
|
|
index.allHeaders = make(map[string]*Reference)
|
|
index.allExamples = make(map[string]*Reference)
|
|
index.allLinks = make(map[string]*Reference)
|
|
index.allCallbacks = make(map[string]*Reference)
|
|
index.allExternalDocuments = make(map[string]*Reference)
|
|
index.polymorphicRefs = make(map[string]*Reference)
|
|
index.refsWithSiblings = make(map[string]Reference)
|
|
index.seenRemoteSources = make(map[string]*yaml.Node)
|
|
index.opServersRefs = make(map[string]map[string][]*Reference)
|
|
|
|
// there is no node! return an empty index.
|
|
if rootNode == nil {
|
|
return index
|
|
}
|
|
|
|
// boot index.
|
|
results := index.ExtractRefs(index.root.Content[0], index.root, []string{}, 0, false, "")
|
|
|
|
// pull out references
|
|
index.ExtractComponentsFromRefs(results)
|
|
index.ExtractExternalDocuments(index.root)
|
|
index.GetPathCount()
|
|
|
|
countFuncs := []func() int{
|
|
index.GetOperationCount,
|
|
index.GetComponentSchemaCount,
|
|
index.GetGlobalTagsCount,
|
|
index.GetComponentParameterCount,
|
|
index.GetOperationsParameterCount,
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(countFuncs))
|
|
runIndexFunction(countFuncs, &wg) // run as fast as we can.
|
|
wg.Wait()
|
|
|
|
// these functions are aggregate and can only run once the rest of the datamodel is ready
|
|
countFuncs = []func() int{
|
|
index.GetInlineUniqueParamCount,
|
|
index.GetOperationTagsCount,
|
|
index.GetGlobalLinksCount,
|
|
}
|
|
|
|
wg.Add(len(countFuncs))
|
|
runIndexFunction(countFuncs, &wg) // run as fast as we can.
|
|
wg.Wait()
|
|
|
|
// these have final calculation dependencies
|
|
index.GetInlineDuplicateParamCount()
|
|
index.GetAllDescriptionsCount()
|
|
index.GetTotalTagsCount()
|
|
|
|
return index
|
|
}
|
|
|
|
// GetRootNode returns document root node.
|
|
func (index *SpecIndex) GetRootNode() *yaml.Node {
|
|
return index.root
|
|
}
|
|
|
|
// GetGlobalTagsNode returns document root node.
|
|
func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node {
|
|
return index.tagsNode
|
|
}
|
|
|
|
// GetPathsNode returns document root node.
|
|
func (index *SpecIndex) GetPathsNode() *yaml.Node {
|
|
return index.pathsNode
|
|
}
|
|
|
|
// GetDiscoveredReferences will return all unique references found in the spec
|
|
func (index *SpecIndex) GetDiscoveredReferences() map[string]*Reference {
|
|
return index.allRefs
|
|
}
|
|
|
|
// GetPolyReferences will return every polymorphic reference in the doc
|
|
func (index *SpecIndex) GetPolyReferences() map[string]*Reference {
|
|
return index.polymorphicRefs
|
|
}
|
|
|
|
// GetPolyAllOfReferences will return every 'allOf' polymorphic reference in the doc
|
|
func (index *SpecIndex) GetPolyAllOfReferences() []*Reference {
|
|
return index.polymorphicAllOfRefs
|
|
}
|
|
|
|
// GetPolyAnyOfReferences will return every 'anyOf' polymorphic reference in the doc
|
|
func (index *SpecIndex) GetPolyAnyOfReferences() []*Reference {
|
|
return index.polymorphicAnyOfRefs
|
|
}
|
|
|
|
// GetPolyOneOfReferences will return every 'allOf' polymorphic reference in the doc
|
|
func (index *SpecIndex) GetPolyOneOfReferences() []*Reference {
|
|
return index.polymorphicOneOfRefs
|
|
}
|
|
|
|
// GetAllCombinedReferences will return the number of unique and polymorphic references discovered.
|
|
func (index *SpecIndex) GetAllCombinedReferences() map[string]*Reference {
|
|
combined := make(map[string]*Reference)
|
|
for k, ref := range index.allRefs {
|
|
combined[k] = ref
|
|
}
|
|
for k, ref := range index.polymorphicRefs {
|
|
combined[k] = ref
|
|
}
|
|
return combined
|
|
}
|
|
|
|
// GetRefsByLine will return all references and the lines at which they were found.
|
|
func (index *SpecIndex) GetRefsByLine() map[string]map[int]bool {
|
|
return index.refsByLine
|
|
}
|
|
|
|
// GetLinesWithReferences will return a map of lines that have a $ref
|
|
func (index *SpecIndex) GetLinesWithReferences() map[int]bool {
|
|
return index.linesWithRefs
|
|
}
|
|
|
|
// GetMappedReferences will return all references that were mapped successfully to actual property nodes.
|
|
// this collection is completely unsorted, traversing it may produce random results when resolving it and
|
|
// encountering circular references can change results depending on where in the collection the resolver started
|
|
// its journey through the index.
|
|
func (index *SpecIndex) GetMappedReferences() map[string]*Reference {
|
|
return index.allMappedRefs
|
|
}
|
|
|
|
// GetMappedReferencesSequenced will return all references that were mapped successfully to nodes, performed in sequence
|
|
// as they were read in from the document.
|
|
func (index *SpecIndex) GetMappedReferencesSequenced() []*ReferenceMapped {
|
|
return index.allMappedRefsSequenced
|
|
}
|
|
|
|
// GetOperationParameterReferences will return all references to operation parameters
|
|
func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string]map[string]*Reference {
|
|
return index.paramOpRefs
|
|
}
|
|
|
|
// GetAllSchemas will return all schemas found in the document
|
|
func (index *SpecIndex) GetAllSchemas() map[string]*Reference {
|
|
return index.allSchemas
|
|
}
|
|
|
|
// GetAllSecuritySchemes will return all security schemes / definitions found in the document.
|
|
func (index *SpecIndex) GetAllSecuritySchemes() map[string]*Reference {
|
|
return index.allSecuritySchemes
|
|
}
|
|
|
|
// GetAllHeaders will return all headers found in the document (under components)
|
|
func (index *SpecIndex) GetAllHeaders() map[string]*Reference {
|
|
return index.allHeaders
|
|
}
|
|
|
|
// GetAllExternalDocuments will return all external documents found
|
|
func (index *SpecIndex) GetAllExternalDocuments() map[string]*Reference {
|
|
return index.allExternalDocuments
|
|
}
|
|
|
|
// GetAllExamples will return all examples found in the document (under components)
|
|
func (index *SpecIndex) GetAllExamples() map[string]*Reference {
|
|
return index.allExamples
|
|
}
|
|
|
|
// GetAllDescriptions will return all descriptions found in the document
|
|
func (index *SpecIndex) GetAllDescriptions() []*DescriptionReference {
|
|
return index.allDescriptions
|
|
}
|
|
|
|
// GetAllEnums will return all enums found in the document
|
|
func (index *SpecIndex) GetAllEnums() []*EnumReference {
|
|
return index.allEnums
|
|
}
|
|
|
|
// GetAllSummaries will return all summaries found in the document
|
|
func (index *SpecIndex) GetAllSummaries() []*DescriptionReference {
|
|
return index.allSummaries
|
|
}
|
|
|
|
// GetAllRequestBodies will return all requestBodies found in the document (under components)
|
|
func (index *SpecIndex) GetAllRequestBodies() map[string]*Reference {
|
|
return index.allRequestBodies
|
|
}
|
|
|
|
// GetAllLinks will return all links found in the document (under components)
|
|
func (index *SpecIndex) GetAllLinks() map[string]*Reference {
|
|
return index.allLinks
|
|
}
|
|
|
|
// GetAllParameters will return all parameters found in the document (under components)
|
|
func (index *SpecIndex) GetAllParameters() map[string]*Reference {
|
|
return index.allParameters
|
|
}
|
|
|
|
// GetAllResponses will return all responses found in the document (under components)
|
|
func (index *SpecIndex) GetAllResponses() map[string]*Reference {
|
|
return index.allResponses
|
|
}
|
|
|
|
// GetAllCallbacks will return all links found in the document (under components)
|
|
func (index *SpecIndex) GetAllCallbacks() map[string]*Reference {
|
|
return index.allCallbacks
|
|
}
|
|
|
|
// GetInlineOperationDuplicateParameters will return a map of duplicates located in operation parameters.
|
|
func (index *SpecIndex) GetInlineOperationDuplicateParameters() map[string][]*Reference {
|
|
return index.paramInlineDuplicates
|
|
}
|
|
|
|
// GetReferencesWithSiblings will return a map of all the references with sibling nodes (illegal)
|
|
func (index *SpecIndex) GetReferencesWithSiblings() map[string]Reference {
|
|
return index.refsWithSiblings
|
|
}
|
|
|
|
// GetAllReferences will return every reference found in the spec, after being de-duplicated.
|
|
func (index *SpecIndex) GetAllReferences() map[string]*Reference {
|
|
return index.allRefs
|
|
}
|
|
|
|
// GetAllSequencedReferences will return every reference (in sequence) that was found (non-polymorphic)
|
|
func (index *SpecIndex) GetAllSequencedReferences() []*Reference {
|
|
return index.rawSequencedRefs
|
|
}
|
|
|
|
// GetSchemasNode will return the schema's node found in the spec
|
|
func (index *SpecIndex) GetSchemasNode() *yaml.Node {
|
|
return index.schemasNode
|
|
}
|
|
|
|
// GetParametersNode will return the schema's node found in the spec
|
|
func (index *SpecIndex) GetParametersNode() *yaml.Node {
|
|
return index.parametersNode
|
|
}
|
|
|
|
// GetOperationParametersIndexErrors any errors that occurred when indexing operation parameters
|
|
func (index *SpecIndex) GetOperationParametersIndexErrors() []*IndexingError {
|
|
return index.operationParamErrors
|
|
}
|
|
|
|
// GetAllPaths will return all paths indexed in the document
|
|
func (index *SpecIndex) GetAllPaths() map[string]map[string]*Reference {
|
|
return index.pathRefs
|
|
}
|
|
|
|
// GetOperationTags will return all references to all tags found in operations.
|
|
func (index *SpecIndex) GetOperationTags() map[string]map[string][]*Reference {
|
|
return index.operationTagsRefs
|
|
}
|
|
|
|
// GetAllParametersFromOperations will return all paths indexed in the document
|
|
func (index *SpecIndex) GetAllParametersFromOperations() map[string]map[string]map[string]*Reference {
|
|
return index.paramOpRefs
|
|
}
|
|
|
|
// GetRootSecurityReferences will return all root security settings
|
|
func (index *SpecIndex) GetRootSecurityReferences() []*Reference {
|
|
return index.rootSecurity
|
|
}
|
|
|
|
// GetRootSecurityNode will return the root security node
|
|
func (index *SpecIndex) GetRootSecurityNode() *yaml.Node {
|
|
return index.rootSecurityNode
|
|
}
|
|
|
|
// GetRootServersNode will return the root servers node
|
|
func (index *SpecIndex) GetRootServersNode() *yaml.Node {
|
|
return index.rootServersNode
|
|
}
|
|
|
|
// GetAllRootServers will return all root servers defined
|
|
func (index *SpecIndex) GetAllRootServers() []*Reference {
|
|
return index.serversRefs
|
|
}
|
|
|
|
// GetAllOperationsServers will return all operation overrides for servers.
|
|
func (index *SpecIndex) GetAllOperationsServers() map[string]map[string][]*Reference {
|
|
return index.opServersRefs
|
|
}
|
|
|
|
func (index *SpecIndex) checkPolymorphicNode(name string) (bool, string) {
|
|
switch name {
|
|
case "anyOf":
|
|
return true, "anyOf"
|
|
case "allOf":
|
|
return true, "allOf"
|
|
case "oneOf":
|
|
return true, "oneOf"
|
|
}
|
|
return false, ""
|
|
}
|
|
|
|
// ExtractRefs will return a deduplicated slice of references for every unique ref found in the document.
|
|
// The total number of refs, will generally be much higher, you can extract those from GetRawReferenceCount()
|
|
func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, level int, poly bool, pName string) []*Reference {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
var found []*Reference
|
|
if len(node.Content) > 0 {
|
|
var prev, polyName string
|
|
for i, n := range node.Content {
|
|
|
|
if utils.IsNodeMap(n) || utils.IsNodeArray(n) {
|
|
level++
|
|
// check if we're using polymorphic values. These tend to create rabbit warrens of circular
|
|
// references if every single link is followed. We don't resolve polymorphic values.
|
|
isPoly, _ := index.checkPolymorphicNode(prev)
|
|
polyName = pName
|
|
if isPoly {
|
|
poly = true
|
|
if prev != "" {
|
|
polyName = prev
|
|
}
|
|
}
|
|
found = append(found, index.ExtractRefs(n, node, seenPath, level, poly, polyName)...)
|
|
}
|
|
|
|
if i%2 == 0 && n.Value == "$ref" {
|
|
|
|
// only look at scalar values, not maps (looking at you k8s)
|
|
if !utils.IsNodeStringValue(node.Content[i+1]) {
|
|
continue
|
|
}
|
|
|
|
index.linesWithRefs[n.Line] = true
|
|
|
|
fp := make([]string, len(seenPath))
|
|
for x, foundPathNode := range seenPath {
|
|
fp[x] = foundPathNode
|
|
}
|
|
|
|
value := node.Content[i+1].Value
|
|
|
|
if strings.Contains(value, "~1") {
|
|
indexError := &IndexingError{
|
|
Error: errors.New("schema reference is contains '~1' win32 truncation and cannot be processed"),
|
|
Node: node.Content[i+1],
|
|
Path: fmt.Sprintf("$.%s", strings.Join(fp, ".")),
|
|
}
|
|
index.refErrors = append(index.refErrors, indexError)
|
|
continue
|
|
}
|
|
|
|
segs := strings.Split(value, "/")
|
|
name := segs[len(segs)-1]
|
|
ref := &Reference{
|
|
Definition: value,
|
|
Name: name,
|
|
Node: node,
|
|
Path: fmt.Sprintf("$.%s", strings.Join(seenPath, ".")),
|
|
}
|
|
|
|
// add to raw sequenced refs
|
|
index.rawSequencedRefs = append(index.rawSequencedRefs, ref)
|
|
|
|
// add ref by line number
|
|
refNameIndex := strings.LastIndex(value, "/")
|
|
refName := value[refNameIndex+1:]
|
|
if len(index.refsByLine[refName]) > 0 {
|
|
index.refsByLine[refName][n.Line] = true
|
|
} else {
|
|
v := make(map[int]bool)
|
|
v[n.Line] = true
|
|
index.refsByLine[refName] = v
|
|
}
|
|
|
|
// if this ref value has any siblings (node.Content is larger than two elements)
|
|
// then add to refs with siblings
|
|
if len(node.Content) > 2 {
|
|
copiedNode := *node
|
|
copied := Reference{
|
|
Definition: ref.Definition,
|
|
Name: ref.Name,
|
|
Node: &copiedNode,
|
|
Path: ref.Path,
|
|
}
|
|
// protect this data using a copy, prevent the resolver from destroying things.
|
|
index.refsWithSiblings[value] = copied
|
|
}
|
|
|
|
// if this is a polymorphic reference, we're going to leave it out
|
|
// allRefs. We don't ever want these resolved, so instead of polluting
|
|
// the timeline, we will keep each poly ref in its own collection for later
|
|
// analysis.
|
|
if poly {
|
|
index.polymorphicRefs[value] = ref
|
|
|
|
// index each type
|
|
switch pName {
|
|
case "anyOf":
|
|
index.polymorphicAnyOfRefs = append(index.polymorphicAnyOfRefs, ref)
|
|
case "allOf":
|
|
index.polymorphicAnyOfRefs = append(index.polymorphicAllOfRefs, ref)
|
|
case "oneOf":
|
|
index.polymorphicOneOfRefs = append(index.polymorphicOneOfRefs, ref)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// check if this is a dupe, if so, skip it, we don't care now.
|
|
if index.allRefs[value] != nil { // seen before, skip.
|
|
continue
|
|
}
|
|
|
|
if value == "" {
|
|
|
|
completedPath := fmt.Sprintf("$.%s", strings.Join(fp, "."))
|
|
|
|
indexError := &IndexingError{
|
|
Error: errors.New("schema reference is empty and cannot be processed"),
|
|
Node: node.Content[i+1],
|
|
Path: completedPath,
|
|
}
|
|
|
|
index.refErrors = append(index.refErrors, indexError)
|
|
|
|
continue
|
|
}
|
|
|
|
index.allRefs[value] = ref
|
|
found = append(found, ref)
|
|
}
|
|
|
|
if i%2 == 0 && n.Value != "$ref" && n.Value != "" {
|
|
|
|
nodePath := fmt.Sprintf("$.%s", strings.Join(seenPath, "."))
|
|
|
|
// capture descriptions and summaries
|
|
if n.Value == "description" {
|
|
|
|
// if the parent is a sequence, ignore.
|
|
if utils.IsNodeArray(node) {
|
|
continue
|
|
}
|
|
|
|
ref := &DescriptionReference{
|
|
Content: node.Content[i+1].Value,
|
|
Path: nodePath,
|
|
Node: node.Content[i+1],
|
|
IsSummary: false,
|
|
}
|
|
|
|
index.allDescriptions = append(index.allDescriptions, ref)
|
|
index.descriptionCount++
|
|
}
|
|
|
|
if n.Value == "summary" {
|
|
|
|
var b *yaml.Node
|
|
|
|
if len(node.Content) == i+1 {
|
|
b = node.Content[i]
|
|
} else {
|
|
b = node.Content[i+1]
|
|
}
|
|
|
|
ref := &DescriptionReference{
|
|
Content: b.Value,
|
|
Path: nodePath,
|
|
Node: b,
|
|
IsSummary: true,
|
|
}
|
|
|
|
index.allSummaries = append(index.allSummaries, ref)
|
|
index.summaryCount++
|
|
}
|
|
|
|
// capture enums
|
|
if n.Value == "enum" {
|
|
|
|
// all enums need to have a type, extract the type from the node where the enum was found.
|
|
_, enumKeyValueNode := utils.FindKeyNode("type", node.Content)
|
|
|
|
if enumKeyValueNode != nil {
|
|
ref := &EnumReference{
|
|
Path: nodePath,
|
|
Node: node.Content[i+1],
|
|
Type: enumKeyValueNode,
|
|
}
|
|
|
|
index.allEnums = append(index.allEnums, ref)
|
|
index.enumCount++
|
|
}
|
|
}
|
|
|
|
seenPath = append(seenPath, n.Value)
|
|
prev = n.Value
|
|
}
|
|
|
|
// if next node is map, don't add segment.
|
|
if i < len(node.Content)-1 {
|
|
next := node.Content[i+1]
|
|
|
|
if i%2 != 0 && next != nil && !utils.IsNodeArray(next) && !utils.IsNodeMap(next) {
|
|
seenPath = seenPath[:len(seenPath)-1]
|
|
}
|
|
}
|
|
}
|
|
if len(seenPath) > 0 {
|
|
seenPath = seenPath[:len(seenPath)-1]
|
|
}
|
|
|
|
}
|
|
if len(seenPath) > 0 {
|
|
seenPath = seenPath[:len(seenPath)-1]
|
|
}
|
|
|
|
index.refCount = len(index.allRefs)
|
|
|
|
return found
|
|
}
|
|
|
|
// GetPathCount will return the number of paths found in the spec
|
|
func (index *SpecIndex) GetPathCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.pathCount > 0 {
|
|
return index.pathCount
|
|
}
|
|
pc := 0
|
|
for i, n := range index.root.Content[0].Content {
|
|
if i%2 == 0 {
|
|
if n.Value == "paths" {
|
|
pn := index.root.Content[0].Content[i+1].Content
|
|
index.pathsNode = index.root.Content[0].Content[i+1]
|
|
pc = len(pn) / 2
|
|
}
|
|
}
|
|
}
|
|
index.pathCount = pc
|
|
return pc
|
|
}
|
|
|
|
// ExtractExternalDocuments will extract the number of externalDocs nodes found in the document.
|
|
func (index *SpecIndex) ExtractExternalDocuments(node *yaml.Node) []*Reference {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
var found []*Reference
|
|
if len(node.Content) > 0 {
|
|
for i, n := range node.Content {
|
|
if utils.IsNodeMap(n) || utils.IsNodeArray(n) {
|
|
found = append(found, index.ExtractExternalDocuments(n)...)
|
|
}
|
|
|
|
if i%2 == 0 && n.Value == "externalDocs" {
|
|
docNode := node.Content[i+1]
|
|
_, urlNode := utils.FindKeyNode("url", docNode.Content)
|
|
if urlNode != nil {
|
|
ref := &Reference{
|
|
Definition: urlNode.Value,
|
|
Name: urlNode.Value,
|
|
Node: docNode,
|
|
}
|
|
index.externalDocumentsRef = append(index.externalDocumentsRef, ref)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
index.externalDocumentsCount = len(index.externalDocumentsRef)
|
|
return found
|
|
}
|
|
|
|
// GetGlobalTagsCount will return the number of tags found in the top level 'tags' node of the document.
|
|
func (index *SpecIndex) GetGlobalTagsCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.globalTagsCount > 0 {
|
|
return index.globalTagsCount
|
|
}
|
|
|
|
for i, n := range index.root.Content[0].Content {
|
|
if i%2 == 0 {
|
|
if n.Value == "tags" {
|
|
tagsNode := index.root.Content[0].Content[i+1]
|
|
if tagsNode != nil {
|
|
index.tagsNode = tagsNode
|
|
index.globalTagsCount = len(tagsNode.Content) // tags is an array, don't divide by 2.
|
|
for x, tagNode := range index.tagsNode.Content {
|
|
|
|
_, name := utils.FindKeyNode("name", tagNode.Content)
|
|
_, description := utils.FindKeyNode("description", tagNode.Content)
|
|
|
|
var desc string
|
|
if description == nil {
|
|
desc = ""
|
|
}
|
|
if name != nil {
|
|
ref := &Reference{
|
|
Definition: desc,
|
|
Name: name.Value,
|
|
Node: tagNode,
|
|
Path: fmt.Sprintf("$.tags[%d]", x),
|
|
}
|
|
index.globalTagRefs[name.Value] = ref
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return index.globalTagsCount
|
|
}
|
|
|
|
// GetOperationTagsCount will return the number of operation tags found (tags referenced in operations)
|
|
func (index *SpecIndex) GetOperationTagsCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.operationTagsCount > 0 {
|
|
return index.operationTagsCount
|
|
}
|
|
|
|
// this is an aggregate count function that can only be run after operations
|
|
// have been calculated.
|
|
seen := make(map[string]bool)
|
|
count := 0
|
|
for _, path := range index.operationTagsRefs {
|
|
for _, method := range path {
|
|
for _, tag := range method {
|
|
if !seen[tag.Name] {
|
|
seen[tag.Name] = true
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
index.operationTagsCount = count
|
|
return index.operationTagsCount
|
|
}
|
|
|
|
// GetTotalTagsCount will return the number of global and operation tags found that are unique.
|
|
func (index *SpecIndex) GetTotalTagsCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
if index.totalTagsCount > 0 {
|
|
return index.totalTagsCount
|
|
}
|
|
|
|
seen := make(map[string]bool)
|
|
count := 0
|
|
|
|
for _, gt := range index.globalTagRefs {
|
|
// TODO: do we still need this?
|
|
if !seen[gt.Name] {
|
|
seen[gt.Name] = true
|
|
count++
|
|
}
|
|
}
|
|
for _, ot := range index.operationTagsRefs {
|
|
for _, m := range ot {
|
|
for _, t := range m {
|
|
if !seen[t.Name] {
|
|
seen[t.Name] = true
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
index.totalTagsCount = count
|
|
return index.totalTagsCount
|
|
}
|
|
|
|
// GetGlobalLinksCount for each response of each operation method, multiple links can be defined
|
|
func (index *SpecIndex) GetGlobalLinksCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.globalLinksCount > 0 {
|
|
return index.globalLinksCount
|
|
}
|
|
|
|
index.pathRefsLock.Lock()
|
|
for path, p := range index.pathRefs {
|
|
for _, m := range p {
|
|
|
|
// look through method for links
|
|
links, _ := yamlpath.NewPath("$..links")
|
|
res, _ := links.Find(m.Node)
|
|
|
|
if len(res) > 0 {
|
|
|
|
for _, link := range res[0].Content {
|
|
if utils.IsNodeMap(link) {
|
|
|
|
ref := &Reference{
|
|
Definition: m.Name,
|
|
Name: m.Name,
|
|
Node: link,
|
|
}
|
|
|
|
if index.linksRefs[path] == nil {
|
|
index.linksRefs[path] = make(map[string][]*Reference)
|
|
}
|
|
if len(index.linksRefs[path][m.Name]) > 0 {
|
|
index.linksRefs[path][m.Name] = append(index.linksRefs[path][m.Name], ref)
|
|
}
|
|
index.linksRefs[path][m.Name] = []*Reference{ref}
|
|
index.globalLinksCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
index.pathRefsLock.Unlock()
|
|
return index.globalLinksCount
|
|
}
|
|
|
|
// GetRawReferenceCount will return the number of raw references located in the document.
|
|
func (index *SpecIndex) GetRawReferenceCount() int {
|
|
return len(index.rawSequencedRefs)
|
|
}
|
|
|
|
// GetComponentSchemaCount will return the number of schemas located in the 'components' or 'definitions' node.
|
|
func (index *SpecIndex) GetComponentSchemaCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.schemaCount > 0 {
|
|
return index.schemaCount
|
|
}
|
|
|
|
for i, n := range index.root.Content[0].Content {
|
|
if i%2 == 0 {
|
|
|
|
// servers
|
|
if n.Value == "servers" {
|
|
index.rootServersNode = index.root.Content[0].Content[i+1]
|
|
if i+1 < len(index.root.Content[0].Content) {
|
|
serverDefinitions := index.root.Content[0].Content[i+1]
|
|
for x, def := range serverDefinitions.Content {
|
|
ref := &Reference{
|
|
Definition: "servers",
|
|
Name: "server",
|
|
Node: def,
|
|
Path: fmt.Sprintf("$.servers[%d]", x),
|
|
}
|
|
index.serversRefs = append(index.serversRefs, ref)
|
|
}
|
|
}
|
|
}
|
|
|
|
// root security definitions
|
|
if n.Value == "security" {
|
|
index.rootSecurityNode = index.root.Content[0].Content[i+1]
|
|
if i+1 < len(index.root.Content[0].Content) {
|
|
securityDefinitions := index.root.Content[0].Content[i+1]
|
|
for x, def := range securityDefinitions.Content {
|
|
if len(def.Content) > 0 {
|
|
name := def.Content[0]
|
|
ref := &Reference{
|
|
Definition: name.Value,
|
|
Name: name.Value,
|
|
Node: def,
|
|
Path: fmt.Sprintf("$.security[%d]", x),
|
|
}
|
|
index.rootSecurity = append(index.rootSecurity, ref)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if n.Value == "components" {
|
|
_, schemasNode := utils.FindKeyNode("schemas", index.root.Content[0].Content[i+1].Content)
|
|
|
|
// while we are here, go ahead and extract everything in components.
|
|
_, parametersNode := utils.FindKeyNode("parameters", index.root.Content[0].Content[i+1].Content)
|
|
_, requestBodiesNode := utils.FindKeyNode("requestBodies", index.root.Content[0].Content[i+1].Content)
|
|
_, responsesNode := utils.FindKeyNode("responses", index.root.Content[0].Content[i+1].Content)
|
|
_, securitySchemesNode := utils.FindKeyNode("securitySchemes", index.root.Content[0].Content[i+1].Content)
|
|
_, headersNode := utils.FindKeyNode("headers", index.root.Content[0].Content[i+1].Content)
|
|
_, examplesNode := utils.FindKeyNode("examples", index.root.Content[0].Content[i+1].Content)
|
|
_, linksNode := utils.FindKeyNode("links", index.root.Content[0].Content[i+1].Content)
|
|
_, callbacksNode := utils.FindKeyNode("callbacks", index.root.Content[0].Content[i+1].Content)
|
|
|
|
// extract schemas
|
|
if schemasNode != nil {
|
|
index.extractDefinitionsAndSchemas(schemasNode, "#/components/schemas/")
|
|
index.schemasNode = schemasNode
|
|
index.schemaCount = len(schemasNode.Content) / 2
|
|
}
|
|
|
|
// extract parameters
|
|
if parametersNode != nil {
|
|
index.extractComponentParameters(parametersNode, "#/components/parameters/")
|
|
index.parametersNode = parametersNode
|
|
}
|
|
|
|
// extract requestBodies
|
|
if requestBodiesNode != nil {
|
|
index.extractComponentRequestBodies(requestBodiesNode, "#/components/requestBodies/")
|
|
index.requestBodiesNode = requestBodiesNode
|
|
}
|
|
|
|
// extract responses
|
|
if responsesNode != nil {
|
|
index.extractComponentResponses(responsesNode, "#/components/responses/")
|
|
index.responsesNode = responsesNode
|
|
}
|
|
|
|
// extract security schemes
|
|
if securitySchemesNode != nil {
|
|
index.extractComponentSecuritySchemes(securitySchemesNode, "#/components/securitySchemes/")
|
|
index.securitySchemesNode = securitySchemesNode
|
|
}
|
|
|
|
// extract headers
|
|
if headersNode != nil {
|
|
index.extractComponentHeaders(headersNode, "#/components/headers/")
|
|
index.headersNode = headersNode
|
|
}
|
|
|
|
// extract examples
|
|
if examplesNode != nil {
|
|
index.extractComponentExamples(examplesNode, "#/components/examples/")
|
|
index.examplesNode = examplesNode
|
|
}
|
|
|
|
// extract links
|
|
if linksNode != nil {
|
|
index.extractComponentLinks(linksNode, "#/components/links/")
|
|
index.linksNode = linksNode
|
|
}
|
|
|
|
// extract callbacks
|
|
if callbacksNode != nil {
|
|
index.extractComponentCallbacks(callbacksNode, "#/components/callbacks/")
|
|
index.callbacksNode = callbacksNode
|
|
}
|
|
|
|
}
|
|
|
|
// swagger
|
|
if n.Value == "definitions" {
|
|
schemasNode := index.root.Content[0].Content[i+1]
|
|
if schemasNode != nil {
|
|
|
|
// extract schemas
|
|
index.extractDefinitionsAndSchemas(schemasNode, "#/definitions/")
|
|
index.schemasNode = schemasNode
|
|
index.schemaCount = len(schemasNode.Content) / 2
|
|
}
|
|
}
|
|
|
|
// swagger
|
|
if n.Value == "parameters" {
|
|
parametersNode := index.root.Content[0].Content[i+1]
|
|
if parametersNode != nil {
|
|
|
|
// extract params
|
|
index.extractComponentParameters(parametersNode, "#/parameters/")
|
|
index.parametersNode = parametersNode
|
|
}
|
|
}
|
|
|
|
if n.Value == "responses" {
|
|
responsesNode := index.root.Content[0].Content[i+1]
|
|
if responsesNode != nil {
|
|
|
|
// extract responses
|
|
index.extractComponentResponses(responsesNode, "#/responses/")
|
|
index.responsesNode = responsesNode
|
|
}
|
|
}
|
|
|
|
if n.Value == "securityDefinitions" {
|
|
securityDefinitionsNode := index.root.Content[0].Content[i+1]
|
|
if securityDefinitionsNode != nil {
|
|
|
|
// extract security definitions.
|
|
index.extractComponentSecuritySchemes(securityDefinitionsNode, "#/securityDefinitions/")
|
|
index.securitySchemesNode = securityDefinitionsNode
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return index.schemaCount
|
|
}
|
|
|
|
// GetComponentParameterCount returns the number of parameter components defined
|
|
func (index *SpecIndex) GetComponentParameterCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.componentParamCount > 0 {
|
|
return index.componentParamCount
|
|
}
|
|
|
|
for i, n := range index.root.Content[0].Content {
|
|
if i%2 == 0 {
|
|
// openapi 3
|
|
if n.Value == "components" {
|
|
_, parametersNode := utils.FindKeyNode("parameters", index.root.Content[0].Content[i+1].Content)
|
|
if parametersNode != nil {
|
|
index.parametersNode = parametersNode
|
|
index.componentParamCount = len(parametersNode.Content) / 2
|
|
}
|
|
}
|
|
// openapi 2
|
|
if n.Value == "parameters" {
|
|
parametersNode := index.root.Content[0].Content[i+1]
|
|
if parametersNode != nil {
|
|
index.parametersNode = parametersNode
|
|
index.componentParamCount = len(parametersNode.Content) / 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return index.componentParamCount
|
|
}
|
|
|
|
// GetOperationCount returns the number of operations (for all paths) located in the document
|
|
func (index *SpecIndex) GetOperationCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.pathsNode == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.operationCount > 0 {
|
|
return index.operationCount
|
|
}
|
|
|
|
opCount := 0
|
|
|
|
for x, p := range index.pathsNode.Content {
|
|
if x%2 == 0 {
|
|
|
|
method := index.pathsNode.Content[x+1]
|
|
|
|
// extract methods for later use.
|
|
for y, m := range method.Content {
|
|
if y%2 == 0 {
|
|
|
|
// check node is a valid method
|
|
valid := false
|
|
for _, methodType := range methodTypes {
|
|
if m.Value == methodType {
|
|
valid = true
|
|
}
|
|
}
|
|
if valid {
|
|
ref := &Reference{
|
|
Definition: m.Value,
|
|
Name: m.Value,
|
|
Node: method.Content[y+1],
|
|
}
|
|
index.pathRefsLock.Lock()
|
|
if index.pathRefs[p.Value] == nil {
|
|
index.pathRefs[p.Value] = make(map[string]*Reference)
|
|
}
|
|
index.pathRefs[p.Value][ref.Name] = ref
|
|
index.pathRefsLock.Unlock()
|
|
// update
|
|
opCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
index.operationCount = opCount
|
|
return opCount
|
|
}
|
|
|
|
// GetOperationsParameterCount returns the number of parameters defined in paths and operations.
|
|
// this method looks in top level (path level) and inside each operation (get, post etc.). Parameters can
|
|
// be hiding within multiple places.
|
|
func (index *SpecIndex) GetOperationsParameterCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.pathsNode == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.operationParamCount > 0 {
|
|
return index.operationParamCount
|
|
}
|
|
|
|
// parameters are sneaky, they can be in paths, in path operations or in components.
|
|
// sometimes they are refs, sometimes they are inline definitions, just for fun.
|
|
// some authors just LOVE to mix and match them all up.
|
|
// check paths first
|
|
for x, pathItemNode := range index.pathsNode.Content {
|
|
if x%2 == 0 {
|
|
|
|
pathPropertyNode := index.pathsNode.Content[x+1]
|
|
|
|
// extract methods for later use.
|
|
for y, prop := range pathPropertyNode.Content {
|
|
if y%2 == 0 {
|
|
|
|
// while we're here, lets extract any top level servers
|
|
if prop.Value == "servers" {
|
|
serversNode := pathPropertyNode.Content[y+1]
|
|
if index.opServersRefs[pathItemNode.Value] == nil {
|
|
index.opServersRefs[pathItemNode.Value] = make(map[string][]*Reference)
|
|
}
|
|
var serverRefs []*Reference
|
|
for _, serverRef := range serversNode.Content {
|
|
ref := &Reference{
|
|
Definition: serverRef.Value,
|
|
Name: serverRef.Value,
|
|
Node: serverRef,
|
|
}
|
|
serverRefs = append(serverRefs, ref)
|
|
}
|
|
index.opServersRefs[pathItemNode.Value]["top"] = serverRefs
|
|
}
|
|
|
|
// top level params
|
|
if prop.Value == "parameters" {
|
|
|
|
// let's look at params, check if they are refs or inline.
|
|
params := pathPropertyNode.Content[y+1].Content
|
|
index.scanOperationParams(params, pathItemNode, "top")
|
|
}
|
|
|
|
// method level params.
|
|
if isHttpMethod(prop.Value) {
|
|
|
|
for z, httpMethodProp := range pathPropertyNode.Content[y+1].Content {
|
|
if z%2 == 0 {
|
|
if httpMethodProp.Value == "parameters" {
|
|
params := pathPropertyNode.Content[y+1].Content[z+1].Content
|
|
index.scanOperationParams(params, pathItemNode, prop.Value)
|
|
}
|
|
|
|
// extract operation tags if set.
|
|
if httpMethodProp.Value == "tags" {
|
|
tags := pathPropertyNode.Content[y+1].Content[z+1]
|
|
|
|
if index.operationTagsRefs[pathItemNode.Value] == nil {
|
|
index.operationTagsRefs[pathItemNode.Value] = make(map[string][]*Reference)
|
|
}
|
|
|
|
var tagRefs []*Reference
|
|
for _, tagRef := range tags.Content {
|
|
ref := &Reference{
|
|
Definition: tagRef.Value,
|
|
Name: tagRef.Value,
|
|
Node: tagRef,
|
|
}
|
|
tagRefs = append(tagRefs, ref)
|
|
}
|
|
index.operationTagsRefs[pathItemNode.Value][prop.Value] = tagRefs
|
|
}
|
|
|
|
// extract description and summaries
|
|
if httpMethodProp.Value == "description" {
|
|
desc := pathPropertyNode.Content[y+1].Content[z+1].Value
|
|
ref := &Reference{
|
|
Definition: desc,
|
|
Name: "description",
|
|
Node: pathPropertyNode.Content[y+1].Content[z+1],
|
|
}
|
|
if index.operationDescriptionRefs[pathItemNode.Value] == nil {
|
|
index.operationDescriptionRefs[pathItemNode.Value] = make(map[string]*Reference)
|
|
}
|
|
|
|
index.operationDescriptionRefs[pathItemNode.Value][prop.Value] = ref
|
|
}
|
|
if httpMethodProp.Value == "summary" {
|
|
summary := pathPropertyNode.Content[y+1].Content[z+1].Value
|
|
ref := &Reference{
|
|
Definition: summary,
|
|
Name: "summary",
|
|
Node: pathPropertyNode.Content[y+1].Content[z+1],
|
|
}
|
|
|
|
if index.operationSummaryRefs[pathItemNode.Value] == nil {
|
|
index.operationSummaryRefs[pathItemNode.Value] = make(map[string]*Reference)
|
|
}
|
|
|
|
index.operationSummaryRefs[pathItemNode.Value][prop.Value] = ref
|
|
}
|
|
|
|
// extract servers from method operation.
|
|
if httpMethodProp.Value == "servers" {
|
|
serversNode := pathPropertyNode.Content[y+1].Content[z+1]
|
|
|
|
var serverRefs []*Reference
|
|
for _, serverRef := range serversNode.Content {
|
|
ref := &Reference{
|
|
Definition: "servers",
|
|
Name: "servers",
|
|
Node: serverRef,
|
|
}
|
|
serverRefs = append(serverRefs, ref)
|
|
}
|
|
|
|
if index.opServersRefs[pathItemNode.Value] == nil {
|
|
index.opServersRefs[pathItemNode.Value] = make(map[string][]*Reference)
|
|
}
|
|
|
|
index.opServersRefs[pathItemNode.Value][prop.Value] = serverRefs
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that all the paths and operations are processed, lets pick out everything from our pre
|
|
// mapped refs and populate our ready to roll index of component params.
|
|
for key, component := range index.allMappedRefs {
|
|
if strings.Contains(key, "/parameters/") {
|
|
index.paramCompRefs[key] = component
|
|
index.paramAllRefs[key] = component
|
|
}
|
|
}
|
|
|
|
//now build main index of all params by combining comp refs with inline params from operations.
|
|
//use the namespace path:::param for inline params to identify them as inline.
|
|
for path, params := range index.paramOpRefs {
|
|
for mName, mValue := range params {
|
|
for pName, pValue := range mValue {
|
|
if !strings.HasPrefix(pName, "#") {
|
|
index.paramInlineDuplicates[pName] = append(index.paramInlineDuplicates[pName], pValue)
|
|
index.paramAllRefs[fmt.Sprintf("%s:::%s", path, mName)] = pValue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
index.operationParamCount = len(index.paramCompRefs) + len(index.paramInlineDuplicates)
|
|
return index.operationParamCount
|
|
|
|
}
|
|
|
|
// GetInlineDuplicateParamCount returns the number of inline duplicate parameters (operation params)
|
|
func (index *SpecIndex) GetInlineDuplicateParamCount() int {
|
|
if index.componentsInlineParamDuplicateCount > 0 {
|
|
return index.componentsInlineParamDuplicateCount
|
|
}
|
|
dCount := len(index.paramInlineDuplicates) - index.countUniqueInlineDuplicates()
|
|
index.componentsInlineParamDuplicateCount = dCount
|
|
return dCount
|
|
}
|
|
|
|
// GetInlineUniqueParamCount returns the number of unique inline parameters (operation params)
|
|
func (index *SpecIndex) GetInlineUniqueParamCount() int {
|
|
return index.countUniqueInlineDuplicates()
|
|
}
|
|
|
|
// ExtractComponentsFromRefs returns located components from references. The returned nodes from here
|
|
// can be used for resolving as they contain the actual object properties.
|
|
func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Reference {
|
|
var found []*Reference
|
|
for _, ref := range refs {
|
|
|
|
// so, some really strange shit showed up when linting api.guru
|
|
if strings.Contains(ref.Definition, "~1") { // this was from azure! jesus guys, win32? wtf.
|
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
|
indexError := &IndexingError{
|
|
Error: fmt.Errorf("component '%s' contains freaky win32 '~1' file truncation, can't be used.", ref.Definition),
|
|
Node: ref.Node,
|
|
Path: path,
|
|
}
|
|
index.refErrors = append(index.refErrors, indexError)
|
|
continue
|
|
}
|
|
|
|
// check reference for back slashes (hah yeah seen this too!)
|
|
if strings.Contains(ref.Definition, "\\") { // this was from blazemeter.com haha!
|
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
|
indexError := &IndexingError{
|
|
Error: fmt.Errorf("component '%s' contains a backslash '\\'. It's not valid.", ref.Definition),
|
|
Node: ref.Node,
|
|
Path: path,
|
|
}
|
|
index.refErrors = append(index.refErrors, indexError)
|
|
continue
|
|
}
|
|
|
|
located := index.FindComponent(ref.Definition, ref.Node)
|
|
if located != nil {
|
|
found = append(found, located)
|
|
index.allMappedRefs[ref.Definition] = located
|
|
index.allMappedRefsSequenced = append(index.allMappedRefsSequenced, &ReferenceMapped{
|
|
Reference: located,
|
|
Definition: ref.Definition,
|
|
})
|
|
} else {
|
|
|
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
|
indexError := &IndexingError{
|
|
Error: fmt.Errorf("component '%s' does not exist in the specification", ref.Definition),
|
|
Node: ref.Node,
|
|
Path: path,
|
|
}
|
|
index.refErrors = append(index.refErrors, indexError)
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
// FindComponent will locate a component by its reference, returns nil if nothing is found.
|
|
// This method will recurse through remote, local and file references. For each new external reference
|
|
// a new index will be created. These indexes can then be traversed recursively.
|
|
func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference {
|
|
if index.root == nil {
|
|
return nil
|
|
}
|
|
|
|
remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
|
|
return index.lookupRemoteReference(id)
|
|
}
|
|
|
|
fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
|
|
return index.lookupFileReference(id)
|
|
}
|
|
|
|
switch determineReferenceResolveType(componentId) {
|
|
case localResolve: // ideally, every single ref in every single spec is local. however, this is not the case.
|
|
return index.findComponentInRoot(componentId)
|
|
|
|
case httpResolve:
|
|
uri := strings.Split(componentId, "#")
|
|
if len(uri) == 2 {
|
|
return index.performExternalLookup(uri, componentId, remoteLookup, parent)
|
|
}
|
|
|
|
case fileResolve:
|
|
uri := strings.Split(componentId, "#")
|
|
if len(uri) == 2 {
|
|
return index.performExternalLookup(uri, componentId, fileLookup, parent)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetAllDescriptionsCount will collect together every single description found in the document
|
|
func (index *SpecIndex) GetAllDescriptionsCount() int {
|
|
return len(index.allDescriptions)
|
|
}
|
|
|
|
// GetAllSummariesCount will collect together every single summary found in the document
|
|
func (index *SpecIndex) GetAllSummariesCount() int {
|
|
return len(index.allSummaries)
|
|
}
|
|
|
|
/* private */
|
|
|
|
func determineReferenceResolveType(ref string) int {
|
|
if ref != "" && ref[0] == '#' {
|
|
return localResolve
|
|
}
|
|
if ref != "" && len(ref) >= 5 && (ref[:5] == "https" || ref[:5] == "http:") {
|
|
return httpResolve
|
|
}
|
|
if strings.Contains(ref, ".json") ||
|
|
strings.Contains(ref, ".yaml") ||
|
|
strings.Contains(ref, ".yml") {
|
|
return fileResolve
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, schema := range schemasNode.Content {
|
|
if i%2 == 0 {
|
|
name = schema.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: schema,
|
|
}
|
|
index.allSchemas[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentParameters(paramsNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, param := range paramsNode.Content {
|
|
if i%2 == 0 {
|
|
name = param.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: param,
|
|
}
|
|
index.allParameters[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentRequestBodies(requestBodiesNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, reqBod := range requestBodiesNode.Content {
|
|
if i%2 == 0 {
|
|
name = reqBod.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: reqBod,
|
|
}
|
|
index.allRequestBodies[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentResponses(responsesNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, response := range responsesNode.Content {
|
|
if i%2 == 0 {
|
|
name = response.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: response,
|
|
}
|
|
index.allResponses[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentHeaders(headersNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, header := range headersNode.Content {
|
|
if i%2 == 0 {
|
|
name = header.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: header,
|
|
}
|
|
index.allHeaders[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentCallbacks(callbacksNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, callback := range callbacksNode.Content {
|
|
if i%2 == 0 {
|
|
name = callback.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: callback,
|
|
}
|
|
index.allCallbacks[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentLinks(linksNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, link := range linksNode.Content {
|
|
if i%2 == 0 {
|
|
name = link.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: link,
|
|
}
|
|
index.allLinks[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentExamples(examplesNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, example := range examplesNode.Content {
|
|
if i%2 == 0 {
|
|
name = example.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: example,
|
|
}
|
|
index.allExamples[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) {
|
|
var name string
|
|
for i, secScheme := range securitySchemesNode.Content {
|
|
if i%2 == 0 {
|
|
name = secScheme.Value
|
|
continue
|
|
}
|
|
def := fmt.Sprintf("%s%s", pathPrefix, name)
|
|
ref := &Reference{
|
|
Definition: def,
|
|
Name: name,
|
|
Node: secScheme,
|
|
}
|
|
index.allSecuritySchemes[def] = ref
|
|
}
|
|
}
|
|
|
|
func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
|
|
lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference {
|
|
|
|
externalSpecIndex := index.externalSpecIndex[uri[0]]
|
|
var foundNode *yaml.Node
|
|
if externalSpecIndex == nil {
|
|
|
|
n, newRoot, err := lookupFunction(componentId)
|
|
|
|
if err != nil {
|
|
indexError := &IndexingError{
|
|
Error: err,
|
|
Node: parent,
|
|
Path: componentId,
|
|
}
|
|
index.refErrors = append(index.refErrors, indexError)
|
|
return nil
|
|
}
|
|
|
|
if n != nil {
|
|
foundNode = n
|
|
}
|
|
|
|
// cool, cool, lets index this spec also. This is a recursive action and will keep going
|
|
// until all remote references have been found.
|
|
newIndex := NewSpecIndex(newRoot)
|
|
index.externalSpecIndex[uri[0]] = newIndex
|
|
|
|
} else {
|
|
|
|
foundRef := externalSpecIndex.findComponentInRoot(uri[1])
|
|
if foundRef != nil {
|
|
foundNode = foundRef.Node
|
|
}
|
|
}
|
|
|
|
if foundNode != nil {
|
|
nameSegs := strings.Split(uri[1], "/")
|
|
ref := &Reference{
|
|
Definition: componentId,
|
|
Name: nameSegs[len(nameSegs)-1],
|
|
Node: foundNode,
|
|
}
|
|
return ref
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (index *SpecIndex) findComponentInRoot(componentId string) *Reference {
|
|
|
|
name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId)
|
|
|
|
path, _ := yamlpath.NewPath(friendlySearch)
|
|
res, _ := path.Find(index.root)
|
|
|
|
if len(res) == 1 {
|
|
ref := &Reference{
|
|
Definition: componentId,
|
|
Name: name,
|
|
Node: res[0],
|
|
}
|
|
|
|
return ref
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (index *SpecIndex) countUniqueInlineDuplicates() int {
|
|
if index.componentsInlineParamUniqueCount > 0 {
|
|
return index.componentsInlineParamUniqueCount
|
|
}
|
|
unique := 0
|
|
for _, p := range index.paramInlineDuplicates {
|
|
if len(p) == 1 {
|
|
unique++
|
|
}
|
|
}
|
|
index.componentsInlineParamUniqueCount = unique
|
|
return unique
|
|
}
|
|
|
|
func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *yaml.Node, method string) {
|
|
for i, param := range params {
|
|
|
|
// param is ref
|
|
if len(param.Content) > 0 && param.Content[0].Value == "$ref" {
|
|
|
|
paramRefName := param.Content[1].Value
|
|
paramRef := index.allMappedRefs[paramRefName]
|
|
|
|
if index.paramOpRefs[pathItemNode.Value] == nil {
|
|
index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string]*Reference)
|
|
index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference)
|
|
|
|
}
|
|
// if we know the path, but it's a new method
|
|
if index.paramOpRefs[pathItemNode.Value][method] == nil {
|
|
index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference)
|
|
}
|
|
index.paramOpRefs[pathItemNode.Value][method][paramRefName] = paramRef
|
|
continue
|
|
|
|
} else {
|
|
|
|
//param is inline.
|
|
_, vn := utils.FindKeyNode("name", param.Content)
|
|
|
|
if vn == nil {
|
|
|
|
path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i)
|
|
if method == "top" {
|
|
path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i)
|
|
}
|
|
|
|
index.operationParamErrors = append(index.operationParamErrors, &IndexingError{
|
|
Error: fmt.Errorf("the '%s' operation parameter at path '%s', index %d has no 'name' value",
|
|
method, pathItemNode.Value, i),
|
|
Node: param,
|
|
Path: path,
|
|
})
|
|
continue
|
|
}
|
|
|
|
ref := &Reference{
|
|
Definition: vn.Value,
|
|
Name: vn.Value,
|
|
Node: param,
|
|
}
|
|
if index.paramOpRefs[pathItemNode.Value] == nil {
|
|
index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string]*Reference)
|
|
index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference)
|
|
}
|
|
|
|
// if we know the path but this is a new method.
|
|
if index.paramOpRefs[pathItemNode.Value][method] == nil {
|
|
index.paramOpRefs[pathItemNode.Value][method] = make(map[string]*Reference)
|
|
}
|
|
|
|
// if this is a duplicate, add an error and ignore it
|
|
if index.paramOpRefs[pathItemNode.Value][method][ref.Name] != nil {
|
|
path := fmt.Sprintf("$.paths.%s.%s.parameters[%d]", pathItemNode.Value, method, i)
|
|
if method == "top" {
|
|
path = fmt.Sprintf("$.paths.%s.parameters[%d]", pathItemNode.Value, i)
|
|
}
|
|
|
|
index.operationParamErrors = append(index.operationParamErrors, &IndexingError{
|
|
Error: fmt.Errorf("the '%s' operation parameter at path '%s', index %d has a duplicate name '%s'",
|
|
method, pathItemNode.Value, i, vn.Value),
|
|
Node: param,
|
|
Path: path,
|
|
})
|
|
} else {
|
|
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = ref
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func isHttpMethod(val string) bool {
|
|
switch strings.ToLower(val) {
|
|
case methodTypes[0]:
|
|
return true
|
|
case methodTypes[1]:
|
|
return true
|
|
case methodTypes[2]:
|
|
return true
|
|
case methodTypes[3]:
|
|
return true
|
|
case methodTypes[4]:
|
|
return true
|
|
case methodTypes[5]:
|
|
return true
|
|
case methodTypes[6]:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) {
|
|
|
|
// split string to remove file reference
|
|
uri := strings.Split(ref, "#")
|
|
|
|
var parsedRemoteDocument *yaml.Node
|
|
if index.seenRemoteSources[uri[0]] != nil {
|
|
parsedRemoteDocument = index.seenRemoteSources[uri[0]]
|
|
} else {
|
|
resp, err := http.Get(uri[0])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var remoteDoc yaml.Node
|
|
err = yaml.Unmarshal(body, &remoteDoc)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
parsedRemoteDocument = &remoteDoc
|
|
index.remoteLock.Lock()
|
|
index.seenRemoteSources[uri[0]] = &remoteDoc
|
|
index.remoteLock.Unlock()
|
|
}
|
|
|
|
if parsedRemoteDocument == nil {
|
|
return nil, nil, fmt.Errorf("unable to parse remote reference: '%s'", uri[0])
|
|
}
|
|
|
|
// lookup item from reference by using a path query.
|
|
query := fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", "."))
|
|
|
|
path, err := yamlpath.NewPath(query)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
result, err := path.Find(parsedRemoteDocument)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if len(result) == 1 {
|
|
return result[0], parsedRemoteDocument, nil
|
|
}
|
|
|
|
return nil, nil, nil
|
|
}
|
|
|
|
func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) {
|
|
|
|
// split string to remove file reference
|
|
uri := strings.Split(ref, "#")
|
|
|
|
if len(uri) != 2 {
|
|
return nil, nil, fmt.Errorf("unable to determine filename for file reference: '%s'", ref)
|
|
}
|
|
|
|
file := strings.ReplaceAll(uri[0], "file:", "")
|
|
|
|
var parsedRemoteDocument *yaml.Node
|
|
if index.seenRemoteSources[file] != nil {
|
|
parsedRemoteDocument = index.seenRemoteSources[file]
|
|
} else {
|
|
|
|
body, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var remoteDoc yaml.Node
|
|
err = yaml.Unmarshal(body, &remoteDoc)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
parsedRemoteDocument = &remoteDoc
|
|
index.seenRemoteSources[file] = &remoteDoc
|
|
}
|
|
|
|
if parsedRemoteDocument == nil {
|
|
return nil, nil, fmt.Errorf("unable to parse file reference: '%s'", file)
|
|
}
|
|
|
|
// lookup item from reference by using a path query.
|
|
query := fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", "."))
|
|
|
|
path, err := yamlpath.NewPath(query)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
result, _ := path.Find(parsedRemoteDocument)
|
|
if len(result) == 1 {
|
|
return result[0], parsedRemoteDocument, nil
|
|
}
|
|
|
|
return nil, parsedRemoteDocument, nil
|
|
}
|