// Copyright 2022-2033 Dave Shanley / Quobix // SPDX-License-Identifier: MIT // Package index contains an OpenAPI indexer that will very quickly scan through an OpenAPI specification (all versions) // and extract references to all the important nodes you might want to look up, as well as counts on total objects. // // When extracting references, the index can determine if the reference is local to the file (recommended) or the // reference is located in another local file, or a remote file. The index will then attempt to load in those remote // files and look up the references there, or continue following the chain. // // When the index loads in a local or remote file, it will also index that remote spec as well. This means everything // is indexed and stored as a tree, depending on how deep the remote references go. package index import ( "fmt" "log/slog" "os" "path/filepath" "sort" "strings" "sync" "github.com/pb33f/libopenapi/utils" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "gopkg.in/yaml.v3" ) const ( // theoreticalRoot is the name of the theoretical spec file used when a root spec file does not exist theoreticalRoot = "root.yaml" ) // NewSpecIndexWithConfig will create a new index of an OpenAPI or Swagger spec. It uses the same logic as NewSpecIndex // except it sets a base URL for resolving relative references, except it also allows for granular control over // how the index is set up. func NewSpecIndexWithConfig(rootNode *yaml.Node, config *SpecIndexConfig) *SpecIndex { index := new(SpecIndex) boostrapIndexCollections(index) index.config = config index.rolodex = config.Rolodex index.uri = config.uri index.specAbsolutePath = config.SpecAbsolutePath if config.Logger != nil { index.logger = config.Logger } else { index.logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelError, })) } if rootNode == nil || len(rootNode.Content) <= 0 { return index } index.root = rootNode return createNewIndex(rootNode, index, config.AvoidBuildIndex) } // 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. // // This creates a new index using a default 'open' configuration. This means if a BaseURL or BasePath are supplied // the rolodex will automatically read those files or open those h func NewSpecIndex(rootNode *yaml.Node) *SpecIndex { index := new(SpecIndex) index.config = CreateOpenAPIIndexConfig() index.root = rootNode boostrapIndexCollections(index) return createNewIndex(rootNode, index, false) } func createNewIndex(rootNode *yaml.Node, index *SpecIndex, avoidBuildOut bool) *SpecIndex { // there is no node! return an empty index. if rootNode == nil { return index } index.nodeMapCompleted = make(chan bool) index.nodeMap = make(map[int]map[int]*yaml.Node) go index.MapNodes(rootNode) // this can run async. index.cache = new(sync.Map) // boot index. results := index.ExtractRefs(index.root.Content[0], index.root, []string{}, 0, false, "") // map poly refs poly := make([]*Reference, len(index.polymorphicRefs)) z := 0 for i := range index.polymorphicRefs { poly[z] = index.polymorphicRefs[i] z++ } // pull out references index.ExtractComponentsFromRefs(results) index.ExtractComponentsFromRefs(poly) index.ExtractExternalDocuments(index.root) index.GetPathCount() // build out the index. if !avoidBuildOut { index.BuildIndex() } <-index.nodeMapCompleted return index } // BuildIndex will run all the count operations required to build up maps of everything. It's what makes the index // useful for looking up things, the count operations are all run in parallel and then the final calculations are run // the index is ready. func (index *SpecIndex) BuildIndex() { if index.built { return } 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() index.built = true } func (index *SpecIndex) GetLogger() *slog.Logger { return index.logger } // GetRootNode returns document root node. func (index *SpecIndex) GetRootNode() *yaml.Node { return index.root } func (index *SpecIndex) GetRolodex() *Rolodex { return index.rolodex } // GetSpecFileName returns the root spec filename, if it exists, otherwise returns the theoretical root spec func (index *SpecIndex) GetSpecFileName() string { if index == nil || index.rolodex == nil || index.rolodex.indexConfig == nil || index.rolodex.indexConfig.SpecFilePath == "" { return theoreticalRoot } return filepath.Base(index.rolodex.indexConfig.SpecFilePath) } // GetGlobalTagsNode returns document root tags node. func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node { return index.tagsNode } // SetCircularReferences is a convenience method for the resolver to pass in circular references // if the resolver is used. func (index *SpecIndex) SetCircularReferences(refs []*CircularReferenceResult) { index.circularReferences = refs } // GetCircularReferences will return any circular reference results that were found by the resolver. func (index *SpecIndex) GetCircularReferences() []*CircularReferenceResult { return index.circularReferences } // SetIgnoredPolymorphicCircularReferences passes on any ignored poly circular refs captured using // `IgnorePolymorphicCircularReferences` func (index *SpecIndex) SetIgnoredPolymorphicCircularReferences(refs []*CircularReferenceResult) { index.polyCircularReferences = refs } func (index *SpecIndex) SetIgnoredArrayCircularReferences(refs []*CircularReferenceResult) { index.arrayCircularReferences = refs } // GetIgnoredPolymorphicCircularReferences will return any polymorphic circular references that were 'ignored' by // using the `IgnorePolymorphicCircularReferences` configuration option. func (index *SpecIndex) GetIgnoredPolymorphicCircularReferences() []*CircularReferenceResult { return index.polyCircularReferences } // GetIgnoredArrayCircularReferences will return any array based circular references that were 'ignored' by // using the `IgnoreArrayCircularReferences` configuration option. func (index *SpecIndex) GetIgnoredArrayCircularReferences() []*CircularReferenceResult { return index.arrayCircularReferences } // 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 } // GetRawReferencesSequenced returns a slice of every single reference found in the document, extracted raw from the doc // returned in the exact order they were found in the document. func (index *SpecIndex) GetRawReferencesSequenced() []*Reference { return index.rawSequencedRefs } // 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 references to all schemas found in the document both inline and those under components // The first elements of at the top of the slice, are all the inline references (using GetAllInlineSchemas), // and then following on are all the references extracted from the components section (using GetAllComponentSchemas). // finally all the references that are not inline, but marked as $ref in the document are returned (using GetAllReferenceSchemas). // the results are sorted by line number. func (index *SpecIndex) GetAllSchemas() []*Reference { componentSchemas := index.GetAllComponentSchemas() inlineSchemas := index.GetAllInlineSchemas() refSchemas := index.GetAllReferenceSchemas() combined := make([]*Reference, len(inlineSchemas)+len(componentSchemas)+len(refSchemas)) i := 0 for x := range inlineSchemas { combined[i] = inlineSchemas[x] i++ } for x := range componentSchemas { combined[i] = componentSchemas[x] i++ } for x := range refSchemas { combined[i] = refSchemas[x] i++ } sort.Slice(combined, func(i, j int) bool { return combined[i].Node.Line < combined[j].Node.Line }) return combined } // GetAllInlineSchemaObjects will return all schemas that are inline (not inside components) and that are also typed // as 'object' or 'array' (not primitives). func (index *SpecIndex) GetAllInlineSchemaObjects() []*Reference { return index.allInlineSchemaObjectDefinitions } // GetAllInlineSchemas will return all schemas defined in the components section of the document. func (index *SpecIndex) GetAllInlineSchemas() []*Reference { return index.allInlineSchemaDefinitions } // GetAllReferenceSchemas will return all schemas that are not inline, but $ref'd from somewhere. func (index *SpecIndex) GetAllReferenceSchemas() []*Reference { return index.allRefSchemaDefinitions } // GetAllComponentSchemas will return all schemas defined in the components section of the document. func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference { if index != nil && index.allComponentSchemas != nil { return index.allComponentSchemas } if index != nil && index.allComponentSchemas == nil { schemaMap := syncMapToMap[string, *Reference](index.allComponentSchemaDefinitions) index.allComponentSchemas = schemaMap return index.allComponentSchemas } return nil } // 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 } // GetAllObjectsWithProperties will return all objects with properties found in the document func (index *SpecIndex) GetAllObjectsWithProperties() []*ObjectReference { return index.allObjectsWithProperties } // 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.paramInlineDuplicateNames } // 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 } // GetReferenceIndexErrors will return any errors that occurred when indexing references func (index *SpecIndex) GetReferenceIndexErrors() []error { return index.refErrors } // GetOperationParametersIndexErrors any errors that occurred when indexing operation parameters func (index *SpecIndex) GetOperationParametersIndexErrors() []error { 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 } // GetSecurityRequirementReferences will return all security requirement definitions found in the document func (index *SpecIndex) GetSecurityRequirementReferences() map[string]map[string][]*Reference { return index.securityRequirementRefs } // 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 } // SetAllowCircularReferenceResolving will flip a bit that can be used by any consumers to determine if they want // to allow or disallow circular references to be resolved or visited func (index *SpecIndex) SetAllowCircularReferenceResolving(allow bool) { index.allowCircularReferences = allow } // AllowCircularReferenceResolving will return a bit that allows developers to determine what to do with circular refs. func (index *SpecIndex) AllowCircularReferenceResolving() bool { return index.allowCircularReferences } 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, "" } // 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.RLock() for path, p := range index.pathRefs { for _, m := range p { // look through method for callbacks callbacks, _ := yamlpath.NewPath("$..callbacks") var res []*yaml.Node 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) } else { index.callbacksRefs[path][m.Name] = []*Reference{ref} } index.globalCallbacksCount++ } } } } } index.pathRefsLock.RUnlock() 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") var res []*yaml.Node 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 || len(index.root.Content) == 0 { 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), ParentNode: index.rootServersNode, } 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.componentLock.Lock() index.parametersNode = parametersNode index.componentLock.Unlock() } // 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.componentLock.Lock() index.parametersNode = parametersNode index.componentLock.Unlock() } } 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.componentLock.Lock() index.parametersNode = parametersNode index.componentParamCount = len(parametersNode.Content) / 2 index.componentLock.Unlock() } } // openapi 2 if n.Value == "parameters" { parametersNode := index.root.Content[0].Content[i+1] if parametersNode != nil { index.componentLock.Lock() index.parametersNode = parametersNode index.componentParamCount = len(parametersNode.Content) / 2 index.componentLock.Unlock() } } } } 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 locatedPathRefs := make(map[string]map[string]*Reference) for x, p := range index.pathsNode.Content { if x%2 == 0 { var method *yaml.Node if utils.IsNodeArray(index.pathsNode) { method = index.pathsNode.Content[x] } else { 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], Path: fmt.Sprintf("$.paths['%s'].%s", p.Value, m.Value), ParentNode: m, } if locatedPathRefs[p.Value] == nil { locatedPathRefs[p.Value] = make(map[string]*Reference) } locatedPathRefs[p.Value][ref.Name] = ref // update opCount++ } } } } } for k, v := range locatedPathRefs { index.pathRefs[k] = v } 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 { var pathPropertyNode *yaml.Node if utils.IsNodeArray(index.pathsNode) { pathPropertyNode = index.pathsNode.Content[x] } else { pathPropertyNode = index.pathsNode.Content[x+1] } // is the path a ref? if isRef, _, ref := utils.IsNodeRefValue(pathPropertyNode); isRef { pNode := seekRefEnd(index, ref) if pNode != nil { pathPropertyNode = pNode.Node } } // 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 i, serverRef := range serversNode.Content { ref := &Reference{ Definition: serverRef.Value, Name: serverRef.Value, Node: serverRef, ParentNode: prop, Path: fmt.Sprintf("$.paths['%s'].servers[%d]", pathItemNode.Value, i), } 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, pathPropertyNode.Content[y], 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, pathPropertyNode.Content[y+1].Content[z], 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 i, serverRef := range serversNode.Content { ref := &Reference{ Definition: "servers", Name: "servers", Node: serverRef, ParentNode: httpMethodProp, Path: fmt.Sprintf("$.paths['%s'].%s.servers[%d]", pathItemNode.Value, prop.Value, i), } 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.paramInlineDuplicateNames[pName] = append(index.paramInlineDuplicateNames[pName], pValue...) for i := range pValue { if pValue[i] != nil { _, in := utils.FindKeyNodeTop("in", pValue[i].Node.Content) if in != nil { index.paramAllRefs[fmt.Sprintf("%s:::%s:::%s", path, mName, in.Value)] = pValue[i] } else { index.paramAllRefs[fmt.Sprintf("%s:::%s", path, mName)] = pValue[i] } } } } } } } index.operationParamCount = len(index.paramCompRefs) + len(index.paramInlineDuplicateNames) 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.paramInlineDuplicateNames) - 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() } // 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) }