From 6d77716c9ad5a1059902f87f5ce901a2bf61a1cc Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Sat, 28 Jan 2023 13:47:22 -0500 Subject: [PATCH] feat: add index for getting all "schema" objects #50 There is new code that looks for inline `schema` definitions and collects them, as well as a futher subset of references that are specifically `object` or `array` types. This is a breaking change because the `index.GetAllSchemas()` method now returns a slice of *Reference pointers, rather than a map of them. The original behavior of `GetAllSchemas()` has been replaced with `GetAllComponentSchemas()` which retains the signature. Two new additional methods `GetAllInlineSchemas()` and `GetAllInlineSchemaObjects()`. The former will return everything in the spec marked with `schema` that is not a reference. The latter will return everything the former does, except filtered down by `object` or `array` types. Also the index is now bubbled up through the document model. There isn't a simple way to get access to it, small non breaking change that attaches a reference to the index, returned by `DocumentModel` --- document.go | 4 +++ index/spec_index.go | 69 ++++++++++++++++++++++++++++++++++++---- index/spec_index_test.go | 16 ++++++++-- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/document.go b/document.go index c41c53b..5a7a5e7 100644 --- a/document.go +++ b/document.go @@ -15,6 +15,7 @@ package libopenapi import ( "fmt" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/datamodel" v2high "github.com/pb33f/libopenapi/datamodel/high/v2" @@ -68,6 +69,7 @@ type document struct { // built from a parent Document. type DocumentModel[T v2high.Swagger | v3high.Document] struct { Model T + Index *index.SpecIndex // index created from the document. } // NewDocument will create a new OpenAPI instance from an OpenAPI specification []byte array. If anything goes @@ -133,6 +135,7 @@ func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) { highDoc := v2high.NewSwaggerDocument(lowDoc) return &DocumentModel[v2high.Swagger]{ Model: *highDoc, + Index: lowDoc.Index, }, errs } @@ -162,6 +165,7 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) { highDoc := v3high.NewDocument(lowDoc) return &DocumentModel[v3high.Document]{ Model: *highDoc, + Index: lowDoc.Index, }, errs } diff --git a/index/spec_index.go b/index/spec_index.go index d125f88..b7961ad 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -122,7 +122,9 @@ type SpecIndex struct { 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 + allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger). + allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger). + allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger). securitySchemesNode *yaml.Node // components/securitySchemes node allSecuritySchemes map[string]*Reference // all security schemes / definitions. requestBodiesNode *yaml.Node // components/requestBodies node @@ -227,7 +229,7 @@ func NewSpecIndex(rootNode *yaml.Node) *SpecIndex { 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.allComponentSchemaDefinitions = make(map[string]*Reference) index.allParameters = make(map[string]*Reference) index.allSecuritySchemes = make(map[string]*Reference) index.allRequestBodies = make(map[string]*Reference) @@ -304,7 +306,7 @@ func (index *SpecIndex) GetRootNode() *yaml.Node { return index.root } -// GetGlobalTagsNode returns document root node. +// GetGlobalTagsNode returns document root tags node. func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node { return index.tagsNode } @@ -391,9 +393,41 @@ func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string] return index.paramOpRefs } -// GetAllSchemas will return all schemas found in the document -func (index *SpecIndex) GetAllSchemas() map[string]*Reference { - return index.allSchemas +// GetAllSchemas will return references to all schemas found in the document both inline and those under components +// The first elements of at the top of the slice, are all the inline references (using GetAllInlineSchemas), +// and then following on are all the references extracted from the components section (using GetAllComponentSchemas). +func (index *SpecIndex) GetAllSchemas() []*Reference { + + componentSchemas := index.GetAllComponentSchemas() + inlineSchemas := index.GetAllInlineSchemas() + + combined := make([]*Reference, len(inlineSchemas)+len(componentSchemas)) + i := 0 + for x := range inlineSchemas { + combined[i] = inlineSchemas[x] + i++ + } + for x := range componentSchemas { + combined[i] = componentSchemas[x] + i++ + } + return combined +} + +// GetAllInlineSchemaObjects will return all schemas that are inline (not inside components) and that are also typed +// as 'object' or 'array' (not primitives). +func (index *SpecIndex) GetAllInlineSchemaObjects() []*Reference { + return index.allInlineSchemaObjectDefinitions +} + +// GetAllInlineSchemas will return all schemas defined in the components section of the document. +func (index *SpecIndex) GetAllInlineSchemas() []*Reference { + return index.allInlineSchemaDefinitions +} + +// GetAllComponentSchemas will return all schemas defined in the components section of the document. +func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference { + return index.allComponentSchemaDefinitions } // GetAllSecuritySchemes will return all security schemes / definitions found in the document. @@ -600,6 +634,27 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, found = append(found, index.ExtractRefs(n, node, seenPath, level, poly, polyName)...) } + if i%2 == 0 && n.Value == "schema" { + isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1]) + if isRef { + continue + } + ref := &Reference{ + Node: node.Content[i+1], + Path: fmt.Sprintf("$.%s", strings.Join(seenPath, ".")), + } + index.allInlineSchemaDefinitions = append(index.allInlineSchemaDefinitions, ref) + + // check if the schema is an object or an array, + // and if so, add it to the list of inline schema object definitions. + k, v := utils.FindKeyNodeTop("type", node.Content[i+1].Content) + if k != nil && v != nil { + if v.Value == "object" || v.Value == "array" { + index.allInlineSchemaObjectDefinitions = append(index.allInlineSchemaObjectDefinitions, ref) + } + } + } + if i%2 == 0 && n.Value == "$ref" { // only look at scalar values, not maps (looking at you k8s) @@ -1711,7 +1766,7 @@ func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pat ParentNode: schemasNode, RequiredRefProperties: index.extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}), } - index.allSchemas[def] = ref + index.allComponentSchemaDefinitions[def] = ref } } diff --git a/index/spec_index_test.go b/index/spec_index_test.go index cc9703d..1aada27 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -191,7 +191,8 @@ func TestSpecIndex_BurgerShop(t *testing.T) { assert.Equal(t, 6, index.pathCount) assert.Equal(t, 6, index.GetPathCount()) - assert.Equal(t, 6, len(index.GetAllSchemas())) + assert.Equal(t, 6, len(index.GetAllComponentSchemas())) + assert.Equal(t, 15, len(index.GetAllSchemas())) assert.Equal(t, 34, len(index.GetAllSequencedReferences())) assert.NotNil(t, index.GetSchemasNode()) @@ -833,19 +834,28 @@ func ExampleNewSpecIndex() { fmt.Printf("There are %d references\n"+ "%d paths\n"+ "%d operations\n"+ - "%d schemas\n"+ + "%d component schemas\n"+ + "%d inline schemas\n"+ + "%d inline schemas that are objects or arrays\n"+ + "%d total schemas\n"+ "%d enums\n"+ "%d polymorphic references", len(index.GetAllCombinedReferences()), len(index.GetAllPaths()), index.GetOperationCount(), + len(index.GetAllComponentSchemas()), + len(index.GetAllInlineSchemas()), + len(index.GetAllInlineSchemaObjects()), len(index.GetAllSchemas()), len(index.GetAllEnums()), len(index.GetPolyOneOfReferences())+len(index.GetPolyAnyOfReferences())) // Output: There are 537 references // 246 paths // 402 operations - // 537 schemas + // 537 component schemas + // 1530 inline schemas + // 711 inline schemas that are objects or arrays + // 2067 total schemas // 1516 enums // 828 polymorphic references }