9c1ea1f161
A few linty things crept in; this addresses them.
383 lines
10 KiB
Go
383 lines
10 KiB
Go
// Copyright 2017 Pulumi, Inc. All rights reserved.
|
|
|
|
package mapper
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type bag struct {
|
|
Bool bool
|
|
BoolP *bool
|
|
String string
|
|
StringP *string
|
|
Float64 float64
|
|
Float64P *float64
|
|
Strings []string
|
|
StringsP *[]string
|
|
}
|
|
|
|
func TestFieldMapper(t *testing.T) {
|
|
md := New(nil)
|
|
tree := Object{
|
|
"b": true,
|
|
"s": "hello",
|
|
"f": float64(3.14159265359),
|
|
"ss": []string{"a", "b", "c"},
|
|
}
|
|
|
|
// Try some simple primitive decodes.
|
|
var s bag
|
|
var err error
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "b", &s.Bool, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["b"], s.Bool)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "b", &s.BoolP, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["b"], *s.BoolP)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "s", &s.String, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["s"], s.String)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "s", &s.StringP, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["s"], *s.StringP)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "f", &s.Float64, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["f"], s.Float64)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "f", &s.Float64P, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["f"], *s.Float64P)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "ss", &s.Strings, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["ss"], s.Strings)
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "ss", &s.StringsP, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, tree["ss"], *s.StringsP)
|
|
|
|
// Ensure interface{} conversions work:
|
|
var sif string
|
|
err = md.DecodeField(Object{"x": interface{}("hello")}, reflect.TypeOf(bag{}), "x", &sif, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "hello", sif)
|
|
|
|
var sifs []string
|
|
err = md.DecodeField(Object{"arr": []interface{}{"a", "b", "c"}}, reflect.TypeOf(bag{}), "arr", &sifs, false)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, []string{"a", "b", "c"}, sifs)
|
|
|
|
// Ensure missing optional fields are ignored:
|
|
s.String = "x"
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "missing", &s.String, true)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "x", s.String)
|
|
|
|
// Try some error conditions; first, wrong type:
|
|
s.String = "x"
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "b", &s.String, false)
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, "Field 'b' on 'mapper.bag' must be a 'string'; got 'bool' instead", err.Error())
|
|
assert.Equal(t, "x", s.String)
|
|
|
|
// Next, missing required field:
|
|
s.String = "x"
|
|
err = md.DecodeField(tree, reflect.TypeOf(bag{}), "missing", &s.String, false)
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, "Missing required field 'missing' on 'mapper.bag'", err.Error())
|
|
assert.Equal(t, "x", s.String)
|
|
}
|
|
|
|
type bagtag struct {
|
|
String string `json:"s"`
|
|
StringSkip string `json:"sc,skip"`
|
|
StringOpt string `json:"so,omitempty"`
|
|
StringSkipOpt string `json:"sco,skip,omitempty"`
|
|
}
|
|
|
|
func TestMapper(t *testing.T) {
|
|
var err error
|
|
md := New(nil)
|
|
|
|
// First, test the fully populated case.
|
|
var b1 bagtag
|
|
err = md.Decode(Object{
|
|
"s": "something",
|
|
"sc": "nothing",
|
|
"so": "ohmy",
|
|
"sco": "ohmynada",
|
|
}, &b1)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "something", b1.String)
|
|
assert.Equal(t, "", b1.StringSkip)
|
|
assert.Equal(t, "ohmy", b1.StringOpt)
|
|
assert.Equal(t, "", b1.StringSkipOpt)
|
|
|
|
// Now let optional fields go missing.
|
|
var b2 bagtag
|
|
err = md.Decode(Object{
|
|
"s": "something",
|
|
"sc": "nothing",
|
|
}, &b2)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, "something", b2.String)
|
|
assert.Equal(t, "", b2.StringSkip)
|
|
assert.Equal(t, "", b2.StringOpt)
|
|
assert.Equal(t, "", b2.StringSkipOpt)
|
|
|
|
// Try some error conditions; first, wrong type:
|
|
var b3 bagtag
|
|
err = md.Decode(Object{
|
|
"s": true,
|
|
"sc": "",
|
|
}, &b3)
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, "1 fields failed to decode:\n"+
|
|
"\ts: Field 's' on 'mapper.bagtag' must be a 'string'; got 'bool' instead", err.Error())
|
|
assert.Equal(t, "", b3.String)
|
|
|
|
// Next, missing required field:
|
|
var b4 bagtag
|
|
err = md.Decode(Object{}, &b4)
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, "1 fields failed to decode:\n"+
|
|
"\ts: Missing required field 's' on 'mapper.bagtag'", err.Error())
|
|
assert.Equal(t, "", b4.String)
|
|
}
|
|
|
|
type bog struct {
|
|
Boggy bogger `json:"boggy"`
|
|
BoggyP *bogger `json:"boggyp"`
|
|
Boggers []bogger `json:"boggers"`
|
|
BoggersP *[]*bogger `json:"boggersp"`
|
|
}
|
|
|
|
type bogger struct {
|
|
Num float64 `json:"num"`
|
|
}
|
|
|
|
func TestNestedMapper(t *testing.T) {
|
|
md := New(nil)
|
|
|
|
// Test one level deep nesting (fields, arrays, pointers).
|
|
var b bog
|
|
err := md.Decode(Object{
|
|
"boggy": Object{"num": float64(99)},
|
|
"boggyp": Object{"num": float64(180)},
|
|
"boggers": []Object{
|
|
{"num": float64(1)},
|
|
{"num": float64(2)},
|
|
{"num": float64(42)},
|
|
},
|
|
"boggersp": []Object{
|
|
{"num": float64(4)},
|
|
{"num": float64(8)},
|
|
{"num": float64(84)},
|
|
},
|
|
}, &b)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, float64(99), b.Boggy.Num)
|
|
assert.NotNil(t, b.BoggyP)
|
|
assert.Equal(t, float64(180), b.BoggyP.Num)
|
|
assert.Equal(t, 3, len(b.Boggers))
|
|
assert.Equal(t, float64(1), b.Boggers[0].Num)
|
|
assert.Equal(t, float64(2), b.Boggers[1].Num)
|
|
assert.Equal(t, float64(42), b.Boggers[2].Num)
|
|
assert.NotNil(t, b.BoggersP)
|
|
assert.Equal(t, 3, len(*b.BoggersP))
|
|
assert.NotNil(t, (*b.BoggersP)[0])
|
|
assert.Equal(t, float64(4), (*b.BoggersP)[0].Num)
|
|
assert.NotNil(t, (*b.BoggersP)[1])
|
|
assert.Equal(t, float64(8), (*b.BoggersP)[1].Num)
|
|
assert.NotNil(t, (*b.BoggersP)[2])
|
|
assert.Equal(t, float64(84), (*b.BoggersP)[2].Num)
|
|
}
|
|
|
|
type boggerdybogger struct {
|
|
Bogs map[string]bog `json:"bogs"`
|
|
BogsP *map[string]*bog `json:"bogsp"`
|
|
}
|
|
|
|
func TestMultiplyNestedMapper(t *testing.T) {
|
|
md := New(nil)
|
|
|
|
// Test multilevel nesting (maps, fields, arrays, pointers).
|
|
var ber boggerdybogger
|
|
err := md.Decode(Object{
|
|
"bogs": Object{
|
|
"a": Object{
|
|
"boggy": Object{"num": float64(99)},
|
|
"boggyp": Object{"num": float64(180)},
|
|
"boggers": []Object{
|
|
{"num": float64(1)},
|
|
{"num": float64(2)},
|
|
{"num": float64(42)},
|
|
},
|
|
"boggersp": []Object{
|
|
{"num": float64(4)},
|
|
{"num": float64(8)},
|
|
{"num": float64(84)},
|
|
},
|
|
},
|
|
},
|
|
"bogsp": Object{
|
|
"z": Object{
|
|
"boggy": Object{"num": float64(188)},
|
|
"boggyp": Object{"num": float64(360)},
|
|
"boggers": []Object{
|
|
{"num": float64(2)},
|
|
{"num": float64(4)},
|
|
{"num": float64(84)},
|
|
},
|
|
"boggersp": []Object{
|
|
{"num": float64(8)},
|
|
{"num": float64(16)},
|
|
{"num": float64(168)},
|
|
},
|
|
},
|
|
},
|
|
}, &ber)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, 1, len(ber.Bogs))
|
|
b := ber.Bogs["a"]
|
|
assert.Equal(t, float64(99), b.Boggy.Num)
|
|
assert.NotNil(t, b.BoggyP)
|
|
assert.Equal(t, float64(180), b.BoggyP.Num)
|
|
assert.Equal(t, 3, len(b.Boggers))
|
|
assert.Equal(t, float64(1), b.Boggers[0].Num)
|
|
assert.Equal(t, float64(2), b.Boggers[1].Num)
|
|
assert.Equal(t, float64(42), b.Boggers[2].Num)
|
|
assert.NotNil(t, b.BoggersP)
|
|
assert.Equal(t, 3, len(*b.BoggersP))
|
|
assert.NotNil(t, (*b.BoggersP)[0])
|
|
assert.Equal(t, float64(4), (*b.BoggersP)[0].Num)
|
|
assert.NotNil(t, (*b.BoggersP)[1])
|
|
assert.Equal(t, float64(8), (*b.BoggersP)[1].Num)
|
|
assert.NotNil(t, (*b.BoggersP)[2])
|
|
assert.Equal(t, float64(84), (*b.BoggersP)[2].Num)
|
|
|
|
assert.NotNil(t, ber.BogsP)
|
|
assert.Equal(t, 1, len(*ber.BogsP))
|
|
p := (*ber.BogsP)["z"]
|
|
assert.NotNil(t, p)
|
|
assert.Equal(t, float64(188), p.Boggy.Num)
|
|
assert.NotNil(t, p.BoggyP)
|
|
assert.Equal(t, float64(360), p.BoggyP.Num)
|
|
assert.Equal(t, 3, len(p.Boggers))
|
|
assert.Equal(t, float64(2), p.Boggers[0].Num)
|
|
assert.Equal(t, float64(4), p.Boggers[1].Num)
|
|
assert.Equal(t, float64(84), p.Boggers[2].Num)
|
|
assert.NotNil(t, p.BoggersP)
|
|
assert.Equal(t, 3, len(*p.BoggersP))
|
|
assert.NotNil(t, (*p.BoggersP)[0])
|
|
assert.Equal(t, float64(8), (*p.BoggersP)[0].Num)
|
|
assert.NotNil(t, (*p.BoggersP)[1])
|
|
assert.Equal(t, float64(16), (*p.BoggersP)[1].Num)
|
|
assert.NotNil(t, (*p.BoggersP)[2])
|
|
assert.Equal(t, float64(168), (*p.BoggersP)[2].Num)
|
|
}
|
|
|
|
type hasmap struct {
|
|
Entries map[string]mapentry `json:"entries"`
|
|
EntriesP map[string]*mapentry `json:"entriesp"`
|
|
}
|
|
|
|
type mapentry struct {
|
|
Title string `json:"title"`
|
|
}
|
|
|
|
func TestMapMapper(t *testing.T) {
|
|
md := New(nil)
|
|
|
|
// Ensure we can decode both maps of structs and maps of pointers to structs.
|
|
var hm hasmap
|
|
err := md.Decode(Object{
|
|
"entries": Object{
|
|
"a": Object{"title": "first"},
|
|
"b": Object{"title": "second"},
|
|
},
|
|
"entriesp": Object{
|
|
"x": Object{"title": "firstp"},
|
|
"y": Object{"title": "secondp"},
|
|
},
|
|
}, &hm)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, 2, len(hm.Entries))
|
|
assert.Equal(t, "first", hm.Entries["a"].Title)
|
|
assert.Equal(t, "second", hm.Entries["b"].Title)
|
|
assert.Equal(t, 2, len(hm.EntriesP))
|
|
assert.NotNil(t, hm.EntriesP["x"])
|
|
assert.NotNil(t, hm.EntriesP["y"])
|
|
assert.Equal(t, "firstp", hm.EntriesP["x"].Title)
|
|
assert.Equal(t, "secondp", hm.EntriesP["y"].Title)
|
|
}
|
|
|
|
type wrap struct {
|
|
C customStruct `json:"c"`
|
|
CI customInterface `json:"ci"`
|
|
}
|
|
|
|
type customInterface interface {
|
|
GetX() float64
|
|
GetY() float64
|
|
}
|
|
|
|
type customStruct struct {
|
|
X float64 `json:"x"`
|
|
Y float64 `json:"y"`
|
|
}
|
|
|
|
func (s *customStruct) GetX() float64 { return s.X }
|
|
func (s *customStruct) GetY() float64 { return s.Y }
|
|
|
|
func TestCustomMapper(t *testing.T) {
|
|
var md Mapper
|
|
md = New(&Opts{
|
|
CustomDecoders: Decoders{
|
|
reflect.TypeOf((*customInterface)(nil)).Elem(): decodeCustomInterface,
|
|
reflect.TypeOf(customStruct{}): decodeCustomStruct,
|
|
},
|
|
})
|
|
|
|
var w wrap
|
|
err := md.Decode(Object{
|
|
"c": Object{
|
|
"x": float64(-99.2),
|
|
"y": float64(127.127),
|
|
},
|
|
"ci": Object{
|
|
"x": float64(42.6),
|
|
"y": float64(247.9),
|
|
},
|
|
}, &w)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, float64(-99.2), w.C.X)
|
|
assert.Equal(t, float64(127.127), w.C.Y)
|
|
assert.NotNil(t, w.CI)
|
|
assert.Equal(t, float64(42.6), w.CI.GetX())
|
|
assert.Equal(t, float64(247.9), w.CI.GetY())
|
|
}
|
|
|
|
func decodeCustomInterface(m Mapper, tree Object) (interface{}, error) {
|
|
var s customStruct
|
|
if err := m.DecodeField(tree, reflect.TypeOf(s), "x", &s.X, false); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := m.DecodeField(tree, reflect.TypeOf(s), "y", &s.Y, false); err != nil {
|
|
return nil, err
|
|
}
|
|
return customInterface(&s), nil
|
|
}
|
|
|
|
func decodeCustomStruct(m Mapper, tree Object) (interface{}, error) {
|
|
var s customStruct
|
|
if err := m.DecodeField(tree, reflect.TypeOf(s), "x", &s.X, false); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := m.DecodeField(tree, reflect.TypeOf(s), "y", &s.Y, false); err != nil {
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|