From 57622b26e527c886fe569e53922bc9cd883149af Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 26 Sep 2022 10:52:09 -0400 Subject: [PATCH] Docs, examples and new logo! --- README.md | 356 +++-- index/spec_index.go | 2814 +++++++++++++++++++------------------ index/spec_index_test.go | 37 + libopenapi-logo.png | Bin 0 -> 94065 bytes resolver/resolver_test.go | 32 + 5 files changed, 1744 insertions(+), 1495 deletions(-) create mode 100644 libopenapi-logo.png diff --git a/README.md b/README.md index 7fa377f..c719c5e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![libopenapi](libopenapi-logo.png) + # libopenapi - enterprise grade OpenAPI tools for golang. ![Pipeline](https://github.com/pb33f/libopenapi/workflows/Build/badge.svg) @@ -49,10 +51,11 @@ Want a lightning fast way to look up any element in an OpenAPI swagger spec? **l Need a way to 'resolve' OpenAPI documents that are exploded out across multiple files, remotely or locally? **libopenapi has you covered** +> **Read the full docs at [https://pkg.go.dev](https://pkg.go.dev/github.com/pb33f/libopenapi)** + --- -## Some examples to get you started. - +## Installing Grab the latest release of **libopenapi** ``` @@ -62,34 +65,40 @@ go get github.com/pb33f/libopenapi ### Load an OpenAPI 3+ spec into a model ```go -// load an OpenAPI 3 specification from bytes -petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") +// import the library +import "github.com/pb33f/libopenapi" -// create a new document from specification bytes -document, err := NewDocument(petstore) - -// if anything went wrong, an error is thrown -if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) -} - -// because we know this is a v3 spec, we can build a ready to go model from it. -v3Model, errors := document.BuildV3Model() - -// if anything went wrong when building the v3 model, a slice of errors will be returned -if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) +func readSpec() { + + // load an OpenAPI 3 specification from bytes + petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") + + // create a new document from specification bytes + document, err := NewDocument(petstore) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + + // because we know this is a v3 spec, we can build a ready to go model from it. + v3Model, errors := document.BuildV3Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + } + + // get a count of the number of paths and schemas. + paths := len(v3Model.Model.Paths.PathItems) + schemas := len(v3Model.Model.Components.Schemas) + + // print the number of paths and schemas in the document + fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) } - -// get a count of the number of paths and schemas. -paths := len(v3Model.Model.Paths.PathItems) -schemas := len(v3Model.Model.Components.Schemas) - -// print the number of paths and schemas in the document -fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) ``` This will print: @@ -101,34 +110,40 @@ There are 13 paths and 8 schemas in the document ### Load a Swagger (OpenAPI 2) spec into a model ```go -// load a Swagger specification from bytes -petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") +// import the library +import "github.com/pb33f/libopenapi" -// create a new document from specification bytes -document, err := NewDocument(petstore) +func readSpec() { -// if anything went wrong, an error is thrown -if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) -} - -// because we know this is a v2 spec, we can build a ready to go model from it. -v2Model, errors := document.BuildV2Model() - -// if anything went wrong when building the v3 model, a slice of errors will be returned -if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) + // load a Swagger specification from bytes + petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") + + // create a new document from specification bytes + document, err := NewDocument(petstore) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + + // because we know this is a v2 spec, we can build a ready to go model from it. + v2Model, errors := document.BuildV2Model() + + // if anything went wrong when building the v3 model, a slice of errors will be returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) + } + + // get a count of the number of paths and schemas. + paths := len(v2Model.Model.Paths.PathItems) + schemas := len(v2Model.Model.Definitions.Definitions) + + // print the number of paths and schemas in the document + fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) } - -// get a count of the number of paths and schemas. -paths := len(v2Model.Model.Paths.PathItems) -schemas := len(v2Model.Model.Definitions.Definitions) - -// print the number of paths and schemas in the document -fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) ``` This will print: @@ -170,14 +185,9 @@ fmt.Printf("value is %s, the value is on line %d, " + lowReqBody.Description.KeyNode.Line, lowReqBody.KeyNode.Column) ``` - -The library heavily depends on the fantastic (yet hard to get used to) [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node). -This is what is exposed by the `GoLow` API. It does not matter if the input material is JSON or YAML, the yaml.Node API -creates a great way to navigate the AST of the document. - --- -## But wait, there's more! +## But wait, there's more - Mutating the model Having a read-only model is great, but what about when we want to modify the model and mutate values, or even add new content to the model? What if we also want to save that output as an updated specification - but we don't want to jumble up @@ -199,8 +209,8 @@ tree in-tact. It allows us to make changes to values in place, and serialize bac other content order. ```go - // create very small, and useless spec that does nothing useful, except showcase this feature. - spec := ` +// create very small, and useless spec that does nothing useful, except showcase this feature. +spec := ` openapi: 3.1.0 info: title: This is a title @@ -210,48 +220,47 @@ info: license: url: http://some-place-on-the-internet.com/license ` - // create a new document from specification bytes - document, err := NewDocument([]byte(spec)) +// create a new document from specification bytes +document, err := NewDocument([]byte(spec)) - // if anything went wrong, an error is thrown - if err != nil { - panic(fmt.Sprintf("cannot create new document: %e", err)) - } +// if anything went wrong, an error is thrown +if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) +} - // because we know this is a v3 spec, we can build a ready to go model from it. - v3Model, errors := document.BuildV3Model() +// because we know this is a v3 spec, we can build a ready to go model from it. +v3Model, errors := document.BuildV3Model() - // if anything went wrong when building the v3 model, a slice of errors will be returned - if len(errors) > 0 { - for i := range errors { - fmt.Printf("error: %e\n", errors[i]) - } - panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) - } +// if anything went wrong when building the v3 model, a slice of errors will be returned +if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %e\n", errors[i]) + } + panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors))) +} - // mutate the title, to do this we currently need to drop down to the low-level API. - v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec") +// mutate the title, to do this we currently need to drop down to the low-level API. +v3Model.Model.GoLow().Info.Value.Title.Mutate("A new title for a useless spec") - // mutate the email address in the contact object. - v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io") +// mutate the email address in the contact object. +v3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate("buckaroo@pb33f.io") - // mutate the name in the contact object. - v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo") +// mutate the name in the contact object. +v3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate("Buckaroo") - // mutate the URL for the license object. - v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license") +// mutate the URL for the license object. +v3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate("https://pb33f.io/license") - // serialize the document back into the original YAML or JSON - mutatedSpec, serialError := document.Serialize() +// serialize the document back into the original YAML or JSON +mutatedSpec, serialError := document.Serialize() - // if something went wrong serializing - if serialError != nil { - panic(fmt.Sprintf("cannot serialize document: %e", serialError)) - } - - // print our modified spec! - fmt.Println(string(mutatedSpec)) +// if something went wrong serializing +if serialError != nil { + panic(fmt.Sprintf("cannot serialize document: %e", serialError)) +} +// print our modified spec! +fmt.Println(string(mutatedSpec)) ``` Which will output: @@ -267,7 +276,168 @@ info: url: https://pb33f.io/license ``` +> It's worth noting that the original line numbers and column numbers **won't be respected** when calling `Serialize()`, +> A new `Document` needs to be created from that raw YAML to continue processing after serialization. + +## Creating an index of an OpenAPI Specification + +An index is really useful when a map of an OpenAPI spec is needed. Knowing where all the references are and where +they point, is very useful when resolving specifications, or just looking things up. + +### Creating an index from the Stripe OpenAPI Spec + +```go +// define a rootNode to hold our raw stripe spec AST. +var rootNode yaml.Node + +// load in the stripe OpenAPI specification into bytes (it's pretty meaty) +stripeSpec, _ := ioutil.ReadFile("test_specs/stripe.yaml") + +// unmarshal spec into our rootNode +yaml.Unmarshal(stripeSpec, &rootNode) + +// create a new specification index. +index := NewSpecIndex(&rootNode) + +// print out some statistics +fmt.Printf("There are %d references\n"+ + "%d paths\n"+ + "%d operations\n"+ + "%d schemas\n"+ + "%d enums\n"+ + "%d polymorphic references", + len(index.GetAllCombinedReferences()), + len(index.GetAllPaths()), + index.GetOperationCount(), + len(index.GetAllSchemas()), + len(index.GetAllEnums()), + len(index.GetPolyOneOfReferences())+len(index.GetPolyAnyOfReferences())) +``` + +## Resolving an OpenAPI Specification + +When creating an index, the raw AST that uses [yaml.Node](https://pkg.go.dev/gopkg.in/yaml.v3#Node) is preserved +when looking up local, file-based and remote references. This means that if required, the spec can be 'resolved' +and all the reference nodes will be replaced with the actual data they reference. + +What this looks like from a spec perspective. + +If the specification looks like this: + +```yaml +paths: + "/some/path/to/a/thing": + get: + responses: + "200": + $ref: '#/components/schemas/MySchema' +components: + schemas: + MySchema: + type: string + description: This is my schema that is great! +``` + +Will become this (as represented by the root [yaml.Node](https://pkg.go.dev/gopkg.in/yaml.v3#Node) + +```yaml +paths: + "/some/path/to/a/thing": + get: + responses: + "200": + type: string + description: This is my schema that is great! +components: + schemas: + MySchema: + type: string + description: This is my schema that is great! +``` +> This is not a valid spec, it's just design to illustrate how resolving works. + +The reference has been 'resolved', so when reading the raw AST, there is no lookup required anymore. + +### Resolving Example: + +Using the Stripe API as an example, we can resolve all references, and then count how many circular reference issues +were found. + +```go +// create a yaml.Node reference as a root node. +var rootNode yaml.Node + +// load in the Stripe OpenAPI spec (lots of polymorphic complexity in here) +stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + +// unmarshal bytes into our rootNode. +_ = yaml.Unmarshal(stripeBytes, &rootNode) + +// create a new spec index (resolver depends on it) +index := index.NewSpecIndex(&rootNode) + +// create a new resolver using the index. +resolver := NewResolver(index) + +// resolve the document, if there are circular reference errors, they are returned/ +// WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved +circularErrors := resolver.Resolve() + +// The Stripe API has a bunch of circular reference problems, mainly from polymorphism. +// So let's print them out. +fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not", +len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors())) +``` + +This will output: + +`There are 21 circular reference errors, 19 of them are polymorphic errors, 2 are not` + +> Important to remember: Resolving is **destructive** and will permanently change the tree, it cannot be un-resolved. + +### Checking for circular errors without resolving + +Resolving is destructive, the original reference nodes are gone and all replaced, so how do we check for circular references +in a non-destructive way? Instead of calling `Resolve()`, we can call `CheckForCircularReferences()` instead. + +The same code as `Resolve()` executes, except the tree is **not actually resolved**, _nothing_ changes and _no destruction_ +occurs. A handy way to perform circular reference analysis on the specification, without permanently altering it. + +```go +// create a yaml.Node reference as a root node. +var rootNode yaml.Node + +// load in the Stripe OpenAPI spec (lots of polymorphic complexity in here) +stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + +// unmarshal bytes into our rootNode. +_ = yaml.Unmarshal(stripeBytes, &rootNode) + +// create a new spec index (resolver depends on it) +index := index.NewSpecIndex(&rootNode) + +// create a new resolver using the index. +resolver := NewResolver(index) + +// extract circular reference errors without any changes to the original tree. +circularErrors := resolver.CheckForCircularReferences() + +// The Stripe API has a bunch of circular reference problems, mainly from polymorphism. +// So let's print them out. +fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not", +len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors())) +``` + +--- + +> **Read the full docs at [https://pkg.go.dev](https://pkg.go.dev/github.com/pb33f/libopenapi)** + +--- The library heavily depends on the fantastic (yet hard to get used to) [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node). -This is what is exposed by the `GoLow` API. It does not matter if the input material is JSON or YAML, the yaml.Node API -creates a great way to navigate the AST of the document. \ No newline at end of file +This is what is exposed by the `GoLow` API. + +> It does not matter if the input material is JSON or YAML, the [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node) supports both and +> creates a great way to navigate the AST of the document. + +Logo gopher is modified, originally from [egonelbre](https://github.com/egonelbre/gophers) \ No newline at end of file diff --git a/index/spec_index.go b/index/spec_index.go index 719ccf4..d4730d0 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -1,144 +1,154 @@ // Copyright 2022 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 ( - "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" + "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" ) +// Constants used to determine if resolving is local, file based or remote file based. const ( - LocalResolve int = 0 - HttpResolve int = 1 - FileResolve int = 2 + 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. + 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. } // ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key) type ReferenceMapped struct { - Reference *Reference - Definition string + 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 - 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. + 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 + 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. } // ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the @@ -147,34 +157,34 @@ type ExternalLookupFunction func(id string) (foundNode *yaml.Node, rootNode *yam // IndexingError holds data about something that went wrong during indexing. type IndexingError struct { - Error error - Node *yaml.Node - Path string + 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 + Content string + Path string + Node *yaml.Node + IsSummary bool } type EnumReference struct { - Node *yaml.Node - Type *yaml.Node - Path string + 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) - } + 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 @@ -182,162 +192,162 @@ func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) { // 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) + 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 - } + // 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, "") + // 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() + // 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, - } + 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() + 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, - } + // 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() + 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() + // these have final calculation dependencies + index.GetInlineDuplicateParamCount() + index.GetAllDescriptionsCount() + index.GetTotalTagsCount() - return index + return index } // GetRootNode returns document root node. func (index *SpecIndex) GetRootNode() *yaml.Node { - return index.root + return index.root } // GetGlobalTagsNode returns document root node. func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node { - return index.tagsNode + 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 + index.circularReferences = refs } // GetCircularReferences will return any circular reference results that were found by the resolver. func (index *SpecIndex) GetCircularReferences() []*CircularReferenceResult { - return index.circularReferences + return index.circularReferences } // GetPathsNode returns document root node. func (index *SpecIndex) GetPathsNode() *yaml.Node { - return index.pathsNode + return index.pathsNode } // GetDiscoveredReferences will return all unique references found in the spec func (index *SpecIndex) GetDiscoveredReferences() map[string]*Reference { - return index.allRefs + return index.allRefs } // GetPolyReferences will return every polymorphic reference in the doc func (index *SpecIndex) GetPolyReferences() map[string]*Reference { - return index.polymorphicRefs + return index.polymorphicRefs } // GetPolyAllOfReferences will return every 'allOf' polymorphic reference in the doc func (index *SpecIndex) GetPolyAllOfReferences() []*Reference { - return index.polymorphicAllOfRefs + return index.polymorphicAllOfRefs } // GetPolyAnyOfReferences will return every 'anyOf' polymorphic reference in the doc func (index *SpecIndex) GetPolyAnyOfReferences() []*Reference { - return index.polymorphicAnyOfRefs + return index.polymorphicAnyOfRefs } // GetPolyOneOfReferences will return every 'allOf' polymorphic reference in the doc func (index *SpecIndex) GetPolyOneOfReferences() []*Reference { - return index.polymorphicOneOfRefs + 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 + 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 + return index.refsByLine } // GetLinesWithReferences will return a map of lines that have a $ref func (index *SpecIndex) GetLinesWithReferences() map[int]bool { - return index.linesWithRefs + return index.linesWithRefs } // GetMappedReferences will return all references that were mapped successfully to actual property nodes. @@ -345,1626 +355,1626 @@ func (index *SpecIndex) GetLinesWithReferences() map[int]bool { // 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 + 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 + 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 + return index.paramOpRefs } // GetAllSchemas will return all schemas found in the document func (index *SpecIndex) GetAllSchemas() map[string]*Reference { - return index.allSchemas + return index.allSchemas } // GetAllSecuritySchemes will return all security schemes / definitions found in the document. func (index *SpecIndex) GetAllSecuritySchemes() map[string]*Reference { - return index.allSecuritySchemes + return index.allSecuritySchemes } // GetAllHeaders will return all headers found in the document (under components) func (index *SpecIndex) GetAllHeaders() map[string]*Reference { - return index.allHeaders + return index.allHeaders } // GetAllExternalDocuments will return all external documents found func (index *SpecIndex) GetAllExternalDocuments() map[string]*Reference { - return index.allExternalDocuments + return index.allExternalDocuments } // GetAllExamples will return all examples found in the document (under components) func (index *SpecIndex) GetAllExamples() map[string]*Reference { - return index.allExamples + return index.allExamples } // GetAllDescriptions will return all descriptions found in the document func (index *SpecIndex) GetAllDescriptions() []*DescriptionReference { - return index.allDescriptions + return index.allDescriptions } // GetAllEnums will return all enums found in the document func (index *SpecIndex) GetAllEnums() []*EnumReference { - return index.allEnums + return index.allEnums } // GetAllSummaries will return all summaries found in the document func (index *SpecIndex) GetAllSummaries() []*DescriptionReference { - return index.allSummaries + return index.allSummaries } // GetAllRequestBodies will return all requestBodies found in the document (under components) func (index *SpecIndex) GetAllRequestBodies() map[string]*Reference { - return index.allRequestBodies + return index.allRequestBodies } // GetAllLinks will return all links found in the document (under components) func (index *SpecIndex) GetAllLinks() map[string]*Reference { - return index.allLinks + return index.allLinks } // GetAllParameters will return all parameters found in the document (under components) func (index *SpecIndex) GetAllParameters() map[string]*Reference { - return index.allParameters + return index.allParameters } // GetAllResponses will return all responses found in the document (under components) func (index *SpecIndex) GetAllResponses() map[string]*Reference { - return index.allResponses + return index.allResponses } // GetAllCallbacks will return all links found in the document (under components) func (index *SpecIndex) GetAllCallbacks() map[string]*Reference { - return index.allCallbacks + return index.allCallbacks } // GetInlineOperationDuplicateParameters will return a map of duplicates located in operation parameters. func (index *SpecIndex) GetInlineOperationDuplicateParameters() map[string][]*Reference { - return index.paramInlineDuplicates + 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 + 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 + return index.allRefs } // GetAllSequencedReferences will return every reference (in sequence) that was found (non-polymorphic) func (index *SpecIndex) GetAllSequencedReferences() []*Reference { - return index.rawSequencedRefs + return index.rawSequencedRefs } // GetSchemasNode will return the schema's node found in the spec func (index *SpecIndex) GetSchemasNode() *yaml.Node { - return index.schemasNode + return index.schemasNode } // GetParametersNode will return the schema's node found in the spec func (index *SpecIndex) GetParametersNode() *yaml.Node { - return index.parametersNode + return index.parametersNode } // GetReferenceIndexErrors will return any errors that occurred when indexing references func (index *SpecIndex) GetReferenceIndexErrors() []*IndexingError { - return index.refErrors + return index.refErrors } // GetOperationParametersIndexErrors any errors that occurred when indexing operation parameters func (index *SpecIndex) GetOperationParametersIndexErrors() []*IndexingError { - return index.operationParamErrors + return index.operationParamErrors } // GetAllPaths will return all paths indexed in the document func (index *SpecIndex) GetAllPaths() map[string]map[string]*Reference { - return index.pathRefs + 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 + 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 + return index.paramOpRefs } // GetRootSecurityReferences will return all root security settings func (index *SpecIndex) GetRootSecurityReferences() []*Reference { - return index.rootSecurity + return index.rootSecurity } // GetRootSecurityNode will return the root security node func (index *SpecIndex) GetRootSecurityNode() *yaml.Node { - return index.rootSecurityNode + return index.rootSecurityNode } // GetRootServersNode will return the root servers node func (index *SpecIndex) GetRootServersNode() *yaml.Node { - return index.rootServersNode + return index.rootServersNode } // GetAllRootServers will return all root servers defined func (index *SpecIndex) GetAllRootServers() []*Reference { - return index.serversRefs + return index.serversRefs } // GetAllOperationsServers will return all operation overrides for servers. func (index *SpecIndex) GetAllOperationsServers() map[string]map[string][]*Reference { - return index.opServersRefs + return index.opServersRefs } // GetAllExternalIndexes will return all indexes for external documents func (index *SpecIndex) GetAllExternalIndexes() map[string]*SpecIndex { - return index.externalSpecIndex + 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 + 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 + 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, "" + 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 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 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" { + 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 - } + // only look at scalar values, not maps (looking at you k8s) + if !utils.IsNodeStringValue(node.Content[i+1]) { + continue + } - index.linesWithRefs[n.Line] = true + index.linesWithRefs[n.Line] = true - fp := make([]string, len(seenPath)) - for x, foundPathNode := range seenPath { - fp[x] = foundPathNode - } + fp := make([]string, len(seenPath)) + for x, foundPathNode := range seenPath { + fp[x] = foundPathNode + } - value := node.Content[i+1].Value + 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, ".")), - } + 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 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 - } + // 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 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 + // 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 - } + // 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 - } + // 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 == "" { + if value == "" { - completedPath := fmt.Sprintf("$.%s", strings.Join(fp, ".")) + 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, - } + 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) + index.refErrors = append(index.refErrors, indexError) - continue - } + continue + } - index.allRefs[value] = ref - found = append(found, ref) - } + index.allRefs[value] = ref + found = append(found, ref) + } - if i%2 == 0 && n.Value != "$ref" && n.Value != "" { + if i%2 == 0 && n.Value != "$ref" && n.Value != "" { - nodePath := fmt.Sprintf("$.%s", strings.Join(seenPath, ".")) + nodePath := fmt.Sprintf("$.%s", strings.Join(seenPath, ".")) - // capture descriptions and summaries - if n.Value == "description" { + // capture descriptions and summaries + if n.Value == "description" { - // if the parent is a sequence, ignore. - if utils.IsNodeArray(node) { - continue - } + // 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, - } + 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++ - } + index.allDescriptions = append(index.allDescriptions, ref) + index.descriptionCount++ + } - if n.Value == "summary" { + if n.Value == "summary" { - var b *yaml.Node - b = node.Content[i+1] - ref := &DescriptionReference{ - Content: b.Value, - Path: nodePath, - Node: b, - IsSummary: true, - } + var b *yaml.Node + b = node.Content[i+1] + ref := &DescriptionReference{ + Content: b.Value, + Path: nodePath, + Node: b, + IsSummary: true, + } - index.allSummaries = append(index.allSummaries, ref) - index.summaryCount++ - } + index.allSummaries = append(index.allSummaries, ref) + index.summaryCount++ + } - // capture enums - if n.Value == "enum" { + // 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) + // 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, - } + if enumKeyValueNode != nil { + ref := &EnumReference{ + Path: nodePath, + Node: node.Content[i+1], + Type: enumKeyValueNode, + } - index.allEnums = append(index.allEnums, ref) - index.enumCount++ - } - } + index.allEnums = append(index.allEnums, ref) + index.enumCount++ + } + } - seenPath = append(seenPath, n.Value) - prev = n.Value - } + 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 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 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] - } + } + if len(seenPath) > 0 { + seenPath = seenPath[:len(seenPath)-1] + } - index.refCount = len(index.allRefs) + index.refCount = len(index.allRefs) - return found + 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.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 + 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 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 + 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.root == nil { + return -1 + } - if index.globalTagsCount > 0 { - return index.globalTagsCount - } + 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 { + 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) + _, 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 + 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.root == nil { + return -1 + } - if index.operationTagsCount > 0 { - return index.operationTagsCount - } + 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 + // 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 - } + if index.root == nil { + return -1 + } + if index.totalTagsCount > 0 { + return index.totalTagsCount + } - seen := make(map[string]bool) - count := 0 + 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 + 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.root == nil { + return -1 + } - if index.globalCallbacksCount > 0 { - return index.globalCallbacksCount - } + if index.globalCallbacksCount > 0 { + return index.globalCallbacksCount + } - //index.pathRefsLock.Lock() - for path, p := range index.pathRefs { - for _, m := range p { + //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) + // look through method for callbacks + callbacks, _ := yamlpath.NewPath("$..callbacks") + res, _ := callbacks.Find(m.Node) - if len(res) > 0 { + if len(res) > 0 { - for _, callback := range res[0].Content { - if utils.IsNodeMap(callback) { + for _, callback := range res[0].Content { + if utils.IsNodeMap(callback) { - ref := &Reference{ - Definition: m.Name, - Name: m.Name, - Node: 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.Unlock() - return index.globalCallbacksCount + 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.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.root == nil { + return -1 + } - if index.globalLinksCount > 0 { - return index.globalLinksCount - } + if index.globalLinksCount > 0 { + return index.globalLinksCount + } - //index.pathRefsLock.Lock() - for path, p := range index.pathRefs { - for _, m := range p { + //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) + // look through method for links + links, _ := yamlpath.NewPath("$..links") + res, _ := links.Find(m.Node) - if len(res) > 0 { + if len(res) > 0 { - for _, link := range res[0].Content { - if utils.IsNodeMap(link) { + 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 + 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) + 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.root == nil { + return -1 + } - if index.schemaCount > 0 { - return index.schemaCount - } + if index.schemaCount > 0 { + return index.schemaCount + } - for i, n := range index.root.Content[0].Content { - if i%2 == 0 { + 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) - } - } - } + // 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) - } - } - } - } + // 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) + 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) + // 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 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 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 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 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 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 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 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 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 - } + // 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 { + // 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 - } - } + // 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 { + // 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 - } - } + // extract params + index.extractComponentParameters(parametersNode, "#/parameters/") + index.parametersNode = parametersNode + } + } - if n.Value == "responses" { - responsesNode := index.root.Content[0].Content[i+1] - if responsesNode != nil { + if n.Value == "responses" { + responsesNode := index.root.Content[0].Content[i+1] + if responsesNode != nil { - // extract responses - index.extractComponentResponses(responsesNode, "#/responses/") - index.responsesNode = responsesNode - } - } + // extract responses + index.extractComponentResponses(responsesNode, "#/responses/") + index.responsesNode = responsesNode + } + } - if n.Value == "securityDefinitions" { - securityDefinitionsNode := index.root.Content[0].Content[i+1] - if securityDefinitionsNode != nil { + 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 - } - } + // extract security definitions. + index.extractComponentSecuritySchemes(securityDefinitionsNode, "#/securityDefinitions/") + index.securitySchemesNode = securityDefinitionsNode + } + } - } - } - return index.schemaCount + } + } + return index.schemaCount } // GetComponentParameterCount returns the number of parameter components defined func (index *SpecIndex) GetComponentParameterCount() int { - if index.root == nil { - return -1 - } + if index.root == nil { + return -1 + } - if index.componentParamCount > 0 { - return index.componentParamCount - } + 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 + 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.root == nil { + return -1 + } - if index.pathsNode == nil { - return -1 - } + if index.pathsNode == nil { + return -1 + } - if index.operationCount > 0 { - return index.operationCount - } + if index.operationCount > 0 { + return index.operationCount + } - opCount := 0 + opCount := 0 - for x, p := range index.pathsNode.Content { - if x%2 == 0 { + for x, p := range index.pathsNode.Content { + if x%2 == 0 { - method := index.pathsNode.Content[x+1] + method := index.pathsNode.Content[x+1] - // extract methods for later use. - for y, m := range method.Content { - if y%2 == 0 { + // 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++ - } - } - } - } - } + // 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 + 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.root == nil { + return -1 + } - if index.pathsNode == nil { - return -1 - } + if index.pathsNode == nil { + return -1 + } - if index.operationParamCount > 0 { - return index.operationParamCount - } + 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 { + // 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] + pathPropertyNode := index.pathsNode.Content[x+1] - // extract methods for later use. - for y, prop := range pathPropertyNode.Content { - if y%2 == 0 { + // 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 - } + // 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" { + // 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") - } + // 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) { + // 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) - } + 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] + // 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) - } + 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 - } + 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) - } + // 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], - } + 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) - } + if index.operationSummaryRefs[pathItemNode.Value] == nil { + index.operationSummaryRefs[pathItemNode.Value] = make(map[string]*Reference) + } - index.operationSummaryRefs[pathItemNode.Value][prop.Value] = ref - } + index.operationSummaryRefs[pathItemNode.Value][prop.Value] = ref + } - // extract servers from method operation. - if httpMethodProp.Value == "servers" { - serversNode := pathPropertyNode.Content[y+1].Content[z+1] + // 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) - } + 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) - } + if index.opServersRefs[pathItemNode.Value] == nil { + index.opServersRefs[pathItemNode.Value] = make(map[string][]*Reference) + } - index.opServersRefs[pathItemNode.Value][prop.Value] = serverRefs - } + 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 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 - } - } - } - } + //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 + 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 + 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() + 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 { + 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 - } + // 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 { + 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 + _, 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 - } + if index.root == nil { + return nil + } - remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { - return index.lookupRemoteReference(id) - } + 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) - } + 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) + 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 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 + 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) + return len(index.allDescriptions) } // GetAllSummariesCount will collect together every single summary found in the document func (index *SpecIndex) GetAllSummariesCount() int { - return len(index.allSummaries) + 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 + 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 - } + 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 - } + 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 - } + 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 - } + 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 - } + 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 - } + 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 - } + 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 - } + 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 - } + 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 { + lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference { - if len(uri) > 0 { - externalSpecIndex := index.externalSpecIndex[uri[0]] - var foundNode *yaml.Node - if externalSpecIndex == nil { + if len(uri) > 0 { + externalSpecIndex := index.externalSpecIndex[uri[0]] + var foundNode *yaml.Node + if externalSpecIndex == nil { - n, newRoot, err := lookupFunction(componentId) + 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 err != nil { + indexError := &IndexingError{ + Error: err, + Node: parent, + Path: componentId, + } + index.refErrors = append(index.refErrors, indexError) + return nil + } - if n != nil { - foundNode = n - } + 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 + // 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 { + } else { - foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) - if foundRef != nil { - foundNode = foundRef.Node - } - } + 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 + 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) - friendlySearch = strings.ReplaceAll(friendlySearch, "~1", "/") - path, _ := yamlpath.NewPath(friendlySearch) - res, _ := path.Find(index.root) + name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) + friendlySearch = strings.ReplaceAll(friendlySearch, "~1", "/") + path, _ := yamlpath.NewPath(friendlySearch) + res, _ := path.Find(index.root) - if len(res) == 1 { - ref := &Reference{ - Definition: componentId, - Name: name, - Node: res[0], - } + if len(res) == 1 { + ref := &Reference{ + Definition: componentId, + Name: name, + Node: res[0], + } - return ref - } - return nil + 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 + 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 { + for i, param := range params { - // param is ref - if len(param.Content) > 0 && param.Content[0].Value == "$ref" { + // param is ref + if len(param.Content) > 0 && param.Content[0].Value == "$ref" { - paramRefName := param.Content[1].Value - paramRef := index.allMappedRefs[paramRefName] + 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 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 + } + // 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 { + } else { - //param is inline. - _, vn := utils.FindKeyNode("name", param.Content) + //param is inline. + _, vn := utils.FindKeyNode("name", param.Content) - if vn == nil { + 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) - } + 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 - } + 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) - } + 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 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) - } + // 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 - } - } + 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 + 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, "#") + // 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 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() - } + 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() + } - // lookup item from reference by using a path query. - query := fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) + // 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", "/") + // 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, nil, nil + 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, nil, nil } func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) { - // split string to remove file reference - uri := strings.Split(ref, "#") + // 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) - } + if len(uri) != 2 { + return nil, nil, fmt.Errorf("unable to determine filename for file reference: '%s'", ref) + } - file := strings.ReplaceAll(uri[0], "file:", "") + file := strings.ReplaceAll(uri[0], "file:", "") - var parsedRemoteDocument *yaml.Node - if index.seenRemoteSources[file] != nil { - parsedRemoteDocument = index.seenRemoteSources[file] - } else { + 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 - } + 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 - } + var remoteDoc yaml.Node + err = yaml.Unmarshal(body, &remoteDoc) + if err != nil { + return nil, nil, err + } + parsedRemoteDocument = &remoteDoc + index.seenRemoteSources[file] = &remoteDoc + } - // lookup item from reference by using a path query. - query := fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) + // 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", "/") + // 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 - } + 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 + return nil, parsedRemoteDocument, nil } diff --git a/index/spec_index_test.go b/index/spec_index_test.go index 5b3f974..5b49158 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -4,6 +4,7 @@ package index import ( + "fmt" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" "io/ioutil" @@ -572,3 +573,39 @@ func TestSpecIndex_lookupFileReference(t *testing.T) { assert.NotNil(t, k) } + +// Example of how to load in an OpenAPI Specification and index it. +func ExampleNewSpecIndex() { + + // define a rootNode to hold our raw spec AST. + var rootNode yaml.Node + + // load in the stripe OpenAPI specification into bytes (it's pretty meaty) + stripeSpec, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + + // unmarshal spec into our rootNode + yaml.Unmarshal(stripeSpec, &rootNode) + + // create a new specification index. + index := NewSpecIndex(&rootNode) + + // print out some statistics + fmt.Printf("There are %d references\n"+ + "%d paths\n"+ + "%d operations\n"+ + "%d schemas\n"+ + "%d enums\n"+ + "%d polymorphic references", + len(index.GetAllCombinedReferences()), + len(index.GetAllPaths()), + index.GetOperationCount(), + len(index.GetAllSchemas()), + len(index.GetAllEnums()), + len(index.GetPolyOneOfReferences())+len(index.GetPolyAnyOfReferences())) + // Output: There are 537 references + // 246 paths + // 402 operations + // 537 schemas + // 1516 enums + // 828 polymorphic references +} diff --git a/libopenapi-logo.png b/libopenapi-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..78bf7e5fc62fea7556372f62e137b3bcfe1caac6 GIT binary patch literal 94065 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 literal 0 HcmV?d00001 diff --git a/resolver/resolver_test.go b/resolver/resolver_test.go index 45914b8..275a71e 100644 --- a/resolver/resolver_test.go +++ b/resolver/resolver_test.go @@ -1,6 +1,7 @@ package resolver import ( + "fmt" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" @@ -173,3 +174,34 @@ func TestResolver_ResolveComponents_k8s(t *testing.T) { circ := resolver.Resolve() assert.Len(t, circ, 1) } + +// Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors +func ExampleNewResolver() { + + // create a yaml.Node reference as a root node. + var rootNode yaml.Node + + // load in the Stripe OpenAPI spec (lots of polymorphic complexity in here) + stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + + // unmarshal bytes into our rootNode. + _ = yaml.Unmarshal(stripeBytes, &rootNode) + + // create a new spec index (resolver depends on it) + index := index.NewSpecIndex(&rootNode) + + // create a new resolver using the index. + resolver := NewResolver(index) + + // resolve the document, if there are circular reference errors, they are returned/ + // WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved + circularErrors := resolver.Resolve() + + // The Stripe API has a bunch of circular reference problems, mainly from polymorphism. + // So let's print them out. + // + fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not", + len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors())) + // Output: There are 21 circular reference errors, 19 of them are polymorphic errors, 2 are not + +} \ No newline at end of file