Files
libopenapi/datamodel/low/extraction_functions.go
Dave Shanley 67c701ff07 Refactoring extraction and builder functions.
now things are robust, we can move things around a little to prepare for the next set of incoming models. The extraction and builder functions have all been moved to the low packakge, and out of the v3 package.
2022-08-14 04:03:13 -04:00

397 lines
11 KiB
Go

package low
import (
"fmt"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3"
"strconv"
"strings"
"sync"
)
func FindItemInMap[T any](item string, collection map[KeyReference[string]]ValueReference[T]) *ValueReference[T] {
for n, o := range collection {
if n.Value == item {
return &o
}
}
return nil
}
var mapLock sync.Mutex
func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node {
if rf, _, rv := utils.IsNodeRefValue(root); rf {
// run through everything and return as soon as we find a match.
// this operates as fast as possible as ever
collections := []func() map[string]*index.Reference{
idx.GetAllSchemas,
idx.GetMappedReferences,
idx.GetAllExternalDocuments,
idx.GetAllParameters,
idx.GetAllHeaders,
idx.GetAllCallbacks,
idx.GetAllLinks,
idx.GetAllExternalDocuments,
idx.GetAllExamples,
idx.GetAllRequestBodies,
idx.GetAllResponses,
idx.GetAllSecuritySchemes,
}
var found map[string]*index.Reference
for _, collection := range collections {
found = collection()
if found != nil && found[rv] != nil {
return found[rv].Node
}
}
// cant be found? last resort is to try a path lookup
cleaned := strings.ReplaceAll(rv, "~1", "/")
cleaned = strings.ReplaceAll(cleaned, "#/paths/", "")
path, err := yamlpath.NewPath(fmt.Sprintf("$.paths.%s", cleaned))
if err == nil {
nodes, fErr := path.Find(idx.GetRootNode())
if fErr == nil {
if len(nodes) > 0 {
return nodes[0]
}
}
}
}
return nil
}
func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (T, error) {
var n T = new(N)
err := BuildModel(root, n)
if err != nil {
return n, err
}
err = n.Build(root, idx)
if err != nil {
return n, err
}
return n, nil
}
func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (NodeReference[T], error) {
var ln, vn *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
if ref != nil {
vn = ref
ln = rl
} else {
return NodeReference[T]{}, fmt.Errorf("object build failed: reference cannot be found: %s",
root.Content[1].Value)
}
} else {
_, ln, vn = utils.FindKeyNodeFull(label, root.Content)
if vn != nil {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := LocateRefNode(vn, idx)
if ref != nil {
vn = ref
} else {
return NodeReference[T]{}, fmt.Errorf("object build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
}
}
var n T = new(N)
err := BuildModel(vn, n)
if err != nil {
return NodeReference[T]{}, err
}
if ln == nil {
return NodeReference[T]{}, nil
}
err = n.Build(vn, idx)
if err != nil {
return NodeReference[T]{}, err
}
return NodeReference[T]{
Value: n,
KeyNode: ln,
ValueNode: vn,
}, nil
}
func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T],
*yaml.Node, *yaml.Node, error) {
var ln, vn *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
if ref != nil {
vn = ref
ln = rl
} else {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
root.Content[1].Value)
}
} else {
_, ln, vn = utils.FindKeyNodeFull(label, root.Content)
if vn != nil {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := LocateRefNode(vn, idx)
if ref != nil {
vn = ref
} else {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
}
}
var items []ValueReference[T]
if vn != nil && ln != nil {
for _, node := range vn.Content {
if rf, _, _ := utils.IsNodeRefValue(node); rf {
ref := LocateRefNode(node, idx)
if ref != nil {
node = ref
} else {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
var n T = new(N)
err := BuildModel(node, n)
if err != nil {
return []ValueReference[T]{}, ln, vn, err
}
berr := n.Build(node, idx)
if berr != nil {
return nil, ln, vn, berr
}
items = append(items, ValueReference[T]{
Value: n,
ValueNode: node,
})
}
}
return items, ln, vn, nil
}
func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], error) {
valueMap := make(map[KeyReference[string]]ValueReference[PT])
if utils.IsNodeMap(root) {
var currentKey *yaml.Node
for i, node := range root.Content {
if i%2 == 0 {
currentKey = node
continue
}
// if value is a reference, we have to look it up in the index!
if h, _, _ := utils.IsNodeRefValue(node); h {
ref := LocateRefNode(node, idx)
if ref != nil {
node = ref
} else {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s", root.Content[1].Value)
}
}
var n PT = new(N)
err := BuildModel(node, n)
if err != nil {
return nil, err
}
berr := n.Build(node, idx)
if berr != nil {
return nil, berr
}
valueMap[KeyReference[string]{
Value: currentKey.Value,
KeyNode: currentKey,
}] = ValueReference[PT]{
Value: n,
ValueNode: node,
}
}
}
return valueMap, nil
}
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) {
var labelNode, valueNode *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
} else {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
} else {
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
if valueNode != nil {
if h, _, _ := utils.IsNodeRefValue(valueNode); h {
ref := LocateRefNode(valueNode, idx)
if ref != nil {
valueNode = ref
} else {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
}
}
if valueNode != nil {
var currentLabelNode *yaml.Node
valueMap := make(map[KeyReference[string]]ValueReference[PT])
for i, en := range valueNode.Content {
if i%2 == 0 {
currentLabelNode = en
continue
}
// check our valueNode isn't a reference still.
if h, _, _ := utils.IsNodeRefValue(en); h {
ref := LocateRefNode(en, idx)
if ref != nil {
en = ref
} else {
return nil, labelNode, valueNode, fmt.Errorf("flat map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
var n PT = new(N)
err := BuildModel(en, n)
if err != nil {
return nil, labelNode, valueNode, err
}
berr := n.Build(en, idx)
if berr != nil {
return nil, labelNode, valueNode, berr
}
valueMap[KeyReference[string]{
Value: currentLabelNode.Value,
KeyNode: currentLabelNode,
}] = ValueReference[PT]{
Value: n,
ValueNode: en,
}
}
return valueMap, labelNode, valueNode, nil
}
return nil, labelNode, valueNode, nil
}
func ExtractMap[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]map[KeyReference[string]]ValueReference[PT], error) {
var labelNode, valueNode *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
} else {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s", root.Content[1].Value)
}
} else {
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
}
if valueNode != nil {
var currentLabelNode *yaml.Node
valueMap := make(map[KeyReference[string]]ValueReference[PT])
for i, en := range valueNode.Content {
if i%2 == 0 {
currentLabelNode = en
continue
}
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
var n PT = new(N)
err := BuildModel(en, n)
if err != nil {
return nil, err
}
berr := n.Build(en, idx)
if berr != nil {
return nil, berr
}
valueMap[KeyReference[string]{
Value: currentLabelNode.Value,
KeyNode: currentLabelNode,
}] = ValueReference[PT]{
Value: n,
ValueNode: en,
}
}
resMap := make(map[KeyReference[string]]map[KeyReference[string]]ValueReference[PT])
resMap[KeyReference[string]{
Value: labelNode.Value,
KeyNode: labelNode,
}] = valueMap
return resMap, nil
}
return nil, nil
}
func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[KeyReference[string]]ValueReference[any])
for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) {
var v interface{}
_ = ext.Value.Decode(&v)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: v, ValueNode: ext.Value}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
_ = ext.Value.Decode(&v)
extensionMap[KeyReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = ValueReference[any]{Value: v, ValueNode: ext.Value}
}
}
return extensionMap
}