mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-07 12:37:48 +00:00
Adding more model building code.
Seems to be working pretty well so far.
This commit is contained in:
@@ -16,6 +16,6 @@ type NodeReference[T comparable] struct {
|
||||
}
|
||||
|
||||
type ObjectReference struct {
|
||||
Value interface{}
|
||||
Value map[string]interface{}
|
||||
Node *yaml.Node
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user