Files
libopenapi/index/spec_index.go
quobix 2bc3c67776 Removed some dead code that does not need to exist
A consequence of the old index design, now gone

Signed-off-by: quobix <dave@quobix.com>
2023-11-01 13:07:53 -04:00

1242 lines
39 KiB
Go

// 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 (
"context"
"fmt"
"golang.org/x/sync/syncmap"
"log/slog"
"os"
"sort"
"strings"
"sync"
"time"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3"
)
// 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)
//if config != nil && config.seenRemoteSources == nil {
// config.seenRemoteSources = &syncmap.Map{}
//}
//config.remoteLock = &sync.Mutex{}
index.config = config
index.rolodex = config.Rolodex
//index.parentIndex = config.ParentIndex
index.uri = config.uri
index.specAbsolutePath = config.SpecAbsolutePath
if rootNode == nil || len(rootNode.Content) <= 0 {
return index
}
if config.Logger != nil {
index.logger = config.Logger
} else {
index.logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelError,
}))
}
boostrapIndexCollections(rootNode, index)
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()
boostrapIndexCollections(rootNode, 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.cache = new(syncmap.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()
}
// do a copy!
//index.config.seenRemoteSources.Range(func(k, v any) bool {
// index.seenRemoteSources[k.(string)] = v.(*yaml.Node)
// return true
//})
return index
}
// BuildIndex will run all of 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) GetSpecAbsolutePath() string {
return index.specAbsolutePath
}
func (index *SpecIndex) GetLogger() *slog.Logger {
return index.logger
}
// GetRootNode returns document root node.
func (index *SpecIndex) GetRootNode() *yaml.Node {
return index.root
}
// 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
}
// 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 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 {
return index.allComponentSchemaDefinitions
}
// 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
}
//// GetAllExternalIndexes will return all indexes for external documents
//func (index *SpecIndex) GetAllExternalIndexes() map[string]*SpecIndex {
// return index.externalSpecIndex
//}
// 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")
// Channel used to receive the result from doSomething function
ch := make(chan string, 1)
// Create a context with a timeout of 5 seconds
ctxTimeout, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
var res []*yaml.Node
doSomething := func(ctx context.Context, ch chan<- string) {
res, _ = links.Find(m.Node)
ch <- m.Definition
}
// Start the doSomething function
go doSomething(ctxTimeout, ch)
select {
case <-ctxTimeout.Done():
fmt.Printf("Global links %d ref: Context cancelled: %v\n", m.Node.Line, ctxTimeout.Err())
case <-ch:
}
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),
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,
}
//index.pathRefsLock.Lock()
if locatedPathRefs[p.Value] == nil {
locatedPathRefs[p.Value] = make(map[string]*Reference)
}
locatedPathRefs[p.Value][ref.Name] = ref
//index.pathRefsLock.Unlock()
// update
opCount++
}
}
}
}
}
index.pathRefsLock.Lock()
for k, v := range locatedPathRefs {
index.pathRefs[k] = v
}
index.pathRefsLock.Unlock()
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]
}
// 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, 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 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)
}