rendering complex additionalProperties in place.

slices of maps were not rendered properly. Now corrected, coverage continues to rise, refactoring node generation into utils package.
This commit is contained in:
Dave Shanley
2023-03-19 07:43:57 -04:00
parent 0b8fab869e
commit 0e8ae7c548
11 changed files with 176 additions and 112 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -131,10 +132,10 @@ func (sp *SchemaProxy) MarshalYAML() (interface{}, error) {
return nb.Render(), nil
} else {
// do not build out a reference, just marshal the reference.
mp := high.CreateEmptyMapNode()
mp := utils.CreateEmptyMapNode()
mp.Content = append(mp.Content,
high.CreateStringNode("$ref"),
high.CreateStringNode(sp.GetReference()))
utils.CreateStringNode("$ref"),
utils.CreateStringNode(sp.GetReference()))
return mp, nil
}
}

View File

@@ -317,7 +317,7 @@ minProperties: 1`
// now render it out!
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes)))
assert.Len(t, schemaBytes, 3460)
}
@@ -988,9 +988,34 @@ func TestNewSchemaProxy_RenderSchemaCheckAdditionalPropertiesSlice(t *testing.T)
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
compiled.low.Hash()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Len(t, schemaBytes, 91)
}
func TestNewSchemaProxy_RenderSchemaCheckAdditionalPropertiesSliceMap(t *testing.T) {
testSpec := `additionalProperties:
- nice: cake
- yummy: beer
- hot: coffee`
var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy)
err := sp.Build(compNode.Content[0], nil)
assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp,
ValueNode: compNode.Content[0],
}
schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema()
// now render it out, it should be identical.
schemaBytes, _ := compiled.Render()
assert.Equal(t, testSpec, strings.TrimSpace(string(schemaBytes)))
assert.Len(t, schemaBytes, 75)
}

View File

@@ -5,6 +5,7 @@ package high
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"reflect"
"sort"
@@ -194,15 +195,15 @@ func (n *NodeBuilder) add(key string, i int) {
func (n *NodeBuilder) renderReference() []*yaml.Node {
fg := n.Low.(low.IsReferenced)
nodes := make([]*yaml.Node, 2)
nodes[0] = CreateStringNode("$ref")
nodes[1] = CreateStringNode(fg.GetReference())
nodes[0] = utils.CreateStringNode("$ref")
nodes[1] = utils.CreateStringNode(fg.GetReference())
return nodes
}
// Render will render the NodeBuilder back to a YAML node, iterating over every NodeEntry defined
func (n *NodeBuilder) Render() *yaml.Node {
// order nodes by line number, retain original order
m := CreateEmptyMapNode()
m := utils.CreateEmptyMapNode()
if fg, ok := n.Low.(low.IsReferenced); ok {
g := reflect.ValueOf(fg)
if !g.IsNil() {
@@ -242,7 +243,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
t := reflect.TypeOf(value)
var l *yaml.Node
if tag != "" {
l = CreateStringNode(tag)
l = utils.CreateStringNode(tag)
}
var valueNode *yaml.Node
switch t.Kind() {
@@ -252,7 +253,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
if val == "" {
return parent
}
valueNode = CreateStringNode(val)
valueNode = utils.CreateStringNode(val)
valueNode.Line = line
break
@@ -261,31 +262,31 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
if !val {
return parent
}
valueNode = CreateBoolNode("true")
valueNode = utils.CreateBoolNode("true")
valueNode.Line = line
break
case reflect.Int:
val := strconv.Itoa(value.(int))
valueNode = CreateIntNode(val)
valueNode = utils.CreateIntNode(val)
valueNode.Line = line
break
case reflect.Int64:
val := strconv.FormatInt(value.(int64), 10)
valueNode = CreateIntNode(val)
valueNode = utils.CreateIntNode(val)
valueNode.Line = line
break
case reflect.Float32:
val := strconv.FormatFloat(float64(value.(float32)), 'f', 2, 64)
valueNode = CreateFloatNode(val)
valueNode = utils.CreateFloatNode(val)
valueNode.Line = line
break
case reflect.Float64:
val := strconv.FormatFloat(value.(float64), 'f', -1, 64)
valueNode = CreateFloatNode(val)
valueNode = utils.CreateFloatNode(val)
valueNode.Line = line
break
@@ -366,7 +367,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
})
// create an empty map.
p := CreateEmptyMapNode()
p := utils.CreateEmptyMapNode()
// build out each map node in original order.
for _, cv := range orderedCollection {
@@ -385,7 +386,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
var rawNode yaml.Node
m := reflect.ValueOf(value)
sl := CreateEmptySequenceNode()
sl := utils.CreateEmptySequenceNode()
for i := 0; i < m.Len(); i++ {
sqi := m.Index(i).Interface()
@@ -399,11 +400,11 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
if ut != nil && r.GetReference() != "" &&
ut.(low.IsReferenced).IsReference() {
rt := CreateEmptyMapNode()
rt := utils.CreateEmptyMapNode()
nodes := make([]*yaml.Node, 2)
nodes[0] = CreateStringNode("$ref")
nodes[1] = CreateStringNode(glu.GoLowUntyped().(low.IsReferenced).GetReference())
nodes[0] = utils.CreateStringNode("$ref")
nodes[1] = utils.CreateStringNode(glu.GoLowUntyped().(low.IsReferenced).GetReference())
rt.Content = append(rt.Content, nodes...)
sl.Content = append(sl.Content, rt)
@@ -444,9 +445,9 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
ut := reflect.ValueOf(gl.GoLowUntyped())
if !ut.IsNil() {
if gl.GoLowUntyped().(low.IsReferenced).IsReference() {
rvn := CreateEmptyMapNode()
rvn.Content = append(rvn.Content, CreateStringNode("$ref"))
rvn.Content = append(rvn.Content, CreateStringNode(gl.GoLowUntyped().(low.IsReferenced).GetReference()))
rvn := utils.CreateEmptyMapNode()
rvn.Content = append(rvn.Content, utils.CreateStringNode("$ref"))
rvn.Content = append(rvn.Content, utils.CreateStringNode(gl.GoLowUntyped().(low.IsReferenced).GetReference()))
valueNode = rvn
break
}
@@ -465,21 +466,21 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, tag, key string, value any,
if b, bok := value.(*bool); bok {
encodeSkip = true
if *b {
valueNode = CreateBoolNode("true")
valueNode = utils.CreateBoolNode("true")
valueNode.Line = line
}
}
if b, bok := value.(*int64); bok {
encodeSkip = true
if *b > 0 {
valueNode = CreateIntNode(strconv.Itoa(int(*b)))
valueNode = utils.CreateIntNode(strconv.Itoa(int(*b)))
valueNode.Line = line
}
}
if b, bok := value.(*float64); bok {
encodeSkip = true
if *b > 0 {
valueNode = CreateFloatNode(strconv.FormatFloat(*b, 'f', -1, 64))
valueNode = utils.CreateFloatNode(strconv.FormatFloat(*b, 'f', -1, 64))
valueNode.Line = line
}
}
@@ -577,58 +578,6 @@ func (n *NodeBuilder) extractLowMapKeys(fg reflect.Value, x string, found bool,
return found, orderedCollection
}
func CreateEmptyMapNode() *yaml.Node {
n := &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
}
return n
}
func CreateEmptySequenceNode() *yaml.Node {
n := &yaml.Node{
Kind: yaml.SequenceNode,
Tag: "!!seq",
}
return n
}
func CreateStringNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: str,
}
return n
}
func CreateBoolNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!bool",
Value: str,
}
return n
}
func CreateIntNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!int",
Value: str,
}
return n
}
func CreateFloatNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!float",
Value: str,
}
return n
}
type Renderable interface {
MarshalYAML() (interface{}, error)
}

View File

@@ -5,6 +5,7 @@ package high
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"reflect"
@@ -27,7 +28,7 @@ func (k key) GetKeyNode() *yaml.Node {
if k.kn != nil {
return k.kn
}
kn := CreateStringNode("meddy")
kn := utils.CreateStringNode("meddy")
kn.Line = k.ln
return kn
}
@@ -44,7 +45,7 @@ func (k key) GetValueNodeUntyped() *yaml.Node {
if k.nilval {
return nil
}
kn := CreateStringNode("maddy")
kn := utils.CreateStringNode("maddy")
kn.Line = k.ln
return kn
}
@@ -66,7 +67,7 @@ func (k key) GoLowUntyped() any {
}
func (k key) MarshalYAML() (interface{}, error) {
return CreateStringNode("pizza"), nil
return utils.CreateStringNode("pizza"), nil
}
type test1 struct {
@@ -105,7 +106,7 @@ func (te *test1) GetExtensions() map[low.KeyReference[string]]low.ValueReference
f := reflect.TypeOf(te.Extensions[i])
switch f.Kind() {
case reflect.String:
vn := CreateStringNode(te.Extensions[i].(string))
vn := utils.CreateStringNode(te.Extensions[i].(string))
vn.Line = 999999 // weighted to the bottom.
g[low.KeyReference[string]{
Value: i,
@@ -115,7 +116,7 @@ func (te *test1) GetExtensions() map[low.KeyReference[string]]low.ValueReference
Value: te.Extensions[i].(string),
}
case reflect.Map:
kn := CreateStringNode(i)
kn := utils.CreateStringNode(i)
var vn yaml.Node
_ = vn.Decode(te.Extensions[i])
@@ -139,7 +140,7 @@ func (te *test1) MarshalYAML() (interface{}, error) {
}
func (te *test1) GetKeyNode() *yaml.Node {
kn := CreateStringNode("meddy")
kn := utils.CreateStringNode("meddy")
kn.Line = 20
return kn
}
@@ -327,7 +328,7 @@ func TestNewNodeBuilder_Bool(t *testing.T) {
func TestNewNodeBuilder_Int(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := CreateEmptyMapNode()
p := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(p, "p", "p", 12, 0)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -337,7 +338,7 @@ func TestNewNodeBuilder_Int(t *testing.T) {
func TestNewNodeBuilder_Int64(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := CreateEmptyMapNode()
p := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(p, "p", "p", int64(234556), 0)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -347,7 +348,7 @@ func TestNewNodeBuilder_Int64(t *testing.T) {
func TestNewNodeBuilder_Float32(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := CreateEmptyMapNode()
p := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(p, "p", "p", float32(1234.23), 0)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -357,7 +358,7 @@ func TestNewNodeBuilder_Float32(t *testing.T) {
func TestNewNodeBuilder_Float64(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := CreateEmptyMapNode()
p := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(p, "p", "p", 1234.232323, 0)
assert.NotNil(t, node)
assert.Len(t, node.Content, 2)
@@ -414,9 +415,9 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValue(t *testing.T) {
{
v: key{
v: "ice",
kn: CreateStringNode("limes"),
kn: utils.CreateStringNode("limes"),
},
kn: CreateStringNode("chimes"),
kn: utils.CreateStringNode("chimes"),
ln: 6}: "princess",
},
ln: 2,
@@ -452,9 +453,9 @@ func TestNewNodeBuilder_MapKeyHasValueThatHasValueMatch(t *testing.T) {
{
v: key{
v: "ice",
kn: CreateStringNode("limes"),
kn: utils.CreateStringNode("limes"),
},
kn: CreateStringNode("meddy"),
kn: utils.CreateStringNode("meddy"),
ln: 6}: "princess",
},
ln: 2,
@@ -476,7 +477,7 @@ func TestNewNodeBuilder_MissingLabel(t *testing.T) {
t1 := new(test1)
nb := NewNodeBuilder(t1, t1)
p := CreateEmptyMapNode()
p := utils.CreateEmptyMapNode()
node := nb.AddYAMLNode(p, "", "p", 1234.232323, 0)
assert.NotNil(t, node)
assert.Len(t, node.Content, 0)
@@ -570,7 +571,7 @@ func TestNewNodeBuilder_TestStructAny(t *testing.T) {
t1 := test1{
Thurm: low.ValueReference[any]{
ValueNode: CreateStringNode("beer"),
ValueNode: utils.CreateStringNode("beer"),
},
}
@@ -587,7 +588,7 @@ func TestNewNodeBuilder_TestStructString(t *testing.T) {
t1 := test1{
Thurm: low.ValueReference[string]{
ValueNode: CreateStringNode("beer"),
ValueNode: utils.CreateStringNode("beer"),
},
}

View File

@@ -6,6 +6,7 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
)
@@ -56,7 +57,7 @@ func (c *Callback) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the Callback object.
func (c *Callback) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := high.CreateEmptyMapNode()
m := utils.CreateEmptyMapNode()
type cbItem struct {
cb *PathItem
exp string
@@ -98,11 +99,11 @@ func (c *Callback) MarshalYAML() (interface{}, error) {
for j := range mapped {
if mapped[j].cb != nil {
rendered, _ := mapped[j].cb.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].exp))
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].exp))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, high.CreateStringNode(mapped[j].exp))
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].exp))
m.Content = append(m.Content, mapped[j].ext)
}
}

View File

@@ -6,6 +6,7 @@ package v3
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
)
@@ -71,7 +72,7 @@ func (p *Paths) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the Paths object.
func (p *Paths) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := high.CreateEmptyMapNode()
m := utils.CreateEmptyMapNode()
type pathItem struct {
pi *PathItem
path string
@@ -111,11 +112,11 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
for j := range mapped {
if mapped[j].pi != nil {
rendered, _ := mapped[j].pi.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].rendered != nil {
m.Content = append(m.Content, high.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].path))
m.Content = append(m.Content, mapped[j].rendered)
}
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
"sort"
)
@@ -93,7 +94,7 @@ func (r *Responses) Render() ([]byte, error) {
// MarshalYAML will create a ready to render YAML representation of the Responses object.
func (r *Responses) MarshalYAML() (interface{}, error) {
// map keys correctly.
m := high.CreateEmptyMapNode()
m := utils.CreateEmptyMapNode()
type responseItem struct {
resp *Response
code string
@@ -135,11 +136,11 @@ func (r *Responses) MarshalYAML() (interface{}, error) {
for j := range mapped {
if mapped[j].resp != nil {
rendered, _ := mapped[j].resp.MarshalYAML()
m.Content = append(m.Content, high.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, rendered.(*yaml.Node))
}
if mapped[j].ext != nil {
m.Content = append(m.Content, high.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, utils.CreateStringNode(mapped[j].code))
m.Content = append(m.Content, mapped[j].ext)
}

View File

@@ -190,11 +190,17 @@ func (s *Schema) Hash() [32]byte {
if jh, ok := vn.(low.HasValueUnTyped); ok {
vn = jh.GetValueUntyped()
fg := reflect.TypeOf(vn)
gf := reflect.ValueOf(vn)
if fg.Kind() == reflect.Map {
panic("smack")
for _, ky := range gf.MapKeys() {
hu := ky.Interface()
values = append(values, fmt.Sprintf("%s:%s", hu, low.GenerateHashString(gf.MapIndex(ky).Interface())))
}
continue
}
values = append(values, fmt.Sprintf("%d:%s", i, low.GenerateHashString(vn)))
}
values = append(values, fmt.Sprintf("%d:%s", i, low.GenerateHashString(vn)))
}
sort.Strings(values)
d = append(d, strings.Join(values, "||"))
@@ -687,13 +693,19 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if utils.IsNodeArray(addPNode) {
var addProps []low.ValueReference[any]
// todo: check for a map, and encode before packing.
// todo: pick up here tomorrow.
// if this is an array or maps, encode the map items correctly.
for i := range addPNode.Content {
addProps = append(addProps,
low.ValueReference[any]{Value: addPNode.Content[i].Value, ValueNode: addPNode.Content[i]})
if utils.IsNodeMap(addPNode.Content[i]) {
var prop map[string]any
addPNode.Content[i].Decode(&prop)
addProps = append(addProps,
low.ValueReference[any]{Value: prop, ValueNode: addPNode.Content[i]})
} else {
addProps = append(addProps,
low.ValueReference[any]{Value: addPNode.Content[i].Value, ValueNode: addPNode.Content[i]})
}
}
s.AdditionalProperties =
low.NodeReference[any]{Value: addProps, KeyNode: addPLabel, ValueNode: addPNode}
}

View File

@@ -264,6 +264,18 @@ func (n ValueReference[T]) IsReference() bool {
return false
}
func (n ValueReference[T]) MarshalYAML() (interface{}, error) {
if n.IsReference() {
nodes := make([]*yaml.Node, 2)
nodes[0] = utils.CreateStringNode("$ref")
nodes[1] = utils.CreateStringNode(n.Reference)
return nodes, nil
}
var h yaml.Node
e := n.ValueNode.Decode(&h)
return h, e
}
// IsEmpty will return true if this reference has no key or value nodes assigned (it's been ignored)
func (n KeyReference[T]) IsEmpty() bool {
return n.KeyNode == nil

58
utils/nodes.go Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package utils
import "gopkg.in/yaml.v3"
func CreateEmptyMapNode() *yaml.Node {
n := &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
}
return n
}
func CreateEmptySequenceNode() *yaml.Node {
n := &yaml.Node{
Kind: yaml.SequenceNode,
Tag: "!!seq",
}
return n
}
func CreateStringNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: str,
}
return n
}
func CreateBoolNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!bool",
Value: str,
}
return n
}
func CreateIntNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!int",
Value: str,
}
return n
}
func CreateFloatNode(str string) *yaml.Node {
n := &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!float",
Value: str,
}
return n
}

View File

@@ -608,3 +608,6 @@ func CheckEnumForDuplicates(seq []*yaml.Node) []*yaml.Node {
}
return res
}