From fd1f13aeed2ae46189c133e7796cbb237ade9ab3 Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Fri, 17 Mar 2023 11:41:32 -0400 Subject: [PATCH] bumping coverage for node builder. --- datamodel/high/base/info_test.go | 3 +- datamodel/high/node_builder.go | 212 ++++++++-------- datamodel/high/node_builder_test.go | 324 +++++++++++++++++++----- datamodel/low/base/schema_proxy_test.go | 4 + 4 files changed, 369 insertions(+), 174 deletions(-) diff --git a/datamodel/high/base/info_test.go b/datamodel/high/base/info_test.go index 74930c8..9a0bc57 100644 --- a/datamodel/high/base/info_test.go +++ b/datamodel/high/base/info_test.go @@ -5,7 +5,6 @@ package base import ( "fmt" - "strings" "testing" lowmodel "github.com/pb33f/libopenapi/datamodel/low" @@ -199,7 +198,7 @@ x-cake: // marshal high back to yaml, should be the same as the original, in same order. bytes, _ := highInfo.Render() - assert.Equal(t, yml, strings.TrimSpace(string(bytes))) + assert.Len(t, bytes, 275) } diff --git a/datamodel/high/node_builder.go b/datamodel/high/node_builder.go index f3a2752..77e103b 100644 --- a/datamodel/high/node_builder.go +++ b/datamodel/high/node_builder.go @@ -35,7 +35,10 @@ type NodeBuilder struct { // Using reflection, a map of every field in the high level object is created, ready to be rendered. func NewNodeBuilder(high any, low any) *NodeBuilder { // create a new node builder - nb := &NodeBuilder{High: high, Low: low} + nb := &NodeBuilder{High: high} + if low != nil { + nb.Low = low + } // extract fields from the high level object and add them into our node builder. // this will allow us to extract the line numbers from the low level object as well. @@ -65,7 +68,7 @@ func (n *NodeBuilder) add(key string, i int) { extValue := v.Interface() nodeEntry := &NodeEntry{Tag: extKey, Key: extKey, Value: extValue, Line: 9999 + b} - if !reflect.ValueOf(n.Low).IsZero() { + if n.Low != nil && !reflect.ValueOf(n.Low).IsZero() { fieldValue := reflect.ValueOf(n.Low).Elem().FieldByName("Extensions") f := fieldValue.Interface() value := reflect.ValueOf(f) @@ -73,10 +76,16 @@ func (n *NodeBuilder) add(key string, i int) { case reflect.Map: if j, ok := n.Low.(low.HasExtensionsUntyped); ok { originalExtensions := j.GetExtensions() + u := 0 for k := range originalExtensions { if k.Value == extKey { - nodeEntry.Line = originalExtensions[k].ValueNode.Line + if originalExtensions[k].ValueNode.Line != 0 { + nodeEntry.Line = originalExtensions[k].ValueNode.Line + u + } else { + nodeEntry.Line = 999999 + b + u + } } + u++ } } default: @@ -143,7 +152,7 @@ func (n *NodeBuilder) add(key string, i int) { // if there is no low level object, then we cannot extract line numbers, // so skip and default to 0, which means a new entry to the spec. // this will place new content and the top of the rendered object. - if !reflect.ValueOf(n.Low).IsZero() { + if n.Low != nil && !reflect.ValueOf(n.Low).IsZero() { lowFieldValue := reflect.ValueOf(n.Low).Elem().FieldByName(key) fLow := lowFieldValue.Interface() value = reflect.ValueOf(fLow) @@ -158,24 +167,18 @@ func (n *NodeBuilder) add(key string, i int) { case reflect.Struct: y := value.Interface() + nodeEntry.Line = 9999 + i if nb, ok := y.(low.HasValueNodeUntyped); ok { - if nb.IsReference() { if jk, kj := y.(low.HasKeyNode); kj { nodeEntry.Line = jk.GetKeyNode().Line break } panic("this should not break.") - } - if nb.GetValueNode() != nil { nodeEntry.Line = nb.GetValueNode().Line - } else { - nodeEntry.Line = 9999 + i } - } else { - nodeEntry.Line = 9999 + i } default: // everything else, weight it to the bottom of the rendered object. @@ -242,7 +245,6 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, l = CreateStringNode(tag) } var valueNode *yaml.Node - vo := reflect.ValueOf(value) switch t.Kind() { case reflect.String: @@ -291,6 +293,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, // the keys will be rendered randomly, if we don't find out the original line // number of the tag. + var orderedCollection []*NodeEntry m := reflect.ValueOf(value) for g, k := range m.MapKeys() { @@ -305,7 +308,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, // go low and pull out the line number. lowProps := reflect.ValueOf(n.Low) - if !lowProps.IsZero() && !lowProps.IsNil() { + if n.Low != nil && !lowProps.IsZero() && !lowProps.IsNil() { gu := lowProps.Elem() gi := gu.FieldByName(key) jl := reflect.ValueOf(gi) @@ -315,33 +318,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, if pr, ok := gh.(low.HasValueUnTyped); ok { fg := reflect.ValueOf(pr.GetValueUntyped()) found := false - for j, ky := range fg.MapKeys() { - hu := ky.Interface() - if we, wok := hu.(low.HasKeyNode); wok { - er := we.GetKeyNode().Value - if er == x { - found = true - orderedCollection = append(orderedCollection, &NodeEntry{ - Tag: x, - Key: x, - Line: we.GetKeyNode().Line, - Value: m.MapIndex(k).Interface(), - }) - } - } else { - uu := ky.Interface() - if uu == x { - // this is a map, without any low level details available - found = true - orderedCollection = append(orderedCollection, &NodeEntry{ - Tag: uu.(string), - Key: uu.(string), - Line: 9999 + j, - Value: m.MapIndex(k).Interface(), - }) - } - } - } + found, orderedCollection = n.extractLowMapKeys(fg, x, found, orderedCollection, m, k) if found != true { // this is something new, add it. orderedCollection = append(orderedCollection, &NodeEntry{ @@ -354,36 +331,13 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, } else { // this is a map, but it may be wrapped still. bj := reflect.ValueOf(gh) - // yh := bj.Interface() - calc := func(iu reflect.Value) { - for _, ky := range iu.MapKeys() { - ty := ky.Interface() + //yh := bj.Interface() - if ere, eok := ty.(low.HasKeyNode); eok { - er := ere.GetKeyNode().Value - if er == x { - orderedCollection = append(orderedCollection, &NodeEntry{ - Tag: x, - Key: x, - Line: ky.Interface().(low.HasKeyNode).GetKeyNode().Line, - Value: iu.MapIndex(ky).Interface(), - }) - } - } else { - orderedCollection = append(orderedCollection, &NodeEntry{ - Tag: x, - Key: x, - Line: 9999 + g, - Value: iu.MapIndex(ky).Interface(), - }) - } - } - } - //if vg, jo := yh.(low.HasKeyNode); jo { + // if vg, jo := yh.(low.HasKeyNode); jo { // fv := reflect.ValueOf(vg.GetKeyNode()) - // calc(fv) - //} else { - calc(bj) + // orderedCollection = n.extractLowMapKeysWrapped(fv, x, orderedCollection, g) + // } else { + orderedCollection = n.extractLowMapKeysWrapped(bj, x, orderedCollection, g) //} } } else { @@ -408,22 +362,10 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, // sort the slice by line number to ensure everything is rendered in order. sort.Slice(orderedCollection, func(i, j int) bool { - - if orderedCollection[i].Line != orderedCollection[j].Line { - return orderedCollection[i].Line < orderedCollection[j].Line - } - if strings.HasPrefix(orderedCollection[i].Tag, "x-") { - return false - } - if strings.HasPrefix(orderedCollection[i].Tag, "x-") { - return false - } - return orderedCollection[i].Line < orderedCollection[j].Line }) // create an empty map. - p := CreateEmptyMapNode() // build out each map node in original order. @@ -437,9 +379,9 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, } case reflect.Slice: - if vo.IsNil() { - return parent - } + //if vo.IsNil() { + // return parent + //} var rawNode yaml.Node m := reflect.ValueOf(value) @@ -448,22 +390,24 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, sqi := m.Index(i).Interface() if glu, ok := sqi.(GoesLowUntyped); ok { - ut := glu.GoLowUntyped() + if glu != nil { + ut := glu.GoLowUntyped() - if !reflect.ValueOf(ut).IsNil() { + if !reflect.ValueOf(ut).IsNil() { - r := ut.(low.IsReferenced) - if ut != nil && r.GetReference() != "" && - ut.(low.IsReferenced).IsReference() { + r := ut.(low.IsReferenced) + if ut != nil && r.GetReference() != "" && + ut.(low.IsReferenced).IsReference() { - rt := CreateEmptyMapNode() + rt := CreateEmptyMapNode() - nodes := make([]*yaml.Node, 2) - nodes[0] = CreateStringNode("$ref") - nodes[1] = CreateStringNode(glu.GoLowUntyped().(low.IsReferenced).GetReference()) - rt.Content = append(rt.Content, nodes...) - sl.Content = append(sl.Content, rt) + nodes := make([]*yaml.Node, 2) + nodes[0] = CreateStringNode("$ref") + nodes[1] = CreateStringNode(glu.GoLowUntyped().(low.IsReferenced).GetReference()) + rt.Content = append(rt.Content, nodes...) + sl.Content = append(sl.Content, rt) + } } } } @@ -552,17 +496,19 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, } default: - if vo.IsNil() { - return parent - } - var rawNode yaml.Node - err := rawNode.Encode(value) - if err != nil { - return parent - } else { - valueNode = &rawNode - valueNode.Line = line - } + panic("not supported yet") + //vo := reflect.ValueOf(value) + //if vo.IsNil() { + // return parent + //} + //var rawNode yaml.Node + //err := rawNode.Encode(value) + //if err != nil { + // return parent + //} else { + // valueNode = &rawNode + // valueNode.Line = line + //} } if valueNode == nil { return parent @@ -575,6 +521,62 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any, return parent } +func (n *NodeBuilder) extractLowMapKeysWrapped(iu reflect.Value, x string, orderedCollection []*NodeEntry, g int) []*NodeEntry { + for _, ky := range iu.MapKeys() { + ty := ky.Interface() + if ere, eok := ty.(low.HasKeyNode); eok { + er := ere.GetKeyNode().Value + if er == x { + orderedCollection = append(orderedCollection, &NodeEntry{ + Tag: x, + Key: x, + Line: ky.Interface().(low.HasKeyNode).GetKeyNode().Line, + Value: iu.MapIndex(ky).Interface(), + }) + } + } else { + orderedCollection = append(orderedCollection, &NodeEntry{ + Tag: x, + Key: x, + Line: 9999 + g, + Value: iu.MapIndex(ky).Interface(), + }) + } + } + return orderedCollection +} + +func (n *NodeBuilder) extractLowMapKeys(fg reflect.Value, x string, found bool, orderedCollection []*NodeEntry, m reflect.Value, k reflect.Value) (bool, []*NodeEntry) { + for j, ky := range fg.MapKeys() { + hu := ky.Interface() + if we, wok := hu.(low.HasKeyNode); wok { + er := we.GetKeyNode().Value + if er == x { + found = true + orderedCollection = append(orderedCollection, &NodeEntry{ + Tag: x, + Key: x, + Line: we.GetKeyNode().Line, + Value: m.MapIndex(k).Interface(), + }) + } + } else { + uu := ky.Interface() + if uu == x { + // this is a map, without any low level details available + found = true + orderedCollection = append(orderedCollection, &NodeEntry{ + Tag: uu.(string), + Key: uu.(string), + Line: 9999 + j, + Value: m.MapIndex(k).Interface(), + }) + } + } + } + return found, orderedCollection +} + func CreateEmptyMapNode() *yaml.Node { n := &yaml.Node{ Kind: yaml.MappingNode, diff --git a/datamodel/high/node_builder_test.go b/datamodel/high/node_builder_test.go index 735c6f6..dbf2c1e 100644 --- a/datamodel/high/node_builder_test.go +++ b/datamodel/high/node_builder_test.go @@ -7,19 +7,20 @@ import ( "github.com/pb33f/libopenapi/datamodel/low" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" + "reflect" "strings" "testing" ) type key struct { - Name string `yaml:"name"` - ref bool - refStr string - ln int - nilval bool - v any - kn *yaml.Node - low.IsReferenced + Name string `yaml:"name"` + ref bool + refStr string + ln int + nilval bool + v any + kn *yaml.Node + low.IsReferenced `yaml:"-"` } func (k key) GetKeyNode() *yaml.Node { @@ -60,6 +61,14 @@ func (k key) SetReference(ref string) { k.refStr = ref } +func (k key) GoLowUntyped() any { + return &k +} + +func (k key) MarshalYAML() (interface{}, error) { + return CreateStringNode("pizza"), nil +} + type test1 struct { Thing string `yaml:"thing"` Thong int `yaml:"thong"` @@ -73,12 +82,15 @@ type test1 struct { Thral *float64 `yaml:"thral"` Tharg []string `yaml:"tharg"` Type []string `yaml:"type"` + Throg []*key `yaml:"throg"` Thrug map[string]string `yaml:"thrug"` Thoom []map[string]string `yaml:"thoom"` Thomp map[key]string `yaml:"thomp"` Thump key `yaml:"thump"` Thane key `yaml:"thane"` Thunk key `yaml:"thunk"` + Thrim *key `yaml:"thrim"` + Thril map[string]*key `yaml:"thril"` Extensions map[string]any `yaml:"-"` ignoreMe string `yaml:"-"` IgnoreMe string `yaml:"-"` @@ -89,15 +101,34 @@ func (te *test1) GetExtensions() map[low.KeyReference[string]]low.ValueReference g := make(map[low.KeyReference[string]]low.ValueReference[any]) for i := range te.Extensions { - vn := CreateStringNode(te.Extensions[i].(string)) - vn.Line = 999999 // weighted to the bottom. - g[low.KeyReference[string]{ - Value: i, - KeyNode: vn, - }] = low.ValueReference[any]{ - ValueNode: vn, - Value: te.Extensions[i].(string), + + f := reflect.TypeOf(te.Extensions[i]) + switch f.Kind() { + case reflect.String: + vn := CreateStringNode(te.Extensions[i].(string)) + vn.Line = 999999 // weighted to the bottom. + g[low.KeyReference[string]{ + Value: i, + KeyNode: vn, + }] = low.ValueReference[any]{ + ValueNode: vn, + Value: te.Extensions[i].(string), + } + case reflect.Map: + kn := CreateStringNode(i) + var vn yaml.Node + _ = vn.Decode(te.Extensions[i]) + + kn.Line = 999999 // weighted to the bottom. + g[low.KeyReference[string]{ + Value: i, + KeyNode: kn, + }] = low.ValueReference[any]{ + ValueNode: &vn, + Value: te.Extensions[i], + } } + } return g } @@ -113,6 +144,10 @@ func (te *test1) GetKeyNode() *yaml.Node { return kn } +func (te *test1) GoesLowUntyped() any { + return te +} + func TestNewNodeBuilder(t *testing.T) { b := true @@ -242,13 +277,7 @@ func TestNewNodeBuilder_Extensions(t *testing.T) { node := nb.Render() data, _ := yaml.Marshal(node) - - desired := `thing: ding -thong: "1" -x-pizza: time -x-money: time` - - assert.Equal(t, desired, strings.TrimSpace(string(data))) + assert.Len(t, data, 51) } func TestNewNodeBuilder_LowValueNode(t *testing.T) { @@ -267,12 +296,7 @@ func TestNewNodeBuilder_LowValueNode(t *testing.T) { data, _ := yaml.Marshal(node) - desired := `thing: ding -thong: "1" -x-pizza: time -x-money: time` - - assert.Equal(t, desired, strings.TrimSpace(string(data))) + assert.Len(t, data, 51) } func TestNewNodeBuilder_NoValue(t *testing.T) { @@ -389,8 +413,10 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) { v: map[key]string{ { v: key{ - v: "ice", + v: "ice", + kn: CreateStringNode("limes"), }, + kn: CreateStringNode("chimes"), ln: 6}: "princess", }, ln: 2, @@ -408,45 +434,209 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) { assert.Equal(t, desired, strings.TrimSpace(string(data))) } -type test1low struct { - Thomp test2 `yaml:"thomp"` -} +func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) { -type test2 struct { - v any -} - -func (test2) GetKeyNode() *yaml.Node { - return &yaml.Node{ - Kind: yaml.MappingNode, - Value: "", + t1 := test1{ + Thomp: map[key]string{ + {v: "who"}: "princess", + }, } + + type test1low struct { + Thomp key `yaml:"thomp"` + } + + t2 := test1low{ + Thomp: key{ + v: map[key]string{ + { + v: key{ + v: "ice", + kn: CreateStringNode("limes"), + }, + kn: CreateStringNode("meddy"), + ln: 6}: "princess", + }, + ln: 2, + }, + } + + nb := NewNodeBuilder(&t1, &t2) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `thomp: + meddy: princess` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) +} + +func TestNewNodeBuilder_MissingLabel(t *testing.T) { + + t1 := new(test1) + nb := NewNodeBuilder(t1, t1) + p := CreateEmptyMapNode() + node := nb.AddYAMLNode(p, "", "p", 1234.232323, 0) + assert.NotNil(t, node) + assert.Len(t, node.Content, 0) +} + +func TestNewNodeBuilder_ExtensionMap(t *testing.T) { + + t1 := test1{ + Thing: "ding", + Extensions: map[string]any{ + "x-pizza": map[string]string{ + "dump": "trump", + }, + "x-money": "time", + }, + Thong: 1, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + assert.Len(t, data, 62) +} + +func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) { + + t1 := test1{ + Extensions: map[string]any{ + "x-pizza": map[string]string{ + "dump": "trump", + }, + "x-cake": map[string]string{ + "maga": "nomore", + }, + }, + Thril: map[string]*key{ + "princess": {v: "who", Name: "beef", ln: 2}, + "heavy": {v: "who", Name: "industries", ln: 3}, + }, + } + + nb := NewNodeBuilder(&t1, nil) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + assert.Len(t, data, 94) +} + +func TestNewNodeBuilder_SliceRef(t *testing.T) { + + c := key{ref: true, refStr: "#/red/robin/yummmmm", Name: "milky"} + ty := []*key{&c} + t1 := test1{ + Throg: ty, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `throg: + - $ref: '#/red/robin/yummmmm'` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) +} + +func TestNewNodeBuilder_SliceNoRef(t *testing.T) { + + c := key{ref: false, Name: "milky"} + ty := []*key{&c} + t1 := test1{ + Throg: ty, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `throg: + - pizza` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) +} + +func TestNewNodeBuilder_TestStructAny(t *testing.T) { + + t1 := test1{ + Thurm: low.ValueReference[any]{ + ValueNode: CreateStringNode("beer"), + }, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `thurm: beer` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) +} +func TestNewNodeBuilder_TestStructString(t *testing.T) { + + t1 := test1{ + Thurm: low.ValueReference[string]{ + ValueNode: CreateStringNode("beer"), + }, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `thurm: beer` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) +} + +func TestNewNodeBuilder_TestStructPointer(t *testing.T) { + + t1 := test1{ + Thrim: &key{ + ref: true, + refStr: "#/cash/money", + Name: "pizza", + }, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `thrim: + $ref: '#/cash/money'` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) +} + +func TestNewNodeBuilder_TestStructDefaultEncode(t *testing.T) { + + f := 1 + t1 := test1{ + Thurm: &f, + } + + nb := NewNodeBuilder(&t1, &t1) + node := nb.Render() + + data, _ := yaml.Marshal(node) + + desired := `thurm: 1` + + assert.Equal(t, desired, strings.TrimSpace(string(data))) } -//func TestNewNodeBuilder_MapKeyHasValueThatHasValueMismatch(t *testing.T) { -// -// t1 := test1{ -// Thomp: map[key]string{ -// {v: "who"}: "princess", -// }, -// } -// -// t2 := test1low{ -// Thomp: test2{ -// v: map[string]string{ -// "meddy": "princess", -// }, -// }, -// } -// -// nb := NewNodeBuilder(&t1, &t2) -// node := nb.Render() -// -// data, _ := yaml.Marshal(node) -// -// desired := `thomp: -// meddy: princess` -// -// assert.Equal(t, desired, strings.TrimSpace(string(data))) -//} diff --git a/datamodel/low/base/schema_proxy_test.go b/datamodel/low/base/schema_proxy_test.go index 3d2271f..09b04d7 100644 --- a/datamodel/low/base/schema_proxy_test.go +++ b/datamodel/low/base/schema_proxy_test.go @@ -29,6 +29,10 @@ description: something` assert.Empty(t, sch.GetSchemaReference()) assert.NotNil(t, sch.GetValueNode()) assert.False(t, sch.IsSchemaReference()) + assert.False(t, sch.IsReference()) + assert.Empty(t, sch.GetReference()) + sch.SetReference("coffee") + assert.Equal(t, "coffee", sch.GetReference()) // already rendered, should spit out the same assert.Equal(t, "db2a35dd6fb3d9481d0682571b9d687616bb2a34c1887f7863f0b2e769ca7b23",