Files
libopenapi/index/spec_index.go
Dave Shanley 1c9f6bbfcc adding tests, fixing bugs, validating model.
Amazing what you find when you really test the fuck out of things.
2022-08-14 08:18:27 -04:00

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 callbacks 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.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 {
// check reference for backslashes (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
}