mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-10 04:20:24 +00:00
The case checking seems kinda dumb now looking back at this code. I am not sure why I felt the need to do that, however after being aware of it for some time and not being happy with the extra cycles it puts the code through. This change removes ConvertCase from the utils package, as it's no longer used or needed right now. If it needs co come back, we can re-add the code, but deleting code always makes me happy. It also removed a dependency from the project, which reduces the footprint, great!
453 lines
12 KiB
Go
453 lines
12 KiB
Go
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package low
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/pb33f/libopenapi/utils"
|
|
"gopkg.in/yaml.v3"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"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 {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
if reflect.ValueOf(model).Type().Kind() != reflect.Pointer {
|
|
return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind())
|
|
}
|
|
v := reflect.ValueOf(model).Elem()
|
|
num := v.NumField()
|
|
for i := 0; i < num; i++ {
|
|
|
|
fName := v.Type().Field(i).Name
|
|
|
|
if fName == "Extensions" {
|
|
continue // internal construct
|
|
}
|
|
|
|
if fName == "PathItems" {
|
|
continue // internal construct
|
|
}
|
|
|
|
kn, vn := utils.FindKeyNodeTop(strings.ToLower(fName), node.Content)
|
|
if vn == nil {
|
|
// no point in going on.
|
|
continue
|
|
}
|
|
|
|
field := v.FieldByName(fName)
|
|
kind := field.Kind()
|
|
switch kind {
|
|
case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer:
|
|
err := SetField(field, vn, kn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unable to parse unsupported type: %v", kind)
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
|
switch field.Type() {
|
|
|
|
case reflect.TypeOf(map[string]NodeReference[any]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeMap(valueNode) {
|
|
if field.CanSet() {
|
|
items := make(map[string]NodeReference[any])
|
|
var currentLabel string
|
|
for i, sliceItem := range valueNode.Content {
|
|
if i%2 == 0 {
|
|
currentLabel = sliceItem.Value
|
|
continue
|
|
}
|
|
var decoded map[string]interface{}
|
|
// I cannot think of a way to make this error out by this point.
|
|
_ = sliceItem.Decode(&decoded)
|
|
items[currentLabel] = NodeReference[any]{
|
|
Value: decoded,
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
}
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
|
|
case reflect.TypeOf(map[string]NodeReference[string]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeMap(valueNode) {
|
|
if field.CanSet() {
|
|
items := make(map[string]NodeReference[string])
|
|
var currentLabel string
|
|
for i, sliceItem := range valueNode.Content {
|
|
if i%2 == 0 {
|
|
currentLabel = sliceItem.Value
|
|
continue
|
|
}
|
|
items[currentLabel] = NodeReference[string]{
|
|
Value: fmt.Sprintf("%v", sliceItem.Value),
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
}
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[any]{}):
|
|
if valueNode != nil {
|
|
var decoded interface{}
|
|
_ = valueNode.Decode(&decoded)
|
|
if utils.IsNodeMap(valueNode) {
|
|
if field.CanSet() {
|
|
or := NodeReference[any]{Value: decoded, ValueNode: valueNode}
|
|
field.Set(reflect.ValueOf(or))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[any]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[any]
|
|
for _, sliceItem := range valueNode.Content {
|
|
var decoded map[string]interface{}
|
|
err := sliceItem.Decode(&decoded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
items = append(items, NodeReference[any]{
|
|
Value: decoded,
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[string]{}):
|
|
if valueNode != nil {
|
|
if field.CanSet() {
|
|
nr := NodeReference[string]{
|
|
Value: fmt.Sprintf("%v", valueNode.Value),
|
|
ValueNode: valueNode,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(nr))
|
|
}
|
|
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[bool]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeBoolValue(valueNode) {
|
|
if field.CanSet() {
|
|
bv, _ := strconv.ParseBool(valueNode.Value)
|
|
nr := NodeReference[bool]{
|
|
Value: bv,
|
|
ValueNode: valueNode,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(nr))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[int]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeIntValue(valueNode) {
|
|
if field.CanSet() {
|
|
fv, _ := strconv.Atoi(valueNode.Value)
|
|
nr := NodeReference[int]{
|
|
Value: fv,
|
|
ValueNode: valueNode,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(nr))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[int64]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) {
|
|
if field.CanSet() {
|
|
fv, _ := strconv.ParseInt(valueNode.Value, 10, 64)
|
|
nr := NodeReference[int64]{
|
|
Value: fv,
|
|
ValueNode: valueNode,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(nr))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[float32]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeFloatValue(valueNode) {
|
|
if field.CanSet() {
|
|
fv, _ := strconv.ParseFloat(valueNode.Value, 32)
|
|
nr := NodeReference[float32]{
|
|
Value: float32(fv),
|
|
ValueNode: valueNode,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(nr))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf(NodeReference[float64]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeFloatValue(valueNode) {
|
|
if field.CanSet() {
|
|
fv, _ := strconv.ParseFloat(valueNode.Value, 64)
|
|
nr := NodeReference[float64]{
|
|
Value: fv,
|
|
ValueNode: valueNode,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(nr))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[string]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[string]
|
|
for _, sliceItem := range valueNode.Content {
|
|
items = append(items, NodeReference[string]{
|
|
Value: sliceItem.Value,
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[float32]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[float32]
|
|
for _, sliceItem := range valueNode.Content {
|
|
fv, _ := strconv.ParseFloat(sliceItem.Value, 32)
|
|
items = append(items, NodeReference[float32]{
|
|
Value: float32(fv),
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[float64]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[float64]
|
|
for _, sliceItem := range valueNode.Content {
|
|
fv, _ := strconv.ParseFloat(sliceItem.Value, 64)
|
|
items = append(items, NodeReference[float64]{Value: fv, ValueNode: sliceItem})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[int]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[int]
|
|
for _, sliceItem := range valueNode.Content {
|
|
iv, _ := strconv.Atoi(sliceItem.Value)
|
|
items = append(items, NodeReference[int]{
|
|
Value: iv,
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[int64]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[int64]
|
|
for _, sliceItem := range valueNode.Content {
|
|
iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64)
|
|
items = append(items, NodeReference[int64]{
|
|
Value: iv,
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
case reflect.TypeOf([]NodeReference[bool]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []NodeReference[bool]
|
|
for _, sliceItem := range valueNode.Content {
|
|
bv, _ := strconv.ParseBool(sliceItem.Value)
|
|
items = append(items, NodeReference[bool]{
|
|
Value: bv,
|
|
ValueNode: sliceItem,
|
|
KeyNode: valueNode,
|
|
})
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
break
|
|
// helper for unpacking string maps.
|
|
case reflect.TypeOf(map[KeyReference[string]]ValueReference[string]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeMap(valueNode) {
|
|
if field.CanSet() {
|
|
items := make(map[KeyReference[string]]ValueReference[string])
|
|
var cf *yaml.Node
|
|
for i, sliceItem := range valueNode.Content {
|
|
if i%2 == 0 {
|
|
cf = sliceItem
|
|
continue
|
|
}
|
|
items[KeyReference[string]{
|
|
Value: cf.Value,
|
|
KeyNode: cf,
|
|
}] = ValueReference[string]{
|
|
Value: sliceItem.Value,
|
|
ValueNode: sliceItem,
|
|
}
|
|
}
|
|
field.Set(reflect.ValueOf(items))
|
|
}
|
|
}
|
|
}
|
|
case reflect.TypeOf(KeyReference[map[KeyReference[string]]ValueReference[string]]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeMap(valueNode) {
|
|
if field.CanSet() {
|
|
items := make(map[KeyReference[string]]ValueReference[string])
|
|
var cf *yaml.Node
|
|
for i, sliceItem := range valueNode.Content {
|
|
if i%2 == 0 {
|
|
cf = sliceItem
|
|
continue
|
|
}
|
|
items[KeyReference[string]{
|
|
Value: cf.Value,
|
|
KeyNode: cf,
|
|
}] = ValueReference[string]{
|
|
Value: sliceItem.Value,
|
|
ValueNode: sliceItem,
|
|
}
|
|
}
|
|
ref := KeyReference[map[KeyReference[string]]ValueReference[string]]{
|
|
Value: items,
|
|
KeyNode: keyNode,
|
|
}
|
|
field.Set(reflect.ValueOf(ref))
|
|
}
|
|
}
|
|
}
|
|
case reflect.TypeOf(NodeReference[[]ValueReference[string]]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []ValueReference[string]
|
|
for _, sliceItem := range valueNode.Content {
|
|
items = append(items, ValueReference[string]{
|
|
Value: sliceItem.Value,
|
|
ValueNode: sliceItem,
|
|
})
|
|
}
|
|
n := NodeReference[[]ValueReference[string]]{
|
|
Value: items,
|
|
KeyNode: keyNode,
|
|
ValueNode: valueNode,
|
|
}
|
|
field.Set(reflect.ValueOf(n))
|
|
}
|
|
}
|
|
}
|
|
case reflect.TypeOf(NodeReference[[]ValueReference[any]]{}):
|
|
if valueNode != nil {
|
|
if utils.IsNodeArray(valueNode) {
|
|
if field.CanSet() {
|
|
var items []ValueReference[any]
|
|
for _, sliceItem := range valueNode.Content {
|
|
items = append(items, ValueReference[any]{
|
|
Value: sliceItem.Value,
|
|
ValueNode: sliceItem,
|
|
})
|
|
}
|
|
n := NodeReference[[]ValueReference[any]]{
|
|
Value: items,
|
|
KeyNode: keyNode,
|
|
ValueNode: valueNode,
|
|
}
|
|
field.Set(reflect.ValueOf(n))
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
// we want to ignore everything else, each model handles its own complex types.
|
|
break
|
|
}
|
|
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) {
|
|
if n != nil {
|
|
err := BuildModel(n, model)
|
|
if err != nil {
|
|
*errors = append(*errors, err)
|
|
}
|
|
}
|
|
lwg.Done()
|
|
}
|