diff --git a/bundler/bundler.go b/bundler/bundler.go index 7bef2678..513e0510 100644 --- a/bundler/bundler.go +++ b/bundler/bundler.go @@ -6,17 +6,35 @@ package bundler import ( "errors" + "fmt" "strings" + "sync" + "github.com/davecgh/go-spew/spew" "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel" - v3 "github.com/pb33f/libopenapi/datamodel/high/v3" + highV3 "github.com/pb33f/libopenapi/datamodel/high/v3" + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/base" + lowV3 "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/index" + "gopkg.in/yaml.v3" ) // ErrInvalidModel is returned when the model is not usable. var ErrInvalidModel = errors.New("invalid model") +type RefHandling string + +const ( + RefHandlingInline RefHandling = "inline" + RefHandlingCompose RefHandling = "compose" +) + +type BundleOptions struct { + RelativeRefHandling RefHandling +} + // BundleBytes will take a byte slice of an OpenAPI specification and return a bundled version of it. // This is useful for when you want to take a specification with external references, and you want to bundle it // into a single document. @@ -25,7 +43,7 @@ var ErrInvalidModel = errors.New("invalid model") // document will be a valid OpenAPI specification, containing no references. // // Circular references will not be resolved and will be skipped. -func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration) ([]byte, error) { +func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration, opts BundleOptions) ([]byte, error) { doc, err := libopenapi.NewDocumentWithConfiguration(bytes, configuration) if err != nil { return nil, err @@ -37,7 +55,12 @@ func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration) ( return nil, errors.Join(ErrInvalidModel, err) } - bundledBytes, e := bundle(&v3Doc.Model, configuration.BundleInlineRefs) + // Overwrite bundle options, if deprecated config field is used. + if configuration.BundleInlineRefs { + opts.RelativeRefHandling = RefHandlingInline + } + + bundledBytes, e := bundle(&v3Doc.Model, opts) return bundledBytes, errors.Join(err, e) } @@ -50,49 +73,144 @@ func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration) ( // document will be a valid OpenAPI specification, containing no references. // // Circular references will not be resolved and will be skipped. -func BundleDocument(model *v3.Document) ([]byte, error) { - return bundle(model, false) +func BundleDocument(model *highV3.Document) ([]byte, error) { + return bundle(model, BundleOptions{RelativeRefHandling: RefHandlingInline}) } -func bundle(model *v3.Document, inline bool) ([]byte, error) { +func bundle(model *highV3.Document, opts BundleOptions) (_ []byte, err error) { rolodex := model.Rolodex - compact := func(idx *index.SpecIndex, root bool) { - mappedReferences := idx.GetMappedReferences() - sequencedReferences := idx.GetRawReferencesSequenced() - for _, sequenced := range sequencedReferences { - mappedReference := mappedReferences[sequenced.FullDefinition] - - // if we're in the root document, don't bundle anything. - refExp := strings.Split(sequenced.FullDefinition, "#/") - if len(refExp) == 2 { - if refExp[0] == sequenced.Index.GetSpecAbsolutePath() || refExp[0] == "" { - if root && !inline { - idx.GetLogger().Debug("[bundler] skipping local root reference", - "ref", sequenced.Definition) - continue - } - } + idx := rolodex.GetRootIndex() + mappedReferences := idx.GetMappedReferences() + sequencedReferences := idx.GetRawReferencesSequenced() + + for _, sequenced := range sequencedReferences { + mappedReference := mappedReferences[sequenced.FullDefinition] + if mappedReference == nil { + return nil, fmt.Errorf("no mapped reference found for: %s", sequenced.FullDefinition) + } + + if mappedReference.DefinitionFile() == idx.GetSpecAbsolutePath() { + // Don't bundle anything that's in the main file. + continue + } + + switch opts.RelativeRefHandling { + case RefHandlingInline: + // Just deal with simple inlining. + sequenced.Node.Content = mappedReference.Node.Content + case RefHandlingCompose: + // Recursively collect all reference targets to be bundled into the root + // file. + bundledComponents := make(map[string]*index.ReferenceNode) + if err := bundleRefTarget(sequenced, mappedReference, bundledComponents, opts); err != nil { + return nil, err } - if mappedReference != nil && !mappedReference.Circular { - sequenced.Node.Content = mappedReference.Node.Content - continue + model, err = composeDocument(model, bundledComponents) + if err != nil { + return nil, err } + } + } + + return model.Render() +} + +func composeDocument(model *highV3.Document, comps map[string]*index.ReferenceNode) (*highV3.Document, error) { + lowModel := model.GoLow() + + components := lowModel.Components + + for def, component := range comps { + defParts := strings.Split(def, "/") + // TODO: use constant from low model labels + if len(defParts) != 4 || defParts[1] != lowV3.ComponentsLabel { + return nil, fmt.Errorf("unsupported component section: %s", def) + } + spew.Dump(component) - if mappedReference != nil && mappedReference.Circular { - if idx.GetLogger() != nil { - idx.GetLogger().Warn("[bundler] skipping circular reference", - "ref", sequenced.FullDefinition) - } + switch defParts[2] { + case "schemas": + key := low.KeyReference[string]{ + Value: defParts[3], + KeyNode: &yaml.Node{ + Kind: yaml.ScalarNode, + Style: yaml.TaggedStyle, + Tag: "!!str", + Value: defParts[3], + }, + } + value := low.ValueReference[*base.SchemaProxy]{ + Reference: low.Reference{}, + Value: &base.SchemaProxy{ + Reference: low.Reference{}, + NodeMap: &low.NodeMap{Nodes: &sync.Map{}}, + }, + ValueNode: &yaml.Node{}, } + components.Value.Schemas.Value.Set(key, value) + + default: + return nil, fmt.Errorf("unsupported component type: %s", defParts[2]) + } + } + + return nil, nil +} + +func bundleRefTarget(ref, mappedRef *index.ReferenceNode, bundledComponents map[string]*index.ReferenceNode, opts BundleOptions) error { + idx := ref.Index + if mappedRef == nil { + if idx.GetLogger() != nil { + idx.GetLogger().Warn("[bundler] skipping unresolved reference", + "ref", ref.FullDefinition) } + return nil } - indexes := rolodex.GetIndexes() - for _, idx := range indexes { - compact(idx, false) + if mappedRef.Circular { + if idx.GetLogger() != nil { + idx.GetLogger().Warn("[bundler] skipping circular reference", + "ref", ref.FullDefinition) + } + return nil } - compact(rolodex.GetRootIndex(), true) - return model.Render() + + bundledRef, exists := bundledComponents[mappedRef.Definition] + if exists && bundledRef.FullDefinition != mappedRef.FullDefinition { + // TODO: we don't want to error here + return fmt.Errorf("duplicate component definition: %s", mappedRef.Definition) + } else { + bundledComponents[mappedRef.Definition] = mappedRef + ref.KeyNode.Value = mappedRef.Definition + } + + // When composing, we need to update the ref values to point to a local reference. At the + // same time we need to track all components referenced by any children of the target, so + // that we can include them in the final document. + // + // One issue we might face is that the name of a target component in any given target + // document is the same as that of another component in a different target document or + // even the root document. + + // Obtain the target's file's index because we should find child references using that. + // Otherwise ExtractRefs will use the ref's index and it's absolute spec path for + // the FullPath of any extracted ref targets. + targetIndex := idx + if targetFile := mappedRef.DefinitionFile(); targetFile != "" { + targetIndex = idx.GetRolodex().GetFileIndex(targetFile) + } + + targetMappedReferences := targetIndex.GetMappedReferences() + + childRefs := targetIndex.ExtractRefs(mappedRef.Node, mappedRef.ParentNode, make([]string, 0), 0, false, "") + for _, childRef := range childRefs { + childRefTarget := targetMappedReferences[childRef.FullDefinition] + if err := bundleRefTarget(childRef, childRefTarget, bundledComponents, opts); err != nil { + return err + } + } + + return nil } diff --git a/bundler/bundler_test.go b/bundler/bundler_test.go index 6ab8bfbe..b0a4dd67 100644 --- a/bundler/bundler_test.go +++ b/bundler/bundler_test.go @@ -19,6 +19,7 @@ import ( "strings" "testing" + "github.com/davecgh/go-spew/spew" "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/utils" @@ -26,6 +27,8 @@ import ( "github.com/stretchr/testify/require" ) +var defaultBundleOpts = BundleOptions{RelativeRefHandling: RefHandlingInline} + func TestBundleDocument_DigitalOcean(t *testing.T) { // test the mother of all exploded specs. @@ -105,7 +108,27 @@ func TestBundleDocument_Circular(t *testing.T) { assert.Len(t, logEntries, 0) } -func TestBundleDocument_MinimalRemoteRefsBundledLocally(t *testing.T) { +func TestBundleDocument_MinimalRemoteRefsBundledLocallyComposed(t *testing.T) { + specBytes, err := os.ReadFile("../test_specs/minimal_remote_refs/openapi.yaml") + require.NoError(t, err) + + require.NoError(t, err) + + config := &datamodel.DocumentConfiguration{ + AllowFileReferences: true, + AllowRemoteReferences: false, + BasePath: "../test_specs/minimal_remote_refs", + BaseURL: nil, + } + require.NoError(t, err) + + bytes, e := BundleBytes(specBytes, config, BundleOptions{RelativeRefHandling: RefHandlingCompose}) + spew.Dump(string(bytes)) + assert.NoError(t, e) + assert.Contains(t, string(bytes), "Name of the account", "should contain all reference targets") +} + +func TestBundleDocument_MinimalRemoteRefsBundledLocallyInline(t *testing.T) { specBytes, err := os.ReadFile("../test_specs/minimal_remote_refs/openapi.yaml") require.NoError(t, err) @@ -114,13 +137,13 @@ func TestBundleDocument_MinimalRemoteRefsBundledLocally(t *testing.T) { config := &datamodel.DocumentConfiguration{ AllowFileReferences: true, AllowRemoteReferences: false, - BundleInlineRefs: false, BasePath: "../test_specs/minimal_remote_refs", BaseURL: nil, } require.NoError(t, err) - bytes, e := BundleBytes(specBytes, config) + bytes, e := BundleBytes(specBytes, config, BundleOptions{RelativeRefHandling: RefHandlingInline}) + spew.Dump(string(bytes)) assert.NoError(t, e) assert.Contains(t, string(bytes), "Name of the account", "should contain all reference targets") } @@ -166,7 +189,7 @@ func TestBundleDocument_MinimalRemoteRefsBundledRemotely(t *testing.T) { } require.NoError(t, err) - bytes, e := BundleBytes(specBytes, config) + bytes, e := BundleBytes(specBytes, config, defaultBundleOpts) assert.NoError(t, e) assert.Contains(t, string(bytes), "Name of the account", "should contain all reference targets") } @@ -185,7 +208,7 @@ func TestBundleBytes(t *testing.T) { })), } - bytes, e := BundleBytes(digi, config) + bytes, e := BundleBytes(digi, config, defaultBundleOpts) assert.Error(t, e) assert.Len(t, bytes, 2016) @@ -214,7 +237,7 @@ components: })), } - _, e := BundleBytes(digi, config) + _, e := BundleBytes(digi, config, defaultBundleOpts) require.Error(t, e) unwrap := utils.UnwrapErrors(e) require.Len(t, unwrap, 2) @@ -270,7 +293,7 @@ components: })), } - bytes, e := BundleBytes(digi, config) + bytes, e := BundleBytes(digi, config, defaultBundleOpts) assert.NoError(t, e) assert.Len(t, bytes, 537) @@ -312,7 +335,7 @@ components: })), } - bytes, e := BundleBytes(digi, config) + bytes, e := BundleBytes(digi, config, defaultBundleOpts) assert.Error(t, e) assert.Len(t, bytes, 458) @@ -321,7 +344,7 @@ components: } func TestBundleBytes_Bad(t *testing.T) { - bytes, e := BundleBytes(nil, nil) + bytes, e := BundleBytes(nil, nil, defaultBundleOpts) assert.Error(t, e) assert.Nil(t, bytes) } @@ -346,7 +369,7 @@ func TestBundleBytes_RootDocumentRefs(t *testing.T) { ExtractRefsSequentially: true, } - bundledSpec, err := BundleBytes(spec, config) + bundledSpec, err := BundleBytes(spec, config, defaultBundleOpts) assert.NoError(t, err) assert.Equal(t, string(spec), string(bundledSpec)) diff --git a/datamodel/document_config.go b/datamodel/document_config.go index caa6e6e1..cba46844 100644 --- a/datamodel/document_config.go +++ b/datamodel/document_config.go @@ -116,6 +116,9 @@ type DocumentConfiguration struct { // BundleInlineRefs is used by the bundler module. If set to true, all references will be inlined, including // local references (to the root document) as well as all external references. This is false by default. + // + // Deprecrated: BundleInlineRefs will be removed in the future in favour of + // [bundler.BundleOpts]. BundleInlineRefs bool } diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index 61a81df1..c9236f45 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -56,8 +56,8 @@ func HashExtensions(ext *orderedmap.Map[KeyReference[string], ValueReference[*ya } // helper function to generate a list of all the things an index should be searched for. -func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference { - return []func() map[string]*index.Reference{ +func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.ReferenceNode { + return []func() map[string]*index.ReferenceNode{ idx.GetAllComponentSchemas, idx.GetMappedReferences, idx.GetAllExternalDocuments, @@ -83,7 +83,7 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S // run through everything and return as soon as we find a match. // this operates as fast as possible as ever collections := generateIndexCollection(idx) - var found map[string]*index.Reference + var found map[string]*index.ReferenceNode for _, collection := range collections { found = collection() if found != nil && found[rv] != nil { diff --git a/datamodel/low/extraction_functions_test.go b/datamodel/low/extraction_functions_test.go index cd501c94..e7ee0e69 100644 --- a/datamodel/low/extraction_functions_test.go +++ b/datamodel/low/extraction_functions_test.go @@ -1929,7 +1929,7 @@ func TestLocateRefNode_DoARealLookup(t *testing.T) { // fake cache to a lookup for a file that does not exist will work. fakeCache := new(sync.Map) - fakeCache.Store(lookup, &index.Reference{Node: &no, Index: idx}) + fakeCache.Store(lookup, &index.ReferenceNode{Node: &no, Index: idx}) idx.SetCache(fakeCache) ctx := context.WithValue(context.Background(), index.CurrentPathKey, lookup) diff --git a/index/circular_reference_result.go b/index/circular_reference_result.go index 858a26bb..49b5565f 100644 --- a/index/circular_reference_result.go +++ b/index/circular_reference_result.go @@ -1,17 +1,18 @@ package index import ( - "gopkg.in/yaml.v3" "strings" + + "gopkg.in/yaml.v3" ) // CircularReferenceResult contains a circular reference found when traversing the graph. type CircularReferenceResult struct { - Journey []*Reference + Journey []*ReferenceNode ParentNode *yaml.Node - Start *Reference + Start *ReferenceNode LoopIndex int - LoopPoint *Reference + LoopPoint *ReferenceNode IsArrayResult bool // if this result comes from an array loop. PolymorphicType string // which type of polymorphic loop is this? (oneOf, anyOf, allOf) IsPolymorphicResult bool // if this result comes from a polymorphic loop. diff --git a/index/circular_reference_result_test.go b/index/circular_reference_result_test.go index 74e7edb7..1900f355 100644 --- a/index/circular_reference_result_test.go +++ b/index/circular_reference_result_test.go @@ -4,13 +4,14 @@ package index import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestCircularReferenceResult_GenerateJourneyPath(t *testing.T) { - refs := []*Reference{ + refs := []*ReferenceNode{ {Name: "chicken"}, {Name: "nuggets"}, {Name: "chicken"}, diff --git a/index/extract_refs.go b/index/extract_refs.go index 7bb9d915..545b49a8 100644 --- a/index/extract_refs.go +++ b/index/extract_refs.go @@ -18,11 +18,11 @@ import ( // 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 { +func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, level int, poly bool, pName string) []*ReferenceNode { if node == nil { return nil } - var found []*Reference + var found []*ReferenceNode if len(node.Content) > 0 { var prev, polyName string for i, n := range node.Content { @@ -57,7 +57,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, _, jsonPath = utils.ConvertComponentIdIntoFriendlyPathSearch(definitionPath) } - ref := &Reference{ + ref := &ReferenceNode{ ParentNode: parent, FullDefinition: fullDefinitionPath, Definition: definitionPath, @@ -134,7 +134,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, fullDefinitionPath = fmt.Sprintf("%s#/%s", index.specAbsolutePath, strings.Join(loc, "/")) _, jsonPath = utils.ConvertComponentIdIntoFriendlyPathSearch(definitionPath) } - ref := &Reference{ + ref := &ReferenceNode{ ParentNode: parent, FullDefinition: fullDefinitionPath, Definition: definitionPath, @@ -182,7 +182,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, _, jsonPath = utils.ConvertComponentIdIntoFriendlyPathSearch(definitionPath) } - ref := &Reference{ + ref := &ReferenceNode{ ParentNode: parent, FullDefinition: fullDefinitionPath, Definition: definitionPath, @@ -344,7 +344,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, _, p := utils.ConvertComponentIdIntoFriendlyPathSearch(componentName) - ref := &Reference{ + ref := &ReferenceNode{ ParentNode: parent, FullDefinition: fullDefinitionPath, Definition: componentName, @@ -373,7 +373,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, // then add to refs with siblings if len(node.Content) > 2 { copiedNode := *node - copied := Reference{ + copied := ReferenceNode{ ParentNode: parent, FullDefinition: fullDefinitionPath, Definition: ref.Definition, @@ -510,20 +510,20 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, continue } if utils.IsNodeArray(b.Content[k].Content[g]) { - var refMap map[string][]*Reference + var refMap map[string][]*ReferenceNode if index.securityRequirementRefs[secKey] == nil { - index.securityRequirementRefs[secKey] = make(map[string][]*Reference) + index.securityRequirementRefs[secKey] = make(map[string][]*ReferenceNode) refMap = index.securityRequirementRefs[secKey] } else { refMap = index.securityRequirementRefs[secKey] } for r := range b.Content[k].Content[g].Content { - var refs []*Reference + var refs []*ReferenceNode if refMap[b.Content[k].Content[g].Content[r].Value] != nil { refs = refMap[b.Content[k].Content[g].Content[r].Value] } - refs = append(refs, &Reference{ + refs = append(refs, &ReferenceNode{ Definition: b.Content[k].Content[g].Content[r].Value, Path: fmt.Sprintf("%s.security[%d].%s[%d]", jsonPath, k, secKey, r), Node: b.Content[k].Content[g].Content[r], @@ -618,8 +618,8 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, // 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 +func (index *SpecIndex) ExtractComponentsFromRefs(refs []*ReferenceNode) []*ReferenceNode { + var found []*ReferenceNode // run this async because when things get recursive, it can take a while var c chan bool @@ -627,14 +627,14 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc c = make(chan bool) } - locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) { + locate := func(ref *ReferenceNode, refIndex int, sequence []*ReferenceMapped) { index.refLock.Lock() if index.allMappedRefs[ref.FullDefinition] != nil { rm := &ReferenceMapped{ - OriginalReference: ref, - Reference: index.allMappedRefs[ref.FullDefinition], - Definition: index.allMappedRefs[ref.FullDefinition].Definition, - FullDefinition: index.allMappedRefs[ref.FullDefinition].FullDefinition, + Source: ref, + Target: index.allMappedRefs[ref.FullDefinition], + Definition: index.allMappedRefs[ref.FullDefinition].Definition, + FullDefinition: index.allMappedRefs[ref.FullDefinition].FullDefinition, } sequence[refIndex] = rm if !index.config.ExtractRefsSequentially { @@ -662,10 +662,10 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc index.allMappedRefs[located.FullDefinition] = located } rm := &ReferenceMapped{ - OriginalReference: ref, - Reference: located, - Definition: located.Definition, - FullDefinition: located.FullDefinition, + Source: ref, + Target: located, + Definition: located.Definition, + FullDefinition: located.FullDefinition, } sequence[refIndex] = rm index.refLock.Unlock() @@ -689,7 +689,7 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc } } - var refsToCheck []*Reference + var refsToCheck []*ReferenceNode refsToCheck = append(refsToCheck, refs...) mappedRefsInSequence := make([]*ReferenceMapped, len(refsToCheck)) diff --git a/index/find_component.go b/index/find_component.go index 5af786cf..d0411279 100644 --- a/index/find_component.go +++ b/index/find_component.go @@ -17,7 +17,7 @@ import ( // 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) *Reference { +func (index *SpecIndex) FindComponent(componentId string) *ReferenceNode { if index.root == nil { return nil } @@ -46,7 +46,7 @@ func (index *SpecIndex) FindComponent(componentId string) *Reference { } } -func FindComponent(root *yaml.Node, componentId, absoluteFilePath string, index *SpecIndex) *Reference { +func FindComponent(root *yaml.Node, componentId, absoluteFilePath string, index *SpecIndex) *ReferenceNode { // check component for url encoding. if strings.Contains(componentId, "%") { // decode the url. @@ -77,7 +77,7 @@ func FindComponent(root *yaml.Node, componentId, absoluteFilePath string, index parentNode = index.allRefs[fullDef].ParentNode } - ref := &Reference{ + ref := &ReferenceNode{ FullDefinition: fullDef, Definition: componentId, Name: name, @@ -93,14 +93,14 @@ func FindComponent(root *yaml.Node, componentId, absoluteFilePath string, index return nil } -func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { +func (index *SpecIndex) FindComponentInRoot(componentId string) *ReferenceNode { if index.root != nil { return FindComponent(index.root, componentId, index.specAbsolutePath, index) } return nil } -func (index *SpecIndex) lookupRolodex(uri []string) *Reference { +func (index *SpecIndex) lookupRolodex(uri []string) *ReferenceNode { if index.rolodex == nil { return nil } @@ -158,7 +158,7 @@ func (index *SpecIndex) lookupRolodex(uri []string) *Reference { // check if there is a component we want to suck in, or if the // entire root needs to come in. - var foundRef *Reference + var foundRef *ReferenceNode if wholeFile { if parsedDocument.Kind == yaml.DocumentNode { parsedDocument = parsedDocument.Content[0] @@ -169,7 +169,7 @@ func (index *SpecIndex) lookupRolodex(uri []string) *Reference { parentNode = index.allRefs[absoluteFileLocation].ParentNode } - foundRef = &Reference{ + foundRef = &ReferenceNode{ ParentNode: parentNode, FullDefinition: absoluteFileLocation, Definition: fileName, diff --git a/index/index_model.go b/index/index_model.go index 8c52d2ed..6e123a92 100644 --- a/index/index_model.go +++ b/index/index_model.go @@ -4,44 +4,71 @@ package index import ( - "github.com/pb33f/libopenapi/datamodel" "io/fs" "log/slog" "net/http" "net/url" "path/filepath" + "strings" "sync" + "github.com/pb33f/libopenapi/datamodel" + "gopkg.in/yaml.v3" ) -// Reference is a wrapper around *yaml.Node results to make things more manageable when performing +// ReferenceNode 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 { - FullDefinition string - Definition string - Name string - Node *yaml.Node - KeyNode *yaml.Node - ParentNode *yaml.Node - ParentNodeSchemaType string // used to determine if the parent node is an array or not. - ParentNodeTypes []string // used to capture deep journeys, if any item is an array, we need to know. - Resolved bool - Circular bool - Seen bool - IsRemote bool - Index *SpecIndex // index that contains this reference. - RemoteLocation string +type ReferenceNode struct { + // FullDefinition is the full path or URL to the Node. If + // the Node is in the same file as the reference, FullDefintion is the same as + // Definition. + // + // Examples: + // - #/components/schemas/SomeSchema + // - https://pb33f.io/libopenapi/somefile.yaml#/components/schemas/SomeSchema + FullDefinition string + // Definition is the location of the reference target within the file the target + // is defined in. It's a sub-string of FullDefinition. + // + // Examples: + // - #/components/schemas/SomeSchema + Definition string + // Name is the name of the reference target. + // + // Examples: + // - SomeSchema + Name string + // Node is the node containing the value, either in the same file or in another file. + Node *yaml.Node + // KeyNode is the node containing the key. + KeyNode *yaml.Node + // ParentNode is the node containing the Node. + ParentNode *yaml.Node + ParentNodeSchemaType string // used to determine if the parent node is an array or not. + ParentNodeTypes []string // used to capture deep journeys, if any item is an array, we need to know. + Resolved bool + Circular bool + Seen bool + IsRemote bool + // Index is the SpecIndex that contains the reference. + Index *SpecIndex + RemoteLocation string + // Path is the JSON path to the reference target. Path string // this won't always be available. RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition } +func (r *ReferenceNode) DefinitionFile() string { + return strings.Split(r.FullDefinition, "#/")[0] +} + // ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key) type ReferenceMapped struct { - OriginalReference *Reference - Reference *Reference - Definition string - FullDefinition string + Source *ReferenceNode + Target *ReferenceNode + Definition string + FullDefinition string } // SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable @@ -190,91 +217,91 @@ func CreateClosedAPIIndexConfig() *SpecIndexConfig { // everything is pre-walked if you need it. type SpecIndex struct { specAbsolutePath string - rolodex *Rolodex // the rolodex is used to fetch remote and file based documents. - allRefs map[string]*Reference // all (deduplicated) refs - rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped. - linesWithRefs map[int]bool // lines that link to references. - allMappedRefs map[string]*Reference // these are the located mapped refs - allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs - refsByLine map[string]map[int]bool // every reference and the lines it's referenced from - pathRefs map[string]map[string]*Reference // all path references - paramOpRefs map[string]map[string]map[string][]*Reference // params in operations. - paramCompRefs map[string]*Reference // params in components - paramAllRefs map[string]*Reference // combined components and ops - paramInlineDuplicateNames map[string][]*Reference // inline params all with the same name - globalTagRefs map[string]*Reference // top level global tags - securitySchemeRefs map[string]*Reference // top level security schemes - requestBodiesRefs map[string]*Reference // top level request bodies - responsesRefs map[string]*Reference // top level responses - headersRefs map[string]*Reference // top level responses - examplesRefs map[string]*Reference // top level examples - securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements - callbacksRefs map[string]map[string][]*Reference // all links - linksRefs map[string]map[string][]*Reference // all callbacks - operationTagsRefs map[string]map[string][]*Reference // tags found in operations - operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations. - operationSummaryRefs map[string]map[string]*Reference // summaries in operations - callbackRefs map[string]*Reference // top level callback refs - serversRefs []*Reference // all top level server refs - rootServersNode *yaml.Node // servers root node - opServersRefs map[string]map[string][]*Reference // all operation level server overrides. - polymorphicRefs map[string]*Reference // every reference to a polymorphic ref - polymorphicAllOfRefs []*Reference // every reference to 'allOf' references - polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references - polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references - externalDocumentsRef []*Reference // all external documents in spec - rootSecurity []*Reference // root security definitions. - rootSecurityNode *yaml.Node // root security node. - refsWithSiblings map[string]Reference // references with sibling elements next to them - pathRefsLock sync.RWMutex // 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 - globalLinksCount int // component links - globalCallbacksCount 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 - parametersNode *yaml.Node // components/parameters node - allParameters map[string]*Reference // all parameters (components/defs) - schemasNode *yaml.Node // components/schemas node - allRefSchemaDefinitions []*Reference // all schemas found that are references. - allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger). - allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger). - allComponentSchemaDefinitions *sync.Map // all schemas found in components (openapi) or definitions (swagger). - securitySchemesNode *yaml.Node // components/securitySchemes node - allSecuritySchemes map[string]*Reference // all security schemes / definitions. - allComponentSchemas map[string]*Reference // all component schema definitions - allComponentSchemasLock sync.RWMutex // prevent concurrent read writes to the schema file which causes a race condition - 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 - allExternalDocuments map[string]*Reference // all external documents - externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds - refErrors []error // errors when indexing references - operationParamErrors []error // errors when indexing parameters - allDescriptions []*DescriptionReference // every single description found in the spec. - allSummaries []*DescriptionReference // every single summary found in the spec. - allEnums []*EnumReference // every single enum found in the spec. - allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec. + rolodex *Rolodex // the rolodex is used to fetch remote and file based documents. + allRefs map[string]*ReferenceNode // all (deduplicated) refs + rawSequencedRefs []*ReferenceNode // all raw references in sequence as they are scanned, not deduped. + linesWithRefs map[int]bool // lines that link to references. + allMappedRefs map[string]*ReferenceNode // 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]*ReferenceNode // all path references + paramOpRefs map[string]map[string]map[string][]*ReferenceNode // params in operations. + paramCompRefs map[string]*ReferenceNode // params in components + paramAllRefs map[string]*ReferenceNode // combined components and ops + paramInlineDuplicateNames map[string][]*ReferenceNode // inline params all with the same name + globalTagRefs map[string]*ReferenceNode // top level global tags + securitySchemeRefs map[string]*ReferenceNode // top level security schemes + requestBodiesRefs map[string]*ReferenceNode // top level request bodies + responsesRefs map[string]*ReferenceNode // top level responses + headersRefs map[string]*ReferenceNode // top level responses + examplesRefs map[string]*ReferenceNode // top level examples + securityRequirementRefs map[string]map[string][]*ReferenceNode // (NOT $ref) but a name based lookup for requirements + callbacksRefs map[string]map[string][]*ReferenceNode // all links + linksRefs map[string]map[string][]*ReferenceNode // all callbacks + operationTagsRefs map[string]map[string][]*ReferenceNode // tags found in operations + operationDescriptionRefs map[string]map[string]*ReferenceNode // descriptions in operations. + operationSummaryRefs map[string]map[string]*ReferenceNode // summaries in operations + callbackRefs map[string]*ReferenceNode // top level callback refs + serversRefs []*ReferenceNode // all top level server refs + rootServersNode *yaml.Node // servers root node + opServersRefs map[string]map[string][]*ReferenceNode // all operation level server overrides. + polymorphicRefs map[string]*ReferenceNode // every reference to a polymorphic ref + polymorphicAllOfRefs []*ReferenceNode // every reference to 'allOf' references + polymorphicOneOfRefs []*ReferenceNode // every reference to 'oneOf' references + polymorphicAnyOfRefs []*ReferenceNode // every reference to 'anyOf' references + externalDocumentsRef []*ReferenceNode // all external documents in spec + rootSecurity []*ReferenceNode // root security definitions. + rootSecurityNode *yaml.Node // root security node. + refsWithSiblings map[string]ReferenceNode // references with sibling elements next to them + pathRefsLock sync.RWMutex // 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 + globalLinksCount int // component links + globalCallbacksCount 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 + parametersNode *yaml.Node // components/parameters node + allParameters map[string]*ReferenceNode // all parameters (components/defs) + schemasNode *yaml.Node // components/schemas node + allRefSchemaDefinitions []*ReferenceNode // all schemas found that are references. + allInlineSchemaDefinitions []*ReferenceNode // all schemas found in document outside of components (openapi) or definitions (swagger). + allInlineSchemaObjectDefinitions []*ReferenceNode // all schemas that are objects found in document outside of components (openapi) or definitions (swagger). + allComponentSchemaDefinitions *sync.Map // all schemas found in components (openapi) or definitions (swagger). + securitySchemesNode *yaml.Node // components/securitySchemes node + allSecuritySchemes map[string]*ReferenceNode // all security schemes / definitions. + allComponentSchemas map[string]*ReferenceNode // all component schema definitions + allComponentSchemasLock sync.RWMutex // prevent concurrent read writes to the schema file which causes a race condition + requestBodiesNode *yaml.Node // components/requestBodies node + allRequestBodies map[string]*ReferenceNode // all request bodies + responsesNode *yaml.Node // components/responses node + allResponses map[string]*ReferenceNode // all responses + headersNode *yaml.Node // components/headers node + allHeaders map[string]*ReferenceNode // all headers + examplesNode *yaml.Node // components/examples node + allExamples map[string]*ReferenceNode // all components examples + linksNode *yaml.Node // components/links node + allLinks map[string]*ReferenceNode // all links + callbacksNode *yaml.Node // components/callbacks node + allCallbacks map[string]*ReferenceNode // all components examples + allExternalDocuments map[string]*ReferenceNode // all external documents + externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds + refErrors []error // errors when indexing references + operationParamErrors []error // errors when indexing parameters + allDescriptions []*DescriptionReference // every single description found in the spec. + allSummaries []*DescriptionReference // every single summary found in the spec. + allEnums []*EnumReference // every single enum found in the spec. + allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec. enumCount int descriptionCount int summaryCount int diff --git a/index/index_utils.go b/index/index_utils.go index 70ec3df1..58d80387 100644 --- a/index/index_utils.go +++ b/index/index_utils.go @@ -29,42 +29,42 @@ func isHttpMethod(val string) bool { } func boostrapIndexCollections(index *SpecIndex) { - index.allRefs = make(map[string]*Reference) - index.allMappedRefs = make(map[string]*Reference) + index.allRefs = make(map[string]*ReferenceNode) + index.allMappedRefs = make(map[string]*ReferenceNode) 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.paramInlineDuplicateNames = 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.pathRefs = make(map[string]map[string]*ReferenceNode) + index.paramOpRefs = make(map[string]map[string]map[string][]*ReferenceNode) + index.operationTagsRefs = make(map[string]map[string][]*ReferenceNode) + index.operationDescriptionRefs = make(map[string]map[string]*ReferenceNode) + index.operationSummaryRefs = make(map[string]map[string]*ReferenceNode) + index.paramCompRefs = make(map[string]*ReferenceNode) + index.paramAllRefs = make(map[string]*ReferenceNode) + index.paramInlineDuplicateNames = make(map[string][]*ReferenceNode) + index.globalTagRefs = make(map[string]*ReferenceNode) + index.securitySchemeRefs = make(map[string]*ReferenceNode) + index.requestBodiesRefs = make(map[string]*ReferenceNode) + index.responsesRefs = make(map[string]*ReferenceNode) + index.headersRefs = make(map[string]*ReferenceNode) + index.examplesRefs = make(map[string]*ReferenceNode) + index.callbacksRefs = make(map[string]map[string][]*ReferenceNode) + index.linksRefs = make(map[string]map[string][]*ReferenceNode) + index.callbackRefs = make(map[string]*ReferenceNode) index.externalSpecIndex = make(map[string]*SpecIndex) index.allComponentSchemaDefinitions = &sync.Map{} - 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.securityRequirementRefs = make(map[string]map[string][]*Reference) - index.polymorphicRefs = make(map[string]*Reference) - index.refsWithSiblings = make(map[string]Reference) - index.opServersRefs = make(map[string]map[string][]*Reference) + index.allParameters = make(map[string]*ReferenceNode) + index.allSecuritySchemes = make(map[string]*ReferenceNode) + index.allRequestBodies = make(map[string]*ReferenceNode) + index.allResponses = make(map[string]*ReferenceNode) + index.allHeaders = make(map[string]*ReferenceNode) + index.allExamples = make(map[string]*ReferenceNode) + index.allLinks = make(map[string]*ReferenceNode) + index.allCallbacks = make(map[string]*ReferenceNode) + index.allExternalDocuments = make(map[string]*ReferenceNode) + index.securityRequirementRefs = make(map[string]map[string][]*ReferenceNode) + index.polymorphicRefs = make(map[string]*ReferenceNode) + index.refsWithSiblings = make(map[string]ReferenceNode) + index.opServersRefs = make(map[string]map[string][]*ReferenceNode) index.componentIndexChan = make(chan bool) index.polyComponentIndexChan = make(chan bool) } diff --git a/index/resolver.go b/index/resolver.go index 90e5651d..70c3e01f 100644 --- a/index/resolver.go +++ b/index/resolver.go @@ -10,9 +10,10 @@ import ( "path/filepath" "strings" + "slices" + "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "slices" ) // ResolvingError represents an issue the resolver had trying to stitch the tree together. @@ -245,15 +246,15 @@ func visitIndexWithoutDamagingIt(res *Resolver, idx *SpecIndex) { res.indexesVisited++ for _, ref := range mapped { seenReferences := make(map[string]bool) - var journey []*Reference + var journey []*ReferenceNode res.journeysTaken++ - res.VisitReference(ref.Reference, seenReferences, journey, false) + res.VisitReference(ref.Target, seenReferences, journey, false) } schemas := idx.GetAllComponentSchemas() for s, schemaRef := range schemas { if mappedIndex[s] == nil { seenReferences := make(map[string]bool) - var journey []*Reference + var journey []*ReferenceNode res.journeysTaken++ res.VisitReference(schemaRef, seenReferences, journey, false) } @@ -261,7 +262,7 @@ func visitIndexWithoutDamagingIt(res *Resolver, idx *SpecIndex) { } type refMap struct { - ref *Reference + ref *ReferenceNode nodes []*yaml.Node } @@ -273,15 +274,15 @@ func visitIndex(res *Resolver, idx *SpecIndex) { var refs []refMap for _, ref := range mapped { seenReferences := make(map[string]bool) - var journey []*Reference + var journey []*ReferenceNode res.journeysTaken++ - if ref != nil && ref.Reference != nil { - n := res.VisitReference(ref.Reference, seenReferences, journey, true) - if !ref.Reference.Circular { + if ref != nil && ref.Target != nil { + n := res.VisitReference(ref.Target, seenReferences, journey, true) + if !ref.Target.Circular { // make a note of the reference and map the original ref after we're done - if ok, _, _ := utils.IsNodeRefValue(ref.OriginalReference.Node); ok { + if ok, _, _ := utils.IsNodeRefValue(ref.Source.Node); ok { refs = append(refs, refMap{ - ref: ref.OriginalReference, + ref: ref.Source, nodes: n, }) } @@ -294,7 +295,7 @@ func visitIndex(res *Resolver, idx *SpecIndex) { for s, schemaRef := range schemas { if mappedIndex[s] == nil { seenReferences := make(map[string]bool) - var journey []*Reference + var journey []*ReferenceNode res.journeysTaken++ schemaRef.Node.Content = res.VisitReference(schemaRef, seenReferences, journey, true) } @@ -304,7 +305,7 @@ func visitIndex(res *Resolver, idx *SpecIndex) { for s, schemaRef := range schemas { if mappedIndex[s] == nil { seenReferences := make(map[string]bool) - var journey []*Reference + var journey []*ReferenceNode res.journeysTaken++ schemaRef.Node.Content = res.VisitReference(schemaRef, seenReferences, journey, true) } @@ -322,7 +323,7 @@ func visitIndex(res *Resolver, idx *SpecIndex) { } // VisitReference will visit a reference as part of a journey and will return resolved nodes. -func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, journey []*Reference, resolve bool) []*yaml.Node { +func (resolver *Resolver) VisitReference(ref *ReferenceNode, seen map[string]bool, journey []*ReferenceNode, resolve bool) []*yaml.Node { resolver.referencesVisited++ if resolve && ref.Seen { if ref.Resolved { @@ -346,7 +347,7 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j for i, j := range journey { if j.FullDefinition == r.FullDefinition { - var foundDup *Reference + var foundDup *ReferenceNode foundRef, _ := resolver.specIndex.SearchIndexForReferenceByReference(r) if foundRef != nil { foundDup = foundRef @@ -393,7 +394,7 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j } if !skip { - var original *Reference + var original *ReferenceNode foundRef, _ := resolver.specIndex.SearchIndexForReferenceByReference(r) if foundRef != nil { original = foundRef @@ -414,8 +415,8 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j return ref.Node.Content } -func (resolver *Resolver) isInfiniteCircularDependency(ref *Reference, visitedDefinitions map[string]bool, - initialRef *Reference, +func (resolver *Resolver) isInfiniteCircularDependency(ref *ReferenceNode, visitedDefinitions map[string]bool, + initialRef *ReferenceNode, ) (bool, map[string]bool) { if ref == nil { return false, visitedDefinitions @@ -451,10 +452,10 @@ func (resolver *Resolver) isInfiniteCircularDependency(ref *Reference, visitedDe return false, visitedDefinitions } -func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.Node, +func (resolver *Resolver) extractRelatives(ref *ReferenceNode, node, parent *yaml.Node, foundRelatives map[string]bool, - journey []*Reference, seen map[int]bool, resolve bool, depth int, -) []*Reference { + journey []*ReferenceNode, seen map[int]bool, resolve bool, depth int, +) []*ReferenceNode { if len(journey) > 100 { return nil } @@ -486,7 +487,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No return nil } - var found []*Reference + var found []*ReferenceNode if len(node.Content) > 0 { skip := false @@ -498,7 +499,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No if utils.IsNodeMap(n) || utils.IsNodeArray(n) { depth++ - var foundRef *Reference + var foundRef *ReferenceNode foundRef, _ = resolver.specIndex.SearchIndexForReferenceByReference(ref) if foundRef != nil && !foundRef.Circular { found = append(found, resolver.extractRelatives(foundRef, n, node, foundRelatives, journey, seen, resolve, depth)...) @@ -523,7 +524,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No value := node.Content[i+1].Value value = strings.ReplaceAll(value, "\\\\", "\\") - var locatedRef *Reference + var locatedRef *ReferenceNode var fullDef string var definition string @@ -605,7 +606,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No } } - searchRef := &Reference{ + searchRef := &ReferenceNode{ Definition: definition, FullDefinition: fullDef, RemoteLocation: ref.RemoteLocation, @@ -808,7 +809,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No return found } -func (resolver *Resolver) buildDefPath(ref *Reference, l string) string { +func (resolver *Resolver) buildDefPath(ref *ReferenceNode, l string) string { def := "" exp := strings.Split(l, "#/") if len(exp) == 2 { diff --git a/index/resolver_test.go b/index/resolver_test.go index ce2d5de1..4925ffa9 100644 --- a/index/resolver_test.go +++ b/index/resolver_test.go @@ -4,9 +4,6 @@ import ( "bytes" "errors" "fmt" - "github.com/pb33f/libopenapi/datamodel" - "github.com/pb33f/libopenapi/utils" - "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "log/slog" "net/http" "net/url" @@ -14,6 +11,10 @@ import ( "path/filepath" "testing" + "github.com/pb33f/libopenapi/datamodel" + "github.com/pb33f/libopenapi/utils" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) @@ -442,7 +443,7 @@ func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) { } func TestResolver_DeepJourney(t *testing.T) { - var journey []*Reference + var journey []*ReferenceNode for f := 0; f < 200; f++ { journey = append(journey, nil) } @@ -478,7 +479,7 @@ func TestResolver_DeepDepth(t *testing.T) { })) idx.logger = logger - ref := &Reference{ + ref := &ReferenceNode{ FullDefinition: "#/components/schemas/A", } found := resolver.extractRelatives(ref, refA, nil, nil, nil, nil, false, 0) @@ -569,7 +570,7 @@ components: description: cheese anyOf: items: - $ref: '#/components/schemas/crackers' + $ref: '#/components/schemas/crackers' crackers: description: crackers allOf: @@ -596,7 +597,7 @@ components: cheese: description: cheese anyOf: - - $ref: '#/components/schemas/crackers' + - $ref: '#/components/schemas/crackers' crackers: description: crackers anyOf: @@ -633,7 +634,7 @@ components: description: cheese properties: thang: - $ref: '#/components/schemas/crackers' + $ref: '#/components/schemas/crackers' crackers: description: crackers properties: @@ -1312,7 +1313,7 @@ components: B: type: object allOf: - - $ref: '#/components/schemas/A' + - $ref: '#/components/schemas/A' properties: children: type: array @@ -1392,7 +1393,7 @@ components: ReferenceType: type: object required: [$ref] - properties: + properties: $ref: type: string` diff --git a/index/rolodex.go b/index/rolodex.go index b0b40735..e11ad2cb 100644 --- a/index/rolodex.go +++ b/index/rolodex.go @@ -138,6 +138,16 @@ func (r *Rolodex) GetIndexes() []*SpecIndex { return r.indexes } +func (r *Rolodex) GetFileIndex(file string) *SpecIndex { + for _, index := range r.indexes { + if index.specAbsolutePath == file { + return index + } + } + + return nil +} + // GetCaughtErrors returns all the errors that were caught during the indexing process. func (r *Rolodex) GetCaughtErrors() []error { return r.caughtErrors @@ -435,8 +445,8 @@ func (r *Rolodex) BuildIndexes() { } // GetAllReferences returns all references found in the root and all other indices -func (r *Rolodex) GetAllReferences() map[string]*Reference { - allRefs := make(map[string]*Reference) +func (r *Rolodex) GetAllReferences() map[string]*ReferenceNode { + allRefs := make(map[string]*ReferenceNode) for _, idx := range append(r.GetIndexes(), r.GetRootIndex()) { if idx == nil { continue @@ -448,8 +458,8 @@ func (r *Rolodex) GetAllReferences() map[string]*Reference { } // GetAllMappedReferences returns all mapped references found in the root and all other indices -func (r *Rolodex) GetAllMappedReferences() map[string]*Reference { - mappedRefs := make(map[string]*Reference) +func (r *Rolodex) GetAllMappedReferences() map[string]*ReferenceNode { + mappedRefs := make(map[string]*ReferenceNode) for _, idx := range append(r.GetIndexes(), r.GetRootIndex()) { if idx == nil { continue diff --git a/index/search_index.go b/index/search_index.go index 93c0557f..6bcff499 100644 --- a/index/search_index.go +++ b/index/search_index.go @@ -18,7 +18,7 @@ const ( FoundIndexKey ContextKey = "foundIndex" ) -func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) { +func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *ReferenceNode) (*ReferenceNode, *SpecIndex) { r, idx, _ := index.SearchIndexForReferenceByReferenceWithContext(context.Background(), fullRef) return r, idx } @@ -26,19 +26,19 @@ func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) ( // SearchIndexForReference searches the index for a reference, first looking through the mapped references // and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes // extracted when parsing the OpenAPI Spec. -func (index *SpecIndex) SearchIndexForReference(ref string) (*Reference, *SpecIndex) { - return index.SearchIndexForReferenceByReference(&Reference{FullDefinition: ref}) +func (index *SpecIndex) SearchIndexForReference(ref string) (*ReferenceNode, *SpecIndex) { + return index.SearchIndexForReferenceByReference(&ReferenceNode{FullDefinition: ref}) } -func (index *SpecIndex) SearchIndexForReferenceWithContext(ctx context.Context, ref string) (*Reference, *SpecIndex, context.Context) { - return index.SearchIndexForReferenceByReferenceWithContext(ctx, &Reference{FullDefinition: ref}) +func (index *SpecIndex) SearchIndexForReferenceWithContext(ctx context.Context, ref string) (*ReferenceNode, *SpecIndex, context.Context) { + return index.SearchIndexForReferenceByReferenceWithContext(ctx, &ReferenceNode{FullDefinition: ref}) } -func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx context.Context, searchRef *Reference) (*Reference, *SpecIndex, context.Context) { +func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx context.Context, searchRef *ReferenceNode) (*ReferenceNode, *SpecIndex, context.Context) { if index.cache != nil { if v, ok := index.cache.Load(searchRef.FullDefinition); ok { - idx := index.extractIndex(v.(*Reference)) - return v.(*Reference), idx, context.WithValue(ctx, CurrentPathKey, v.(*Reference).RemoteLocation) + idx := index.extractIndex(v.(*ReferenceNode)) + return v.(*ReferenceNode), idx, context.WithValue(ctx, CurrentPathKey, v.(*ReferenceNode).RemoteLocation) } } @@ -113,7 +113,7 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex } if r, ok := index.allComponentSchemaDefinitions.Load(refAlt); ok { - rf := r.(*Reference) + rf := r.(*ReferenceNode) idx := index.extractIndex(rf) index.cache.Store(refAlt, r) return rf, idx, context.WithValue(ctx, CurrentPathKey, rf.RemoteLocation) @@ -153,7 +153,7 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex if strings.HasSuffix(refParsed, n) { node, _ := rFile.GetContentAsYAMLNode() if node != nil { - r := &Reference{ + r := &ReferenceNode{ FullDefinition: n, Definition: n, IsRemote: true, @@ -181,7 +181,7 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex // build a collection of all the inline schemas and search them // for the reference. - var d []*Reference + var d []*ReferenceNode d = append(d, idx.allInlineSchemaDefinitions...) d = append(d, idx.allRefSchemaDefinitions...) d = append(d, idx.allInlineSchemaObjectDefinitions...) @@ -197,7 +197,7 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex // does component exist in the root? node, _ := rFile.GetContentAsYAMLNode() if node != nil { - var found *Reference + var found *ReferenceNode exp := strings.Split(ref, "#/") compId := ref @@ -225,7 +225,7 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex return nil, index, ctx } -func (index *SpecIndex) extractIndex(r *Reference) *SpecIndex { +func (index *SpecIndex) extractIndex(r *ReferenceNode) *SpecIndex { idx := r.Index if idx != nil && r.Index.GetSpecAbsolutePath() != r.RemoteLocation { for i := range r.Index.rolodex.indexes { diff --git a/index/spec_index.go b/index/spec_index.go index bc1dae1a..69298230 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -84,7 +84,7 @@ func createNewIndex(rootNode *yaml.Node, index *SpecIndex, avoidBuildOut bool) * results := index.ExtractRefs(index.root.Content[0], index.root, []string{}, 0, false, "") // map poly refs - poly := make([]*Reference, len(index.polymorphicRefs)) + poly := make([]*ReferenceNode, len(index.polymorphicRefs)) z := 0 for i := range index.polymorphicRefs { poly[z] = index.polymorphicRefs[i] @@ -210,33 +210,33 @@ func (index *SpecIndex) GetPathsNode() *yaml.Node { } // GetDiscoveredReferences will return all unique references found in the spec -func (index *SpecIndex) GetDiscoveredReferences() map[string]*Reference { +func (index *SpecIndex) GetDiscoveredReferences() map[string]*ReferenceNode { return index.allRefs } // GetPolyReferences will return every polymorphic reference in the doc -func (index *SpecIndex) GetPolyReferences() map[string]*Reference { +func (index *SpecIndex) GetPolyReferences() map[string]*ReferenceNode { return index.polymorphicRefs } // GetPolyAllOfReferences will return every 'allOf' polymorphic reference in the doc -func (index *SpecIndex) GetPolyAllOfReferences() []*Reference { +func (index *SpecIndex) GetPolyAllOfReferences() []*ReferenceNode { return index.polymorphicAllOfRefs } // GetPolyAnyOfReferences will return every 'anyOf' polymorphic reference in the doc -func (index *SpecIndex) GetPolyAnyOfReferences() []*Reference { +func (index *SpecIndex) GetPolyAnyOfReferences() []*ReferenceNode { return index.polymorphicAnyOfRefs } // GetPolyOneOfReferences will return every 'allOf' polymorphic reference in the doc -func (index *SpecIndex) GetPolyOneOfReferences() []*Reference { +func (index *SpecIndex) GetPolyOneOfReferences() []*ReferenceNode { 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) +func (index *SpecIndex) GetAllCombinedReferences() map[string]*ReferenceNode { + combined := make(map[string]*ReferenceNode) for k, ref := range index.allRefs { combined[k] = ref } @@ -260,13 +260,13 @@ func (index *SpecIndex) GetLinesWithReferences() map[int]bool { // this collection is completely unsorted, traversing it may produce random results when resolving it and // encountering circular references can change results depending on where in the collection the resolver started // its journey through the index. -func (index *SpecIndex) GetMappedReferences() map[string]*Reference { +func (index *SpecIndex) GetMappedReferences() map[string]*ReferenceNode { return index.allMappedRefs } // GetRawReferencesSequenced returns a slice of every single reference found in the document, extracted raw from the doc // returned in the exact order they were found in the document. -func (index *SpecIndex) GetRawReferencesSequenced() []*Reference { +func (index *SpecIndex) GetRawReferencesSequenced() []*ReferenceNode { return index.rawSequencedRefs } @@ -277,7 +277,7 @@ func (index *SpecIndex) GetMappedReferencesSequenced() []*ReferenceMapped { } // GetOperationParameterReferences will return all references to operation parameters -func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string]map[string][]*Reference { +func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string]map[string][]*ReferenceNode { return index.paramOpRefs } @@ -286,11 +286,11 @@ func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string] // and then following on are all the references extracted from the components section (using GetAllComponentSchemas). // finally all the references that are not inline, but marked as $ref in the document are returned (using GetAllReferenceSchemas). // the results are sorted by line number. -func (index *SpecIndex) GetAllSchemas() []*Reference { +func (index *SpecIndex) GetAllSchemas() []*ReferenceNode { componentSchemas := index.GetAllComponentSchemas() inlineSchemas := index.GetAllInlineSchemas() refSchemas := index.GetAllReferenceSchemas() - combined := make([]*Reference, len(inlineSchemas)+len(componentSchemas)+len(refSchemas)) + combined := make([]*ReferenceNode, len(inlineSchemas)+len(componentSchemas)+len(refSchemas)) i := 0 for x := range inlineSchemas { combined[i] = inlineSchemas[x] @@ -312,22 +312,22 @@ func (index *SpecIndex) GetAllSchemas() []*Reference { // GetAllInlineSchemaObjects will return all schemas that are inline (not inside components) and that are also typed // as 'object' or 'array' (not primitives). -func (index *SpecIndex) GetAllInlineSchemaObjects() []*Reference { +func (index *SpecIndex) GetAllInlineSchemaObjects() []*ReferenceNode { return index.allInlineSchemaObjectDefinitions } // GetAllInlineSchemas will return all schemas defined in the components section of the document. -func (index *SpecIndex) GetAllInlineSchemas() []*Reference { +func (index *SpecIndex) GetAllInlineSchemas() []*ReferenceNode { return index.allInlineSchemaDefinitions } // GetAllReferenceSchemas will return all schemas that are not inline, but $ref'd from somewhere. -func (index *SpecIndex) GetAllReferenceSchemas() []*Reference { +func (index *SpecIndex) GetAllReferenceSchemas() []*ReferenceNode { return index.allRefSchemaDefinitions } // GetAllComponentSchemas will return all schemas defined in the components section of the document. -func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference { +func (index *SpecIndex) GetAllComponentSchemas() map[string]*ReferenceNode { if index == nil { return nil } @@ -347,7 +347,7 @@ func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference { // Double-check if another goroutine initialized it if index.allComponentSchemas == nil { - schemaMap := syncMapToMap[string, *Reference](index.allComponentSchemaDefinitions) + schemaMap := syncMapToMap[string, *ReferenceNode](index.allComponentSchemaDefinitions) index.allComponentSchemas = schemaMap } @@ -355,22 +355,22 @@ func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference { } // GetAllSecuritySchemes will return all security schemes / definitions found in the document. -func (index *SpecIndex) GetAllSecuritySchemes() map[string]*Reference { +func (index *SpecIndex) GetAllSecuritySchemes() map[string]*ReferenceNode { return index.allSecuritySchemes } // GetAllHeaders will return all headers found in the document (under components) -func (index *SpecIndex) GetAllHeaders() map[string]*Reference { +func (index *SpecIndex) GetAllHeaders() map[string]*ReferenceNode { return index.allHeaders } // GetAllExternalDocuments will return all external documents found -func (index *SpecIndex) GetAllExternalDocuments() map[string]*Reference { +func (index *SpecIndex) GetAllExternalDocuments() map[string]*ReferenceNode { return index.allExternalDocuments } // GetAllExamples will return all examples found in the document (under components) -func (index *SpecIndex) GetAllExamples() map[string]*Reference { +func (index *SpecIndex) GetAllExamples() map[string]*ReferenceNode { return index.allExamples } @@ -395,47 +395,47 @@ func (index *SpecIndex) GetAllSummaries() []*DescriptionReference { } // GetAllRequestBodies will return all requestBodies found in the document (under components) -func (index *SpecIndex) GetAllRequestBodies() map[string]*Reference { +func (index *SpecIndex) GetAllRequestBodies() map[string]*ReferenceNode { return index.allRequestBodies } // GetAllLinks will return all links found in the document (under components) -func (index *SpecIndex) GetAllLinks() map[string]*Reference { +func (index *SpecIndex) GetAllLinks() map[string]*ReferenceNode { return index.allLinks } // GetAllParameters will return all parameters found in the document (under components) -func (index *SpecIndex) GetAllParameters() map[string]*Reference { +func (index *SpecIndex) GetAllParameters() map[string]*ReferenceNode { return index.allParameters } // GetAllResponses will return all responses found in the document (under components) -func (index *SpecIndex) GetAllResponses() map[string]*Reference { +func (index *SpecIndex) GetAllResponses() map[string]*ReferenceNode { return index.allResponses } // GetAllCallbacks will return all links found in the document (under components) -func (index *SpecIndex) GetAllCallbacks() map[string]*Reference { +func (index *SpecIndex) GetAllCallbacks() map[string]*ReferenceNode { return index.allCallbacks } // GetInlineOperationDuplicateParameters will return a map of duplicates located in operation parameters. -func (index *SpecIndex) GetInlineOperationDuplicateParameters() map[string][]*Reference { +func (index *SpecIndex) GetInlineOperationDuplicateParameters() map[string][]*ReferenceNode { return index.paramInlineDuplicateNames } // GetReferencesWithSiblings will return a map of all the references with sibling nodes (illegal) -func (index *SpecIndex) GetReferencesWithSiblings() map[string]Reference { +func (index *SpecIndex) GetReferencesWithSiblings() map[string]ReferenceNode { return index.refsWithSiblings } // GetAllReferences will return every reference found in the spec, after being de-duplicated. -func (index *SpecIndex) GetAllReferences() map[string]*Reference { +func (index *SpecIndex) GetAllReferences() map[string]*ReferenceNode { return index.allRefs } // GetAllSequencedReferences will return every reference (in sequence) that was found (non-polymorphic) -func (index *SpecIndex) GetAllSequencedReferences() []*Reference { +func (index *SpecIndex) GetAllSequencedReferences() []*ReferenceNode { return index.rawSequencedRefs } @@ -460,27 +460,27 @@ func (index *SpecIndex) GetOperationParametersIndexErrors() []error { } // GetAllPaths will return all paths indexed in the document -func (index *SpecIndex) GetAllPaths() map[string]map[string]*Reference { +func (index *SpecIndex) GetAllPaths() map[string]map[string]*ReferenceNode { return index.pathRefs } // GetOperationTags will return all references to all tags found in operations. -func (index *SpecIndex) GetOperationTags() map[string]map[string][]*Reference { +func (index *SpecIndex) GetOperationTags() map[string]map[string][]*ReferenceNode { return index.operationTagsRefs } // GetAllParametersFromOperations will return all paths indexed in the document -func (index *SpecIndex) GetAllParametersFromOperations() map[string]map[string]map[string][]*Reference { +func (index *SpecIndex) GetAllParametersFromOperations() map[string]map[string]map[string][]*ReferenceNode { return index.paramOpRefs } // GetRootSecurityReferences will return all root security settings -func (index *SpecIndex) GetRootSecurityReferences() []*Reference { +func (index *SpecIndex) GetRootSecurityReferences() []*ReferenceNode { return index.rootSecurity } // GetSecurityRequirementReferences will return all security requirement definitions found in the document -func (index *SpecIndex) GetSecurityRequirementReferences() map[string]map[string][]*Reference { +func (index *SpecIndex) GetSecurityRequirementReferences() map[string]map[string][]*ReferenceNode { return index.securityRequirementRefs } @@ -495,12 +495,12 @@ func (index *SpecIndex) GetRootServersNode() *yaml.Node { } // GetAllRootServers will return all root servers defined -func (index *SpecIndex) GetAllRootServers() []*Reference { +func (index *SpecIndex) GetAllRootServers() []*ReferenceNode { return index.serversRefs } // GetAllOperationsServers will return all operation overrides for servers. -func (index *SpecIndex) GetAllOperationsServers() map[string]map[string][]*Reference { +func (index *SpecIndex) GetAllOperationsServers() map[string]map[string][]*ReferenceNode { return index.opServersRefs } @@ -551,11 +551,11 @@ func (index *SpecIndex) GetPathCount() int { } // ExtractExternalDocuments will extract the number of externalDocs nodes found in the document. -func (index *SpecIndex) ExtractExternalDocuments(node *yaml.Node) []*Reference { +func (index *SpecIndex) ExtractExternalDocuments(node *yaml.Node) []*ReferenceNode { if node == nil { return nil } - var found []*Reference + var found []*ReferenceNode if len(node.Content) > 0 { for i, n := range node.Content { if utils.IsNodeMap(n) || utils.IsNodeArray(n) { @@ -566,7 +566,7 @@ func (index *SpecIndex) ExtractExternalDocuments(node *yaml.Node) []*Reference { docNode := node.Content[i+1] _, urlNode := utils.FindKeyNode("url", docNode.Content) if urlNode != nil { - ref := &Reference{ + ref := &ReferenceNode{ Definition: urlNode.Value, Name: urlNode.Value, Node: docNode, @@ -607,7 +607,7 @@ func (index *SpecIndex) GetGlobalTagsCount() int { desc = "" } if name != nil { - ref := &Reference{ + ref := &ReferenceNode{ Definition: desc, Name: name.Value, Node: tagNode, @@ -706,19 +706,19 @@ func (index *SpecIndex) GetGlobalCallbacksCount() int { for _, callback := range res[0].Content { if utils.IsNodeMap(callback) { - ref := &Reference{ + ref := &ReferenceNode{ Definition: m.Name, Name: m.Name, Node: callback, } if index.callbacksRefs[path] == nil { - index.callbacksRefs[path] = make(map[string][]*Reference) + index.callbacksRefs[path] = make(map[string][]*ReferenceNode) } 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.callbacksRefs[path][m.Name] = []*ReferenceNode{ref} } index.globalCallbacksCount++ } @@ -754,18 +754,18 @@ func (index *SpecIndex) GetGlobalLinksCount() int { for _, link := range res[0].Content { if utils.IsNodeMap(link) { - ref := &Reference{ + ref := &ReferenceNode{ Definition: m.Name, Name: m.Name, Node: link, } if index.linksRefs[path] == nil { - index.linksRefs[path] = make(map[string][]*Reference) + index.linksRefs[path] = make(map[string][]*ReferenceNode) } 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.linksRefs[path][m.Name] = []*ReferenceNode{ref} index.globalLinksCount++ } } @@ -800,7 +800,7 @@ func (index *SpecIndex) GetComponentSchemaCount() int { 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{ + ref := &ReferenceNode{ Definition: "servers", Name: "server", Node: def, @@ -820,7 +820,7 @@ func (index *SpecIndex) GetComponentSchemaCount() int { for x, def := range securityDefinitions.Content { if len(def.Content) > 0 { name := def.Content[0] - ref := &Reference{ + ref := &ReferenceNode{ Definition: name.Value, Name: name.Value, Node: def, @@ -1006,7 +1006,7 @@ func (index *SpecIndex) GetOperationCount() int { opCount := 0 - locatedPathRefs := make(map[string]map[string]*Reference) + locatedPathRefs := make(map[string]map[string]*ReferenceNode) for x, p := range index.pathsNode.Content { if x%2 == 0 { @@ -1038,7 +1038,7 @@ func (index *SpecIndex) GetOperationCount() int { } } if valid { - ref := &Reference{ + ref := &ReferenceNode{ Definition: m.Value, Name: m.Value, Node: method.Content[y+1], @@ -1046,7 +1046,7 @@ func (index *SpecIndex) GetOperationCount() int { ParentNode: m, } if locatedPathRefs[p.Value] == nil { - locatedPathRefs[p.Value] = make(map[string]*Reference) + locatedPathRefs[p.Value] = make(map[string]*ReferenceNode) } locatedPathRefs[p.Value][ref.Name] = ref // update @@ -1109,11 +1109,11 @@ func (index *SpecIndex) GetOperationsParameterCount() int { if prop.Value == "servers" { serversNode := pathPropertyNode.Content[y+1] if index.opServersRefs[pathItemNode.Value] == nil { - index.opServersRefs[pathItemNode.Value] = make(map[string][]*Reference) + index.opServersRefs[pathItemNode.Value] = make(map[string][]*ReferenceNode) } - var serverRefs []*Reference + var serverRefs []*ReferenceNode for i, serverRef := range serversNode.Content { - ref := &Reference{ + ref := &ReferenceNode{ Definition: serverRef.Value, Name: serverRef.Value, Node: serverRef, @@ -1147,12 +1147,12 @@ func (index *SpecIndex) GetOperationsParameterCount() int { tags := pathPropertyNode.Content[y+1].Content[z+1] if index.operationTagsRefs[pathItemNode.Value] == nil { - index.operationTagsRefs[pathItemNode.Value] = make(map[string][]*Reference) + index.operationTagsRefs[pathItemNode.Value] = make(map[string][]*ReferenceNode) } - var tagRefs []*Reference + var tagRefs []*ReferenceNode for _, tagRef := range tags.Content { - ref := &Reference{ + ref := &ReferenceNode{ Definition: tagRef.Value, Name: tagRef.Value, Node: tagRef, @@ -1165,27 +1165,27 @@ func (index *SpecIndex) GetOperationsParameterCount() int { // extract description and summaries if httpMethodProp.Value == "description" { desc := pathPropertyNode.Content[y+1].Content[z+1].Value - ref := &Reference{ + ref := &ReferenceNode{ 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] = make(map[string]*ReferenceNode) } index.operationDescriptionRefs[pathItemNode.Value][prop.Value] = ref } if httpMethodProp.Value == "summary" { summary := pathPropertyNode.Content[y+1].Content[z+1].Value - ref := &Reference{ + ref := &ReferenceNode{ Definition: summary, Name: "summary", Node: pathPropertyNode.Content[y+1].Content[z+1], } if index.operationSummaryRefs[pathItemNode.Value] == nil { - index.operationSummaryRefs[pathItemNode.Value] = make(map[string]*Reference) + index.operationSummaryRefs[pathItemNode.Value] = make(map[string]*ReferenceNode) } index.operationSummaryRefs[pathItemNode.Value][prop.Value] = ref @@ -1195,9 +1195,9 @@ func (index *SpecIndex) GetOperationsParameterCount() int { if httpMethodProp.Value == "servers" { serversNode := pathPropertyNode.Content[y+1].Content[z+1] - var serverRefs []*Reference + var serverRefs []*ReferenceNode for i, serverRef := range serversNode.Content { - ref := &Reference{ + ref := &ReferenceNode{ Definition: "servers", Name: "servers", Node: serverRef, @@ -1208,7 +1208,7 @@ func (index *SpecIndex) GetOperationsParameterCount() int { } if index.opServersRefs[pathItemNode.Value] == nil { - index.opServersRefs[pathItemNode.Value] = make(map[string][]*Reference) + index.opServersRefs[pathItemNode.Value] = make(map[string][]*ReferenceNode) } index.opServersRefs[pathItemNode.Value][prop.Value] = serverRefs diff --git a/index/utility_methods.go b/index/utility_methods.go index 09b6e248..214aa019 100644 --- a/index/utility_methods.go +++ b/index/utility_methods.go @@ -26,7 +26,7 @@ func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pat def := fmt.Sprintf("%s%s", pathPrefix, name) fullDef := fmt.Sprintf("%s%s", index.specAbsolutePath, def) - ref := &Reference{ + ref := &ReferenceNode{ FullDefinition: fullDef, Definition: def, Name: name, @@ -208,7 +208,7 @@ func (index *SpecIndex) extractComponentParameters(paramsNode *yaml.Node, pathPr continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: param, @@ -228,7 +228,7 @@ func (index *SpecIndex) extractComponentRequestBodies(requestBodiesNode *yaml.No continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: reqBod, @@ -248,7 +248,7 @@ func (index *SpecIndex) extractComponentResponses(responsesNode *yaml.Node, path continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: response, @@ -268,7 +268,7 @@ func (index *SpecIndex) extractComponentHeaders(headersNode *yaml.Node, pathPref continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: header, @@ -288,7 +288,7 @@ func (index *SpecIndex) extractComponentCallbacks(callbacksNode *yaml.Node, path continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: callback, @@ -308,7 +308,7 @@ func (index *SpecIndex) extractComponentLinks(linksNode *yaml.Node, pathPrefix s continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: link, @@ -328,7 +328,7 @@ func (index *SpecIndex) extractComponentExamples(examplesNode *yaml.Node, pathPr continue } def := fmt.Sprintf("%s%s", pathPrefix, name) - ref := &Reference{ + ref := &ReferenceNode{ Definition: def, Name: name, Node: example, @@ -350,7 +350,7 @@ func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yam def := fmt.Sprintf("%s%s", pathPrefix, name) fullDef := fmt.Sprintf("%s%s", index.specAbsolutePath, def) - ref := &Reference{ + ref := &ReferenceNode{ FullDefinition: fullDef, Definition: def, Name: name, @@ -378,7 +378,7 @@ func (index *SpecIndex) countUniqueInlineDuplicates() int { return unique } -func seekRefEnd(index *SpecIndex, refName string) *Reference { +func seekRefEnd(index *SpecIndex, refName string) *ReferenceNode { ref, _ := index.SearchIndexForReference(refName) if ref != nil { if ok, _, v := utils.IsNodeRefValue(ref.Node); ok { @@ -408,13 +408,13 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, keyNode, pathIt } 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) + index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*ReferenceNode) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*ReferenceNode) } // 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] = make(map[string][]*ReferenceNode) } // if this is a duplicate, add an error and ignore it @@ -458,7 +458,7 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, keyNode, pathIt continue } - ref := &Reference{ + ref := &ReferenceNode{ Definition: vn.Value, Name: vn.Value, Node: param, @@ -466,13 +466,13 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, keyNode, pathIt Path: path, } 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) + index.paramOpRefs[pathItemNode.Value] = make(map[string]map[string][]*ReferenceNode) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*ReferenceNode) } // 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) + index.paramOpRefs[pathItemNode.Value][method] = make(map[string][]*ReferenceNode) } // if this is a duplicate name, check if the `in` type is also the same, if so, it's a duplicate. diff --git a/test_specs/minimal_remote_refs/openapi.yaml b/test_specs/minimal_remote_refs/openapi.yaml index 6e2fc34a..b1b52ebb 100644 --- a/test_specs/minimal_remote_refs/openapi.yaml +++ b/test_specs/minimal_remote_refs/openapi.yaml @@ -29,8 +29,27 @@ paths: responses: "200": $ref: ./schemas/components.openapi.yaml#/components/responses/ListAccounts + /api/v1/example: + get: + summary: TODO + description: TODO + tags: + - Account + operationId: placeholder + responses: + "200": + description: > + Foo + content: + application/json: + schema: components: securitySchemes: BearerAuth: type: http scheme: bearer + schemas: + Foobar: + type: string + description: > + Something nice to say. diff --git a/test_specs/minimal_remote_refs/schemas/components.openapi.yaml b/test_specs/minimal_remote_refs/schemas/components.openapi.yaml index f4dd599f..beec951e 100644 --- a/test_specs/minimal_remote_refs/schemas/components.openapi.yaml +++ b/test_specs/minimal_remote_refs/schemas/components.openapi.yaml @@ -20,9 +20,12 @@ components: type: string description: > Name of the account + Email: + type: string + format: email responses: ListAccounts: - description: List all accounts. + description: A list of accounts. content: application/json: schema: @@ -37,3 +40,9 @@ components: total: type: integer description: Total number of accounts. + GetAccountEmail: + description: A single account. + content: + application/json: + schema: + $ref: "#/components/schemas/Email"