Building out low level docs now

a long road ahead, but we must push forward.
This commit is contained in:
Dave Shanley
2022-09-20 07:55:19 -04:00
parent b5e19ceeb2
commit 65b242b6c4
16 changed files with 152 additions and 72 deletions

View File

@@ -13,6 +13,8 @@ import (
"strings" "strings"
) )
// FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every
// KeyReference will have its value checked against the string key and if there is a match, it will be returned.
func FindItemInMap[T any](item string, collection map[KeyReference[string]]ValueReference[T]) *ValueReference[T] { func FindItemInMap[T any](item string, collection map[KeyReference[string]]ValueReference[T]) *ValueReference[T] {
for n, o := range collection { for n, o := range collection {
if n.Value == item { if n.Value == item {
@@ -25,6 +27,7 @@ func FindItemInMap[T any](item string, collection map[KeyReference[string]]Value
return nil return nil
} }
// 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 { func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference {
return []func() map[string]*index.Reference{ return []func() map[string]*index.Reference{
idx.GetAllSchemas, idx.GetAllSchemas,
@@ -42,6 +45,8 @@ func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Re
} }
} }
// LocateRefNode will perform a complete lookup for a $ref node. This function searches the entire index for
// the reference being supplied. If there is a match found, the reference *yaml.Node is returned.
func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) { func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
if rf, _, rv := utils.IsNodeRefValue(root); rf { if rf, _, rv := utils.IsNodeRefValue(root); rf {
// run through everything and return as soon as we find a match. // run through everything and return as soon as we find a match.
@@ -72,15 +77,8 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
if !IsCircular(found[rv].Node, idx) { if !IsCircular(found[rv].Node, idx) {
return LocateRefNode(found[rv].Node, idx) return LocateRefNode(found[rv].Node, idx)
} else { } else {
//Log.Error("circular reference found during lookup, and will remain un-resolved.", return found[rv].Node, fmt.Errorf("circular reference '%s' found during lookup at line "+
// zap.Int("line", found[rv].Node.Line), "%d, column %d, It cannot be resolved",
// zap.Int("column", found[rv].Node.Column),
// zap.String("reference", found[rv].Definition),
// zap.String("journey",
// GetCircularReferenceResult(found[rv].Node, idx).GenerateJourneyPath()))
return found[rv].Node, fmt.Errorf("circular reference '%s' found during lookup at line %d, column %d, "+
"It cannot be resolved",
GetCircularReferenceResult(found[rv].Node, idx).GenerateJourneyPath(), GetCircularReferenceResult(found[rv].Node, idx).GenerateJourneyPath(),
found[rv].Node.Line, found[rv].Node.Line,
found[rv].Node.Column) found[rv].Node.Column)
@@ -104,11 +102,14 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
} }
} }
} }
return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found", root.Value, root.Line, root.Column) return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found",
root.Value, root.Line, root.Column)
} }
return nil, nil return nil, nil
} }
// ExtractObjectRaw will extract a typed Buildable[N] object from a root yaml.Node. The 'raw' aspect is
// that there is no NodeReference wrapper around the result returned, just the raw object.
func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (T, error) { func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (T, error) {
var circError error var circError error
if h, _, _ := utils.IsNodeRefValue(root); h { if h, _, _ := utils.IsNodeRefValue(root); h {
@@ -133,12 +134,15 @@ func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecInd
if err != nil { if err != nil {
return n, err return n, err
} }
// do we want to throw an error as well if circular error reporting is on?
if circError != nil && !idx.AllowCircularReferenceResolving() { if circError != nil && !idx.AllowCircularReferenceResolving() {
return n, circError return n, circError
} }
return n, nil return n, nil
} }
// ExtractObject will extract a typed Buildable[N] object from a root yaml.Node. The result is wrapped in a
// NodeReference[T] that contains the key node found and value node found when looking up the reference.
func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (NodeReference[T], error) { func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (NodeReference[T], error) {
var ln, vn *yaml.Node var ln, vn *yaml.Node
var circError error var circError error
@@ -190,12 +194,15 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
// do we want to throw an error as well if circular error reporting is on?
if circError != nil && !idx.AllowCircularReferenceResolving() { if circError != nil && !idx.AllowCircularReferenceResolving() {
return res, circError return res, circError
} }
return res, nil return res, nil
} }
// ExtractArray will extract a slice of []ValueReference[T] from a root yaml.Node that is defined as a sequence.
// Used when the value being extracted is an array.
func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T], func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T],
*yaml.Node, *yaml.Node, error) { *yaml.Node, *yaml.Node, error) {
var ln, vn *yaml.Node var ln, vn *yaml.Node
@@ -266,12 +273,15 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
}) })
} }
} }
// include circular errors?
if circError != nil && !idx.AllowCircularReferenceResolving() { if circError != nil && !idx.AllowCircularReferenceResolving() {
return items, ln, vn, circError return items, ln, vn, circError
} }
return items, ln, vn, nil return items, ln, vn, nil
} }
// ExtractExample will extract a value supplied as an example into a NodeReference. Value can be anything.
// the node value is untyped, so casting will be required when trying to use it.
func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] { func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
ref := NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode} ref := NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
if utils.IsNodeMap(expNode) { if utils.IsNodeMap(expNode) {
@@ -287,7 +297,15 @@ func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
return ref return ref
} }
func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], error) { // ExtractMapNoLookup will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'NoLookup' part
// refers to the fact that there is no key supplied as part of the extraction, there is no lookup performed and the
// root yaml.Node pointer is used directly.
//
// This is useful when the node to be extracted, is already known and does not require a search.
func ExtractMapNoLookup[PT Buildable[N], N any](
root *yaml.Node,
idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], error) {
valueMap := make(map[KeyReference[string]]ValueReference[PT]) valueMap := make(map[KeyReference[string]]ValueReference[PT])
var circError error var circError error
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
@@ -350,7 +368,16 @@ type mappingResult[T any] struct {
v ValueReference[T] v ValueReference[T]
} }
func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) { // ExtractMap will extract a map of KeyReference and ValueReference from a root yaml.Node. The 'label' is
// used to locate the node to be extracted from the root node supplied.
//
// The second return value is the yaml.Node found for the 'label' and the third return value is the yaml.Node
// found for the value extracted from the label node.
func ExtractMap[PT Buildable[N], N any](
label string,
root *yaml.Node,
idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
var labelNode, valueNode *yaml.Node var labelNode, valueNode *yaml.Node
var circError error var circError error
if rf, rl, _ := utils.IsNodeRefValue(root); rf { if rf, rl, _ := utils.IsNodeRefValue(root); rf {
@@ -459,6 +486,14 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
return nil, labelNode, valueNode, nil return nil, labelNode, valueNode, nil
} }
// ExtractExtensions will extract any 'x-' prefixed key nodes from a root node into a map. Values have been pre-cast:
//
// Maps
// map[string]interface{} for maps
// Slices
// []interface{}
// int, float, bool, string
// int64, float64, bool, string
func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] { func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] {
extensions := utils.FindExtensionNodes(root.Content) extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[KeyReference[string]]ValueReference[any]) extensionMap := make(map[KeyReference[string]]ValueReference[any])

View File

@@ -942,7 +942,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, err := ExtractMapFlatNoLookup[*test_Good](cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -968,7 +968,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, err := ExtractMapFlatNoLookup[*test_Good](cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -994,7 +994,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, err := ExtractMapFlatNoLookup[*test_Good](cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
@@ -1026,7 +1026,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, err := ExtractMapFlatNoLookup[*test_Good](cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_Good](cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -1052,7 +1052,7 @@ hello:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, err := ExtractMapFlatNoLookup[*test_noGood](cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_noGood](cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
@@ -1078,7 +1078,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, err := ExtractMapFlatNoLookup[*test_almostGood](cNode.Content[0], idx) things, err := ExtractMapNoLookup[*test_almostGood](cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
@@ -1101,7 +1101,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -1128,7 +1128,7 @@ one:
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -1159,7 +1159,7 @@ func TestExtractMapFlat_DoubleRef(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -1189,7 +1189,7 @@ func TestExtractMapFlat_DoubleRef_Error(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_almostGood]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_almostGood]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
@@ -1216,7 +1216,7 @@ func TestExtractMapFlat_DoubleRef_Error_NotFound(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_almostGood]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_almostGood]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
@@ -1248,7 +1248,7 @@ func TestExtractMapFlat_DoubleRef_Circles(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
@@ -1275,7 +1275,7 @@ func TestExtractMapFlat_Ref_Error(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_almostGood]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_almostGood]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
@@ -1305,7 +1305,7 @@ func TestExtractMapFlat_Ref_Circ_Error(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
} }
@@ -1335,7 +1335,7 @@ func TestExtractMapFlat_Ref_Nested_Circ_Error(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)
} }
@@ -1361,7 +1361,7 @@ func TestExtractMapFlat_Ref_Nested_Error(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
} }
@@ -1387,7 +1387,7 @@ func TestExtractMapFlat_BadKey_Ref_Nested_Error(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("not-even-there", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("not-even-there", cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
} }
@@ -1416,7 +1416,7 @@ func TestExtractMapFlat_Ref_Bad(t *testing.T) {
e := yaml.Unmarshal([]byte(yml), &cNode) e := yaml.Unmarshal([]byte(yml), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, things, 0) assert.Len(t, things, 0)
} }
@@ -1447,7 +1447,7 @@ func TestLocateRefNode_RemoteFile(t *testing.T) {
e := yaml.Unmarshal([]byte(ymlLocal), &cNode) e := yaml.Unmarshal([]byte(ymlLocal), &cNode)
assert.NoError(t, e) assert.NoError(t, e)
things, _, _, err := ExtractMapFlat[*test_Good]("one", cNode.Content[0], idx) things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, things, 1) assert.Len(t, things, 1)

View File

@@ -1,12 +0,0 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package low
//import "go.uber.org/zap"
//
//var Log *zap.Logger
//
//func init() {
// Log, _ = zap.NewProduction()
//}

14
datamodel/low/low.go Normal file
View File

@@ -0,0 +1,14 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
// Package low contains a set of low-level models that represent OpenAPI 2 and 3 documents.
// These low-level models (plumbing) are used to create high-level models, and used when deep knowledge
// about the original data, positions, comments and the original node structures.
//
// Low-level models are not designed to be easily navigated, every single property is either a NodeReference
// an KeyReference or a ValueReference. These references hold the raw value and key or value nodes that contain
// the original yaml.Node trees that make up the object.
//
// Navigating maps that use a KeyReference as a key is tricky, because there is no easy way to provide a lookup.
// Convenience methods for lookup up properties in a low-level model have therefore been provided.
package low

View File

@@ -12,6 +12,11 @@ import (
"sync" "sync"
) )
// BuildModel accepts a yaml.Node pointer and a model, which can be any struct. Using reflection, the model is
// analyzed and the names of all the properties are extracted from the model and subsequently looked up from within
// the yaml.Node.Content value.
//
// BuildModel is non-recursive and will only build out a single layer of the node tree.
func BuildModel(node *yaml.Node, model interface{}) error { func BuildModel(node *yaml.Node, model interface{}) error {
if node == nil { if node == nil {
return nil return nil
@@ -35,6 +40,7 @@ func BuildModel(node *yaml.Node, model interface{}) error {
} }
// we need to find a matching field in the YAML, the cases may be off, so take no chances. // we need to find a matching field in the YAML, the cases may be off, so take no chances.
// TODO: investigate if a straight up to_lower will speed things up here, will it decrease or increase accuracy?
cases := []utils.Case{utils.CamelCase, utils.PascalCase, utils.ScreamingSnakeCase, cases := []utils.Case{utils.CamelCase, utils.PascalCase, utils.ScreamingSnakeCase,
utils.SnakeCase, utils.KebabCase, utils.RegularCase} utils.SnakeCase, utils.KebabCase, utils.RegularCase}
@@ -68,6 +74,9 @@ func BuildModel(node *yaml.Node, model interface{}) error {
return nil return nil
} }
// SetField accepts a field reflection value, a yaml.Node valueNode and a yaml.Node keyNode. Using reflection, the
// function will attempt to set the value of the field based on the key and value nodes. This method is only useful
// for low-level models, it has no value to high-level ones.
func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error { func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error {
switch field.Type() { switch field.Type() {
@@ -198,7 +207,7 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
break break
case reflect.TypeOf(NodeReference[int64]{}): case reflect.TypeOf(NodeReference[int64]{}):
if valueNode != nil { if valueNode != nil {
if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) { // if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseInt(valueNode.Value, 10, 64) fv, _ := strconv.ParseInt(valueNode.Value, 10, 64)
nr := NodeReference[int64]{ nr := NodeReference[int64]{
@@ -422,6 +431,7 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
return nil return nil
} }
// BuildModelAsync is a convenience function for calling BuildModel from a goroutine, requires a sync.WaitGroup
func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) { func BuildModelAsync(n *yaml.Node, model interface{}, lwg *sync.WaitGroup, errors *[]error) {
if n != nil { if n != nil {
err := BuildModel(n, model) err := BuildModel(n, model)

View File

@@ -7,67 +7,99 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type HasNode interface { // Buildable is an interface for any struct that can be 'built out'. This means that a struct can accept
GetNode() *yaml.Node // a root node and a reference to the index that carries data about any references used.
} //
// Used by generic functions when automatically building out structs based on yaml.Node inputs.
type Buildable[T any] interface { type Buildable[T any] interface {
Build(node *yaml.Node, idx *index.SpecIndex) error Build(node *yaml.Node, idx *index.SpecIndex) error
*T *T
} }
// NodeReference is a low-level container for holding a Value of type T, as well as references to
// a key yaml.Node that points to the key node that contains the value node, and the value node that contains
// the actual value.
type NodeReference[T any] struct { type NodeReference[T any] struct {
Value T
ValueNode *yaml.Node
KeyNode *yaml.Node
}
type KeyReference[T any] struct { // The value being referenced
Value T Value T
// The yaml.Node that holds the value
ValueNode *yaml.Node
// The yaml.Node that is the key, that contains the value.
KeyNode *yaml.Node KeyNode *yaml.Node
} }
// KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the
// yaml.Node that holds a key to a value.
type KeyReference[T any] struct {
// The value being referenced.
Value T
// The yaml.Node that holds this referenced key
KeyNode *yaml.Node
}
// ValueReference is a low-level container for value nodes that hold a Value of type T. A ValueNode is a pointer
// to the yaml.Node that holds the value.
type ValueReference[T any] struct { type ValueReference[T any] struct {
Value T
// The value being referenced.
Value T
// The yaml.Node that holds the referenced value
ValueNode *yaml.Node ValueNode *yaml.Node
} }
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n NodeReference[T]) IsEmpty() bool { func (n NodeReference[T]) IsEmpty() bool {
return n.KeyNode == nil && n.ValueNode == nil return n.KeyNode == nil && n.ValueNode == nil
} }
// GenerateMapKey will return a string based on the line and column number of the node, e.g. 33:56 for line 33, col 56.
func (n NodeReference[T]) GenerateMapKey() string { func (n NodeReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column) return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
} }
// Mutate will set the reference value to what is supplied. This happens to both the Value and ValueNode, which means
// the root document is permanently mutated and changes will be reflected in any serialization of the root document.
func (n NodeReference[T]) Mutate(value T) NodeReference[T] { func (n NodeReference[T]) Mutate(value T) NodeReference[T] {
n.ValueNode.Value = fmt.Sprintf("%v", value) n.ValueNode.Value = fmt.Sprintf("%v", value)
n.Value = value n.Value = value
return n return n
} }
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n ValueReference[T]) IsEmpty() bool { func (n ValueReference[T]) IsEmpty() bool {
return n.ValueNode == nil return n.ValueNode == nil
} }
// GenerateMapKey will return a string based on the line and column number of the node, e.g. 33:56 for line 33, col 56.
func (n ValueReference[T]) GenerateMapKey() string { func (n ValueReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column) return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
} }
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n KeyReference[T]) IsEmpty() bool { func (n KeyReference[T]) IsEmpty() bool {
return n.KeyNode == nil return n.KeyNode == nil
} }
// GenerateMapKey will return a string based on the line and column number of the node, e.g. 33:56 for line 33, col 56.
func (n KeyReference[T]) GenerateMapKey() string { func (n KeyReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column) return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column)
} }
// Mutate will set the reference value to what is supplied. This happens to both the Value and ValueNode, which means
// the root document is permanently mutated and changes will be reflected in any serialization of the root document.
func (n ValueReference[T]) Mutate(value T) ValueReference[T] { func (n ValueReference[T]) Mutate(value T) ValueReference[T] {
n.ValueNode.Value = fmt.Sprintf("%v", value) n.ValueNode.Value = fmt.Sprintf("%v", value)
n.Value = value n.Value = value
return n return n
} }
// IsCircular will determine if the node in question, is part of a circular reference chain discovered by the index.
func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool { func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool {
if idx == nil { if idx == nil {
return false // no index! nothing we can do. return false // no index! nothing we can do.
@@ -94,6 +126,8 @@ func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool {
return false return false
} }
// GetCircularReferenceResult will check if a node is part of a circular reference chain and then return that
// index.CircularReferenceResult it was located in. Returns nil if not found.
func GetCircularReferenceResult(node *yaml.Node, idx *index.SpecIndex) *index.CircularReferenceResult { func GetCircularReferenceResult(node *yaml.Node, idx *index.SpecIndex) *index.CircularReferenceResult {
if idx == nil { if idx == nil {
return nil // no index! nothing we can do. return nil // no index! nothing we can do.
@@ -118,6 +152,5 @@ func GetCircularReferenceResult(node *yaml.Node, idx *index.SpecIndex) *index.Ci
} }
} }
} }
return nil return nil
} }

View File

@@ -48,7 +48,7 @@ func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Examples = examples r.Examples = examples
//extract headers //extract headers
headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx) headers, lN, kN, err := low.ExtractMap[*Header](HeadersLabel, root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -21,7 +21,7 @@ func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
codes, err := low.ExtractMapFlatNoLookup[*Response](root, idx) codes, err := low.ExtractMapNoLookup[*Response](root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -177,7 +177,7 @@ func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
} }
func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
hooks, hooksL, hooksN, eErr := low.ExtractMapFlat[*PathItem](WebhooksLabel, info.RootNode, idx) hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](WebhooksLabel, info.RootNode, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }

View File

@@ -27,7 +27,7 @@ func (en *Encoding) FindHeader(hType string) *low.ValueReference[*Header] {
func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error { func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error {
headers, hL, hN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx) headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -52,7 +52,7 @@ func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMapFlat[*base.Example](base.ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
@@ -74,7 +74,7 @@ func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMapFlat[*MediaType](ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }

View File

@@ -54,7 +54,7 @@ func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMapFlat[*base.Example](base.ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
@@ -67,7 +67,7 @@ func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle encoding // handle encoding
encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx) encs, encsL, encsN, encErr := low.ExtractMap[*Encoding](EncodingLabel, root, idx)
if encErr != nil { if encErr != nil {
return encErr return encErr
} }

View File

@@ -76,7 +76,7 @@ func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
o.Responses = respBody o.Responses = respBody
// extract callbacks // extract callbacks
callbacks, cbL, cbN, cbErr := low.ExtractMapFlat[*Callback](CallbacksLabel, root, idx) callbacks, cbL, cbN, cbErr := low.ExtractMap[*Callback](CallbacksLabel, root, idx)
if cbErr != nil { if cbErr != nil {
return cbErr return cbErr
} }

View File

@@ -63,7 +63,7 @@ func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMapFlat[*base.Example](base.ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
@@ -76,7 +76,7 @@ func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMapFlat[*MediaType](ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }

View File

@@ -28,7 +28,7 @@ func (rb *RequestBody) Build(root *yaml.Node, idx *index.SpecIndex) error {
rb.Extensions = low.ExtractExtensions(root) rb.Extensions = low.ExtractExtensions(root)
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMapFlat[*MediaType](ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }

View File

@@ -23,7 +23,7 @@ type Responses struct {
func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
codes, err := low.ExtractMapFlatNoLookup[*Response](root, idx) codes, err := low.ExtractMapNoLookup[*Response](root, idx)
if err != nil { if err != nil {
return err return err
} }
@@ -76,7 +76,7 @@ func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
//extract headers //extract headers
headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx) headers, lN, kN, err := low.ExtractMap[*Header](HeadersLabel, root, idx)
if err != nil { if err != nil {
return err return err
} }
@@ -88,7 +88,7 @@ func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
con, clN, cN, cErr := low.ExtractMapFlat[*MediaType](ContentLabel, root, idx) con, clN, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }
@@ -101,7 +101,7 @@ func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle links if set // handle links if set
links, linkLabel, linkValue, lErr := low.ExtractMapFlat[*Link](LinksLabel, root, idx) links, linkLabel, linkValue, lErr := low.ExtractMap[*Link](LinksLabel, root, idx)
if lErr != nil { if lErr != nil {
return lErr return lErr
} }