Files
libopenapi/datamodel/model_builder.go
Dave Shanley 2f60694047 Added tag model build out with tests.
Working through patterns and re-applying them as I go, cleaning things up as I cook.
2022-07-31 12:04:15 -04:00

412 lines
11 KiB
Go

package datamodel
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"reflect"
"strconv"
"sync"
)
func BuildModel(node *yaml.Node, model interface{}) error {
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()
for i := 0; i < v.NumField(); i++ {
fName := v.Type().Field(i).Name
// we need to find a matching field in the YAML, the cases may be off, so take no chances.
cases := []utils.Case{utils.PascalCase, utils.CamelCase, utils.ScreamingSnakeCase,
utils.SnakeCase, utils.KebabCase, utils.RegularCase}
var vn, kn *yaml.Node
for _, tryCase := range cases {
kn, vn = utils.FindKeyNode(utils.ConvertCase(fName, tryCase), node.Content)
if vn != nil {
break
}
}
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
}
func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) error {
switch field.Type() {
case reflect.TypeOf(map[string]low.ObjectReference{}):
if valueNode != nil {
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[string]low.ObjectReference)
var currentLabel string
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
currentLabel = sliceItem.Value
continue
}
var decoded map[string]interface{}
err := sliceItem.Decode(&decoded)
if err != nil {
return err
}
items[currentLabel] = low.ObjectReference{
Value: decoded,
ValueNode: sliceItem,
KeyNode: valueNode,
}
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf(map[string]low.NodeReference[string]{}):
if valueNode != nil {
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
items := make(map[string]low.NodeReference[string])
var currentLabel string
for i, sliceItem := range valueNode.Content {
if i%2 == 0 {
currentLabel = sliceItem.Value
continue
}
items[currentLabel] = low.NodeReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
KeyNode: valueNode,
}
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf(low.ObjectReference{}):
if valueNode != nil {
var decoded map[string]interface{}
err := valueNode.Decode(&decoded)
if err != nil {
return err
}
if utils.IsNodeMap(valueNode) {
if field.CanSet() {
or := low.ObjectReference{Value: decoded, ValueNode: valueNode}
field.Set(reflect.ValueOf(or))
}
}
}
break
case reflect.TypeOf([]low.ObjectReference{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.ObjectReference
for _, sliceItem := range valueNode.Content {
var decoded map[string]interface{}
err := sliceItem.Decode(&decoded)
if err != nil {
return err
}
items = append(items, low.ObjectReference{
Value: decoded,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf(low.NodeReference[string]{}):
if valueNode != nil {
if utils.IsNodeStringValue(valueNode) {
if field.CanSet() {
nr := low.NodeReference[string]{
Value: valueNode.Value,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[bool]{}):
if valueNode != nil {
if utils.IsNodeBoolValue(valueNode) {
if field.CanSet() {
bv, _ := strconv.ParseBool(valueNode.Value)
nr := low.NodeReference[bool]{
Value: bv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[int]{}):
if valueNode != nil {
if utils.IsNodeIntValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.Atoi(valueNode.Value)
nr := low.NodeReference[int]{
Value: fv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[int64]{}):
if valueNode != nil {
if utils.IsNodeIntValue(valueNode) || utils.IsNodeFloatValue(valueNode) { //
if field.CanSet() {
fv, _ := strconv.ParseInt(valueNode.Value, 10, 64)
nr := low.NodeReference[int64]{
Value: fv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[float32]{}):
if valueNode != nil {
if utils.IsNodeFloatValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 32)
nr := low.NodeReference[float32]{
Value: float32(fv),
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[float64]{}):
if valueNode != nil {
if utils.IsNodeFloatValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 64)
nr := low.NodeReference[float64]{
Value: fv,
ValueNode: valueNode,
KeyNode: keyNode,
}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[string]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[string]
for _, sliceItem := range valueNode.Content {
items = append(items, low.NodeReference[string]{
Value: sliceItem.Value,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[float32]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[float32]
for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 32)
items = append(items, low.NodeReference[float32]{
Value: float32(fv),
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[float64]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[float64]
for _, sliceItem := range valueNode.Content {
fv, _ := strconv.ParseFloat(sliceItem.Value, 64)
items = append(items, low.NodeReference[float64]{Value: fv, ValueNode: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[int]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[int]
for _, sliceItem := range valueNode.Content {
iv, _ := strconv.Atoi(sliceItem.Value)
items = append(items, low.NodeReference[int]{
Value: iv,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[int64]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[int64]
for _, sliceItem := range valueNode.Content {
iv, _ := strconv.ParseInt(sliceItem.Value, 10, 64)
items = append(items, low.NodeReference[int64]{
Value: iv,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[bool]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[bool]
for _, sliceItem := range valueNode.Content {
bv, _ := strconv.ParseBool(sliceItem.Value)
items = append(items, low.NodeReference[bool]{
Value: bv,
ValueNode: sliceItem,
KeyNode: valueNode,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
default:
// we want to ignore everything else.
break
}
return nil
}
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()
}
func ExtractExtensions(root *yaml.Node) (map[low.NodeReference[string]]low.NodeReference[any], error) {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[low.NodeReference[string]]low.NodeReference[any])
for _, ext := range extensions {
if utils.IsNodeMap(ext.Value) {
var v interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: v, KeyNode: ext.Key}
}
if utils.IsNodeStringValue(ext.Value) {
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: ext.Value.Value, ValueNode: ext.Value}
}
if utils.IsNodeFloatValue(ext.Value) {
fv, _ := strconv.ParseFloat(ext.Value.Value, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: fv, ValueNode: ext.Value}
}
if utils.IsNodeIntValue(ext.Value) {
iv, _ := strconv.ParseInt(ext.Value.Value, 10, 64)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: iv, ValueNode: ext.Value}
}
if utils.IsNodeBoolValue(ext.Value) {
bv, _ := strconv.ParseBool(ext.Value.Value)
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: bv, ValueNode: ext.Value}
}
if utils.IsNodeArray(ext.Value) {
var v []interface{}
err := ext.Value.Decode(&v)
if err != nil {
return nil, err
}
extensionMap[low.NodeReference[string]{
Value: ext.Key.Value,
KeyNode: ext.Key,
}] = low.NodeReference[any]{Value: v, ValueNode: ext.Value}
}
}
return extensionMap, nil
}