Adding more model building code.

Seems to be working pretty well so far.
This commit is contained in:
Dave Shanley
2022-07-30 08:56:09 -04:00
parent ae4a40fb38
commit 4e3a5584c3
5 changed files with 381 additions and 78 deletions

View File

@@ -16,6 +16,6 @@ type NodeReference[T comparable] struct {
}
type ObjectReference struct {
Value interface{}
Value map[string]interface{}
Node *yaml.Node
}

View File

@@ -2,7 +2,6 @@ package utils
import (
"fmt"
"github.com/iancoleman/strcase"
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"reflect"
@@ -14,73 +13,31 @@ func BuildModel(node *yaml.Node, model interface{}) error {
for i := 0; i < v.NumField(); i++ {
fName := v.Type().Field(i).Name
fieldName := strcase.ToLowerCamel(fName)
_, vn := FindKeyNode(fieldName, node.Content)
field := v.FieldByName(fName)
switch field.Kind() {
case reflect.Struct:
switch field.Type() {
case reflect.TypeOf(low.NodeReference[string]{}):
if vn != nil {
if IsNodeStringValue(vn) {
if field.CanSet() {
nr := low.NodeReference[string]{Value: vn.Value, Node: vn}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[bool]{}):
// we need to find a matching field in the YAML, the cases may be off, so take no chances.
cases := []Case{PascalCase, CamelCase, ScreamingSnakeCase,
SnakeCase, KebabCase, RegularCase}
var vn, kn *yaml.Node
for _, tryCase := range cases {
kn, vn = FindKeyNode(ConvertCase(fName, tryCase), node.Content)
if vn != nil {
if IsNodeBoolValue(vn) {
if field.CanSet() {
bv, _ := strconv.ParseBool(vn.Value)
nr := low.NodeReference[bool]{Value: bv, Node: vn}
field.Set(reflect.ValueOf(nr))
}
}
}
case reflect.TypeOf(low.NodeReference[int]{}):
if vn != nil {
if IsNodeIntValue(vn) {
if field.CanSet() {
fv, _ := strconv.Atoi(vn.Value)
nr := low.NodeReference[int]{Value: fv, Node: vn}
field.Set(reflect.ValueOf(nr))
}
}
}
case reflect.TypeOf(low.NodeReference[int64]{}):
if vn != nil {
if IsNodeIntValue(vn) {
if field.CanSet() {
fv, _ := strconv.Atoi(vn.Value)
nr := low.NodeReference[int64]{Value: int64(fv), Node: vn}
field.Set(reflect.ValueOf(nr))
}
}
}
case reflect.TypeOf(low.NodeReference[float32]{}):
if vn != nil {
if IsNodeFloatValue(vn) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(vn.Value, 32)
nr := low.NodeReference[float32]{Value: float32(fv), Node: vn}
field.Set(reflect.ValueOf(nr))
}
}
}
case reflect.TypeOf(low.NodeReference[float64]{}):
if vn != nil {
if IsNodeFloatValue(vn) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(vn.Value, 64)
nr := low.NodeReference[float64]{Value: fv, Node: vn}
field.Set(reflect.ValueOf(nr))
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:
err := SetField(field, vn, kn)
if err != nil {
return nil
}
default:
fmt.Printf("Unsupported type: %v", v.Field(i).Kind())
@@ -91,3 +48,211 @@ func BuildModel(node *yaml.Node, model interface{}) error {
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 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,
Node: sliceItem,
}
}
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 IsNodeMap(valueNode) {
if field.CanSet() {
or := low.ObjectReference{Value: decoded, Node: valueNode}
field.Set(reflect.ValueOf(or))
}
}
}
break
case reflect.TypeOf([]low.ObjectReference{}):
if valueNode != nil {
if 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,
Node: sliceItem,
})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf(low.NodeReference[string]{}):
if valueNode != nil {
if IsNodeStringValue(valueNode) {
if field.CanSet() {
nr := low.NodeReference[string]{Value: valueNode.Value, Node: valueNode}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[bool]{}):
if valueNode != nil {
if IsNodeBoolValue(valueNode) {
if field.CanSet() {
bv, _ := strconv.ParseBool(valueNode.Value)
nr := low.NodeReference[bool]{Value: bv, Node: valueNode}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[int]{}):
if valueNode != nil {
if IsNodeIntValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.Atoi(valueNode.Value)
nr := low.NodeReference[int]{Value: fv, Node: valueNode}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[int64]{}):
if valueNode != nil {
if IsNodeIntValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.Atoi(valueNode.Value)
nr := low.NodeReference[int64]{Value: int64(fv), Node: valueNode}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[float32]{}):
if valueNode != nil {
if IsNodeFloatValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 32)
nr := low.NodeReference[float32]{Value: float32(fv), Node: valueNode}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf(low.NodeReference[float64]{}):
if valueNode != nil {
if IsNodeFloatValue(valueNode) {
if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 64)
nr := low.NodeReference[float64]{Value: fv, Node: valueNode}
field.Set(reflect.ValueOf(nr))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[string]{}):
if valueNode != nil {
if IsNodeArray(valueNode) {
if field.CanSet() {
var items []low.NodeReference[string]
for _, sliceItem := range valueNode.Content {
items = append(items, low.NodeReference[string]{Value: sliceItem.Value, Node: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[float32]{}):
if valueNode != nil {
if 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), Node: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[float64]{}):
if valueNode != nil {
if 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, Node: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[int]{}):
if valueNode != nil {
if 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, Node: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
case reflect.TypeOf([]low.NodeReference[bool]{}):
if valueNode != nil {
if 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, Node: sliceItem})
}
field.Set(reflect.ValueOf(items))
}
}
}
break
default:
m := field.Type()
fmt.Printf("error, unknown type!!! %v", m)
return fmt.Errorf("unknown type, cannot parse: %v", m)
}
return nil
}

View File

@@ -9,12 +9,19 @@ import (
type hotdog struct {
Name low.NodeReference[string]
Beef low.NodeReference[bool]
Fat low.NodeReference[int]
Ketchup low.NodeReference[float32]
Mustard low.NodeReference[float64]
Grilled low.NodeReference[bool]
MaxTemp low.NodeReference[int]
Drinks []low.NodeReference[string]
Sides []low.NodeReference[float32]
BigSides []low.NodeReference[float64]
Temps []low.NodeReference[int]
Buns []low.NodeReference[bool]
UnknownElements low.ObjectReference
LotsOfUnknowns []low.ObjectReference
Where map[string]low.ObjectReference
}
func (h hotdog) Build(node *yaml.Node) {
@@ -28,8 +35,46 @@ beef: true
fat: 200
ketchup: 200.45
mustard: 324938249028.98234892374892374923874823974
grilled: false
grilled: true
maxTemp: 250
drinks:
- nice
- rice
- spice
sides:
- 0.23
- 22.23
- 99.45
- 22311.2234
bigSides:
- 98237498.9872349872349872349872347982734927342983479234234234234234234
- 9827347234234.982374982734987234987
- 234234234.234982374982347982374982374982347
- 987234987234987234982734.987234987234987234987234987234987234987234982734982734982734987234987234987234987
temps:
- 1
- 2
buns:
- true
- false
unknownElements:
well:
whoKnows: not me?
doYou:
love: beerToo?
lotsOfUnknowns:
- wow:
what: aTrip
- amazing:
french: fries
- amazing:
french: fries
where:
things:
are:
wild: out here
howMany:
bears: 200
`
var rootNode yaml.Node
@@ -40,10 +85,18 @@ maxTemp: 250
cErr := BuildModel(&rootNode, &hd)
assert.Equal(t, 200, hd.Fat.Value)
assert.Equal(t, 3, hd.Fat.Node.Line)
assert.Equal(t, true, hd.Beef.Value)
assert.Equal(t, true, hd.Grilled.Value)
assert.Equal(t, "yummy", hd.Name.Value)
assert.Equal(t, float32(200.45), hd.Ketchup.Value)
assert.Len(t, hd.Drinks, 3)
assert.Len(t, hd.Sides, 4)
assert.Len(t, hd.BigSides, 4)
assert.Len(t, hd.Temps, 2)
assert.Equal(t, 2, hd.Temps[1].Value)
assert.Equal(t, 24, hd.Temps[1].Node.Line)
assert.Len(t, hd.UnknownElements.Value, 2)
assert.Len(t, hd.LotsOfUnknowns, 3)
assert.Len(t, hd.Where, 2)
assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value)
assert.NoError(t, cErr)
}

View File

@@ -3,12 +3,16 @@ package utils
import (
"encoding/json"
"fmt"
"github.com/iancoleman/strcase"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3"
"regexp"
"strconv"
"strings"
)
type Case int8
const (
// OpenApi3 is used by all OpenAPI 3+ docs
OpenApi3 = "openapi"
@@ -18,6 +22,14 @@ const (
// AsyncApi is used by akk AsyncAPI docs, all versions.
AsyncApi = "asyncapi"
PascalCase Case = iota
CamelCase
ScreamingSnakeCase
SnakeCase
KebabCase
ScreamingKebabCase
RegularCase
)
// FindNodes will find a node based on JSONPath, it accepts raw yaml/json as input.
@@ -439,3 +451,56 @@ func RenderCodeSnippet(startNode *yaml.Node, specData []string, before, after in
return buf.String()
}
func ConvertCase(input string, convert Case) string {
if input == "" {
return ""
}
switch convert {
case PascalCase:
return strcase.ToCamel(input)
case CamelCase:
return strcase.ToLowerCamel(input)
case ScreamingKebabCase:
return strcase.ToScreamingKebab(input)
case ScreamingSnakeCase:
return strcase.ToScreamingSnake(input)
case SnakeCase:
return strcase.ToSnake(input)
default:
return input
}
}
func DetectCase(input string) Case {
trim := strings.TrimSpace(input)
if trim == "" {
return -1
}
pascalCase := regexp.MustCompile("^[A-Z][a-z]+(?:[A-Z][a-z]+)*$")
camelCase := regexp.MustCompile("^[a-z]+(?:[A-Z][a-z]+)*$")
screamingSnakeCase := regexp.MustCompile("^[A-Z]+(_[A-Z]+)*$")
snakeCase := regexp.MustCompile("^[a-z]+(_[a-z]+)*$")
kebabCase := regexp.MustCompile("^[a-z]+(-[a-z]+)*$")
screamingKebabCase := regexp.MustCompile("^[A-Z]+(-[A-Z]+)*$")
if pascalCase.MatchString(trim) {
return PascalCase
}
if camelCase.MatchString(trim) {
return CamelCase
}
if screamingSnakeCase.MatchString(trim) {
return ScreamingSnakeCase
}
if snakeCase.MatchString(trim) {
return SnakeCase
}
if kebabCase.MatchString(trim) {
return KebabCase
}
if screamingKebabCase.MatchString(trim) {
return ScreamingKebabCase
}
return RegularCase
}

View File

@@ -419,3 +419,23 @@ func TestConvertComponentIdIntoPath(t *testing.T) {
assert.Equal(t, "$.chicken.chips.pizza.cake", path)
assert.Equal(t, "cake", segment)
}
func TestDetectCase(t *testing.T) {
assert.Equal(t, PascalCase, DetectCase("PizzaPie"))
assert.Equal(t, CamelCase, DetectCase("anyoneForTennis"))
assert.Equal(t, ScreamingSnakeCase, DetectCase("I_LOVE_BEER"))
assert.Equal(t, ScreamingKebabCase, DetectCase("I-LOVE-BURGERS"))
assert.Equal(t, SnakeCase, DetectCase("snakes_on_a_plane"))
assert.Equal(t, KebabCase, DetectCase("chicken-be-be-beef-or-pork"))
assert.Equal(t, RegularCase, DetectCase("kebab-TimeIn_london-TOWN"))
}
func TestConvertCase(t *testing.T) {
str1 := "chicken-nuggets-chicken-soup"
assert.Equal(t, "chickenNuggetsChickenSoup", ConvertCase(str1, CamelCase))
assert.Equal(t, "ChickenNuggetsChickenSoup", ConvertCase(str1, PascalCase))
assert.Equal(t, "chicken_nuggets_chicken_soup", ConvertCase(str1, SnakeCase))
assert.Equal(t, str1, ConvertCase(str1, KebabCase))
assert.Equal(t, "CHICKEN-NUGGETS-CHICKEN-SOUP", ConvertCase(str1, ScreamingKebabCase))
assert.Equal(t, "CHICKEN_NUGGETS_CHICKEN_SOUP", ConvertCase(str1, ScreamingSnakeCase))
}