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 (
"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
}

View File

@@ -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
}
}

View File

@@ -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
}