mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-07 20:47:45 +00:00
Different race conditions popped up at different times, depending on how complex the nesting of indexes was. After running tests with `-race` I've knocked out all the concurrency issues being reported and now there are no race conditions reported by the tests. This should knock out all known race conditions with some targeted mutex locks. Non breaking change.
274 lines
18 KiB
Go
274 lines
18 KiB
Go
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package index
|
|
|
|
import (
|
|
"golang.org/x/sync/syncmap"
|
|
"gopkg.in/yaml.v3"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// Constants used to determine if resolving is local, file based or remote file based.
|
|
const (
|
|
LocalResolve = iota
|
|
HttpResolve
|
|
FileResolve
|
|
)
|
|
|
|
// 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.
|
|
RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
|
|
}
|
|
|
|
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
|
|
type ReferenceMapped struct {
|
|
Reference *Reference
|
|
Definition string
|
|
}
|
|
|
|
// SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable
|
|
// set of granular options. The first being the ability to set the Base URL for resolving relative references, and
|
|
// allowing or disallowing remote or local file lookups.
|
|
// - https://github.com/pb33f/libopenapi/issues/73
|
|
type SpecIndexConfig struct {
|
|
// The BaseURL will be the root from which relative references will be resolved from if they can't be found locally.
|
|
//
|
|
// For example:
|
|
// - $ref: somefile.yaml#/components/schemas/SomeSchema
|
|
//
|
|
// Might not be found locally, if the file was pulled in from a remote server (a good example is the DigitalOcean API).
|
|
// so by setting a BaseURL, the reference will try to be resolved from the remote server.
|
|
//
|
|
// If our baseURL is set to https://pb33f.io/libopenapi then our reference will try to be resolved from:
|
|
// - $ref: https://pb33f.io/libopenapi/somefile.yaml#/components/schemas/SomeSchema
|
|
//
|
|
// More details on relative references can be found in issue #73: https://github.com/pb33f/libopenapi/issues/73
|
|
BaseURL *url.URL // set the Base URL for resolving relative references if the spec is exploded.
|
|
|
|
// If resolving locally, the BasePath will be the root from which relative references will be resolved from
|
|
BasePath string // set the Base Path for resolving relative references if the spec is exploded.
|
|
|
|
// In an earlier version of libopenapi (pre 0.6.0) the index would automatically resolve all references
|
|
// They could have been local, or they could have been remote. This was a problem because it meant
|
|
// There was a potential for a remote exploit if a remote reference was malicious. There aren't any known
|
|
// exploits, but it's better to be safe than sorry.
|
|
//
|
|
// To read more about this, you can find a discussion here: https://github.com/pb33f/libopenapi/pull/64
|
|
AllowRemoteLookup bool // Allow remote lookups for references. Defaults to false
|
|
AllowFileLookup bool // Allow file lookups for references. Defaults to false
|
|
|
|
// private fields
|
|
seenRemoteSources *syncmap.Map
|
|
remoteLock *sync.Mutex
|
|
}
|
|
|
|
// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
|
// AllowFileLookup set to true. This is the default behaviour of the index in previous versions of libopenapi. (pre 0.6.0)
|
|
//
|
|
// The default BasePath is the current working directory.
|
|
func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
|
cw, _ := os.Getwd()
|
|
return &SpecIndexConfig{
|
|
BasePath: cw,
|
|
AllowRemoteLookup: true,
|
|
AllowFileLookup: true,
|
|
seenRemoteSources: &syncmap.Map{},
|
|
}
|
|
}
|
|
|
|
// CreateClosedAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
|
// AllowFileLookup set to false. This is the default behaviour of the index in versions 0.6.0+
|
|
//
|
|
// The default BasePath is the current working directory.
|
|
func CreateClosedAPIIndexConfig() *SpecIndexConfig {
|
|
cw, _ := os.Getwd()
|
|
return &SpecIndexConfig{
|
|
BasePath: cw,
|
|
AllowRemoteLookup: false,
|
|
AllowFileLookup: false,
|
|
seenRemoteSources: &syncmap.Map{},
|
|
}
|
|
}
|
|
|
|
// 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
|
|
paramInlineDuplicateNames 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
|
|
securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements
|
|
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
|
|
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
|
|
allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger).
|
|
allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger).
|
|
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 []error // errors when indexing references
|
|
operationParamErrors []error // 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.
|
|
allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec.
|
|
enumCount int
|
|
descriptionCount int
|
|
summaryCount int
|
|
seenRemoteSources map[string]*yaml.Node
|
|
seenLocalSources map[string]*yaml.Node
|
|
refLock sync.Mutex
|
|
sourceLock sync.Mutex
|
|
componentLock sync.RWMutex
|
|
externalLock sync.RWMutex
|
|
errorLock sync.RWMutex
|
|
circularReferences []*CircularReferenceResult // only available when the resolver has been used.
|
|
allowCircularReferences bool // decide if you want to error out, or allow circular references, default is false.
|
|
relativePath string // relative path of the spec file.
|
|
config *SpecIndexConfig // configuration for the index
|
|
httpClient *http.Client
|
|
componentIndexChan chan bool
|
|
polyComponentIndexChan chan bool
|
|
|
|
// when things get complex (looking at you digital ocean) then we need to know
|
|
// what we have seen across indexes, so we need to be able to travel back up to the root
|
|
// cto avoid re-downloading sources.
|
|
parentIndex *SpecIndex
|
|
children []*SpecIndex
|
|
}
|
|
|
|
func (index *SpecIndex) AddChild(child *SpecIndex) {
|
|
index.children = append(index.children, child)
|
|
}
|
|
|
|
// GetChildren returns the children of this index.
|
|
func (index *SpecIndex) GetChildren() []*SpecIndex {
|
|
return index.children
|
|
}
|
|
|
|
// 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 {
|
|
Err error
|
|
Node *yaml.Node
|
|
Path string
|
|
}
|
|
|
|
func (i *IndexingError) Error() string {
|
|
return i.Err.Error()
|
|
}
|
|
|
|
// 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
|
|
SchemaNode *yaml.Node
|
|
ParentNode *yaml.Node
|
|
}
|
|
|
|
type ObjectReference struct {
|
|
Node *yaml.Node
|
|
Path string
|
|
ParentNode *yaml.Node
|
|
}
|
|
|
|
var methodTypes = []string{"get", "post", "put", "patch", "options", "head", "delete"}
|