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`
This commit is contained in:
Dave Shanley
2023-01-28 13:47:22 -05:00
parent de1a42c72f
commit 6d77716c9a
3 changed files with 79 additions and 10 deletions

View File

@@ -15,6 +15,7 @@ package libopenapi
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
v2high "github.com/pb33f/libopenapi/datamodel/high/v2" v2high "github.com/pb33f/libopenapi/datamodel/high/v2"
@@ -68,6 +69,7 @@ type document struct {
// built from a parent Document. // built from a parent Document.
type DocumentModel[T v2high.Swagger | v3high.Document] struct { type DocumentModel[T v2high.Swagger | v3high.Document] struct {
Model T 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 // 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) highDoc := v2high.NewSwaggerDocument(lowDoc)
return &DocumentModel[v2high.Swagger]{ return &DocumentModel[v2high.Swagger]{
Model: *highDoc, Model: *highDoc,
Index: lowDoc.Index,
}, errs }, errs
} }
@@ -162,6 +165,7 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
highDoc := v3high.NewDocument(lowDoc) highDoc := v3high.NewDocument(lowDoc)
return &DocumentModel[v3high.Document]{ return &DocumentModel[v3high.Document]{
Model: *highDoc, Model: *highDoc,
Index: lowDoc.Index,
}, errs }, errs
} }

View File

@@ -122,7 +122,9 @@ type SpecIndex struct {
allParametersNode map[string]*Reference // all parameters node allParametersNode map[string]*Reference // all parameters node
allParameters map[string]*Reference // all parameters (components/defs) allParameters map[string]*Reference // all parameters (components/defs)
schemasNode *yaml.Node // components/schemas node 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 securitySchemesNode *yaml.Node // components/securitySchemes node
allSecuritySchemes map[string]*Reference // all security schemes / definitions. allSecuritySchemes map[string]*Reference // all security schemes / definitions.
requestBodiesNode *yaml.Node // components/requestBodies node 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.linksRefs = make(map[string]map[string][]*Reference)
index.callbackRefs = make(map[string]*Reference) index.callbackRefs = make(map[string]*Reference)
index.externalSpecIndex = make(map[string]*SpecIndex) 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.allParameters = make(map[string]*Reference)
index.allSecuritySchemes = make(map[string]*Reference) index.allSecuritySchemes = make(map[string]*Reference)
index.allRequestBodies = make(map[string]*Reference) index.allRequestBodies = make(map[string]*Reference)
@@ -304,7 +306,7 @@ func (index *SpecIndex) GetRootNode() *yaml.Node {
return index.root return index.root
} }
// GetGlobalTagsNode returns document root node. // GetGlobalTagsNode returns document root tags node.
func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node { func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node {
return index.tagsNode return index.tagsNode
} }
@@ -391,9 +393,41 @@ func (index *SpecIndex) GetOperationParameterReferences() map[string]map[string]
return index.paramOpRefs return index.paramOpRefs
} }
// GetAllSchemas will return all schemas found in the document // GetAllSchemas will return references to all schemas found in the document both inline and those under components
func (index *SpecIndex) GetAllSchemas() map[string]*Reference { // The first elements of at the top of the slice, are all the inline references (using GetAllInlineSchemas),
return index.allSchemas // 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. // 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)...) 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" { if i%2 == 0 && n.Value == "$ref" {
// only look at scalar values, not maps (looking at you k8s) // 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, ParentNode: schemasNode,
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}), RequiredRefProperties: index.extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}),
} }
index.allSchemas[def] = ref index.allComponentSchemaDefinitions[def] = ref
} }
} }

View File

@@ -191,7 +191,8 @@ func TestSpecIndex_BurgerShop(t *testing.T) {
assert.Equal(t, 6, index.pathCount) assert.Equal(t, 6, index.pathCount)
assert.Equal(t, 6, index.GetPathCount()) 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.Equal(t, 34, len(index.GetAllSequencedReferences()))
assert.NotNil(t, index.GetSchemasNode()) assert.NotNil(t, index.GetSchemasNode())
@@ -833,19 +834,28 @@ func ExampleNewSpecIndex() {
fmt.Printf("There are %d references\n"+ fmt.Printf("There are %d references\n"+
"%d paths\n"+ "%d paths\n"+
"%d operations\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 enums\n"+
"%d polymorphic references", "%d polymorphic references",
len(index.GetAllCombinedReferences()), len(index.GetAllCombinedReferences()),
len(index.GetAllPaths()), len(index.GetAllPaths()),
index.GetOperationCount(), index.GetOperationCount(),
len(index.GetAllComponentSchemas()),
len(index.GetAllInlineSchemas()),
len(index.GetAllInlineSchemaObjects()),
len(index.GetAllSchemas()), len(index.GetAllSchemas()),
len(index.GetAllEnums()), len(index.GetAllEnums()),
len(index.GetPolyOneOfReferences())+len(index.GetPolyAnyOfReferences())) len(index.GetPolyOneOfReferences())+len(index.GetPolyAnyOfReferences()))
// Output: There are 537 references // Output: There are 537 references
// 246 paths // 246 paths
// 402 operations // 402 operations
// 537 schemas // 537 component schemas
// 1530 inline schemas
// 711 inline schemas that are objects or arrays
// 2067 total schemas
// 1516 enums // 1516 enums
// 828 polymorphic references // 828 polymorphic references
} }