mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 12:37:49 +00:00
lookups are performed inline now. keeps things simpler, however it has a performance knock, so it's time to refine async building were possible.
1961 lines
63 KiB
Go
1961 lines
63 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
|
|
IsRemote bool
|
|
RemoteLocation string
|
|
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
|
|
callbacksRefs map[string]map[string][]*Reference // all links
|
|
linksRefs map[string]map[string][]*Reference // all callbacks
|
|
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
|
|
globalCallbacksCount int // component callbacks
|
|
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.callbacksRefs = make(map[string]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,
|
|
index.GetGlobalCallbacksCount,
|
|
}
|
|
|
|
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
|
|
|
|
segs := strings.Split(value, "/")
|
|
name := segs[len(segs)-1]
|
|
//name := strings.ReplaceAll(segs[len(segs)-1], "~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.polymorphicAllOfRefs = 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
|
|
}
|
|
|
|
// GetGlobalCallbacksCount for each response of each operation method, multiple links can be defined
|
|
func (index *SpecIndex) GetGlobalCallbacksCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.globalCallbacksCount > 0 {
|
|
return index.globalCallbacksCount
|
|
}
|
|
|
|
index.pathRefsLock.Lock()
|
|
for path, p := range index.pathRefs {
|
|
for _, m := range p {
|
|
|
|
// look through method for callbacks
|
|
callbacks, _ := yamlpath.NewPath("$..callbacks")
|
|
res, _ := callbacks.Find(m.Node)
|
|
|
|
if len(res) > 0 {
|
|
|
|
for _, callback := range res[0].Content {
|
|
if utils.IsNodeMap(callback) {
|
|
|
|
ref := &Reference{
|
|
Definition: m.Name,
|
|
Name: m.Name,
|
|
Node: callback,
|
|
}
|
|
|
|
if index.callbacksRefs[path] == nil {
|
|
index.callbacksRefs[path] = make(map[string][]*Reference)
|
|
}
|
|
if len(index.callbacksRefs[path][m.Name]) > 0 {
|
|
index.callbacksRefs[path][m.Name] = append(index.callbacksRefs[path][m.Name], ref)
|
|
}
|
|
index.callbacksRefs[path][m.Name] = []*Reference{ref}
|
|
index.globalCallbacksCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
index.pathRefsLock.Unlock()
|
|
return index.globalCallbacksCount
|
|
}
|
|
|
|
// GetGlobalLinksCount for each response of each operation method, multiple callbacks can be defined
|
|
func (index *SpecIndex) GetGlobalLinksCount() int {
|
|
if index.root == nil {
|
|
return -1
|
|
}
|
|
|
|
if index.globalCallbacksCount > 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 {
|
|
|
|
// 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)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
/* private */
|
|
|
|
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,
|
|
IsRemote: true,
|
|
RemoteLocation: componentId,
|
|
}
|
|
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], "/", "."))
|
|
|
|
// remove any URL encoding
|
|
query = strings.Replace(query, "~1", "./", 1)
|
|
query = strings.ReplaceAll(query, "~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], "/", "."))
|
|
|
|
// remove any URL encoding
|
|
query = strings.Replace(query, "~1", "./", 1)
|
|
query = strings.ReplaceAll(query, "~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
|
|
}
|