4389da48e4
--- type: change description: |- Fix a bug in typeobj when a type field's name is the same as one of its inner fields This specifically came up with Comment (though it wasn't caught because an error wasn't being caught, that's fixed here as well). Prior to unmarshaling into the selected inner struct field, typeobj.UnmarshalYAML unmarshals into the outer struct in order to unmarshal all non-type fields. However, if one of the fields intended for the inner struct field has the same name as one of the type fields in the outer struct there would be a conflict at this point. The solution is to modify the type of the outer struct being unmarshaled into at this stage, so that all fields with type tags automatically have a `yaml:"-"` tag, and so are ignored by the yaml unmarshaler at this stage. fingerprint: AL32FBVJ7Bu2dz1ysrCiRFz2/y+QuaEyhKygvWP/fihw credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6rQYoACgkQlcRvpqQRSKzs0RAAkdU5Ty2uigHZSXqSgU4JiDLMmzlr4B4ODautUuLBmdskVaAAuOUJuS+egUU6Xz6lmL4+zQRBNGCvaZTxu0OT4H4wFWNQ9RdurLbuSJDeQY4htn5bP6BqcOy5aiTiYpnrZu6yuzMTco4jVSZ961o6t829gDBu1jAk32i/l3ivQpMSijEwjK9m74jKxF+fIVqT3+isgs0qzaDkskpdlDEgd/cf4Ibeb1+BAEZRShMXHBhF415rldjYs9H1Q2TSVwAaP7Zqn9gIV04yB/C8Waysh/NCMsIvQcACbVoO9vSBQ/1d+jttI+KTqOTA8lQ/ygWrFdYtPBjXRO7CVrah7PPE+EbFbPBbjH6ddP20uVeoTPTcjUwaWpdg5e4vZfuqXEe0IWW8NyMh8UL1tJ1LpLlWZKx6tz7gcUgoq+jOLUmUG5EB8HjQfqZx6WDHuyPTpy3c646SaIjg8B8tKkwUR+w9zntId7N4mWB+c+qMDH72mU54sXJ/i+XexqZaQgQiz2jRcltNc4S+/ohT5UDAYuivJsCDBcZdOYiMIB2cnPsm2DbCdbQPAq4oK1Ni+2wo7Pj9nVENamc+g6evqCnBZsWQUt5bDUwneIFwYcqdIPulX0NV9rtZQxexIkCmsO1vSrzdkeNyfWizFlRLavW5OBmxuLtoFoZUv5Oijwu8QT0eXMU= account: mediocregopher
170 lines
4.0 KiB
Go
170 lines
4.0 KiB
Go
package typeobj
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type foo struct {
|
|
A int `yaml:"a"`
|
|
}
|
|
|
|
type bar struct {
|
|
B int `yaml:"b"`
|
|
}
|
|
|
|
// baz has a field of the same name as the type, which is tricky
|
|
type baz struct {
|
|
Baz int `yaml:"baz"`
|
|
}
|
|
|
|
type outer struct {
|
|
Foo foo `type:"foo"`
|
|
Bar *bar `type:"bar"`
|
|
Baz baz `type:"baz"`
|
|
|
|
Other string `yaml:"other_field,omitempty"`
|
|
}
|
|
|
|
func (o outer) MarshalYAML() (interface{}, error) {
|
|
return MarshalYAML(o)
|
|
}
|
|
|
|
func (o *outer) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
return UnmarshalYAML(o, unmarshal)
|
|
}
|
|
|
|
type outerWDefault struct {
|
|
Foo foo `type:"foo,default"`
|
|
Bar *bar `type:"bar"`
|
|
}
|
|
|
|
func (o outerWDefault) MarshalYAML() (interface{}, error) {
|
|
return MarshalYAML(o)
|
|
}
|
|
|
|
func (o *outerWDefault) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
return UnmarshalYAML(o, unmarshal)
|
|
}
|
|
|
|
func TestTypeObj(t *testing.T) {
|
|
|
|
type test struct {
|
|
descr string
|
|
str string
|
|
|
|
expErr string
|
|
expObj interface{}
|
|
expTypeTag string
|
|
expElem interface{}
|
|
expMarshalOut string // defaults to str
|
|
}
|
|
|
|
tests := []test{
|
|
{
|
|
descr: "no type set",
|
|
str: `{}`,
|
|
expErr: "type field not set",
|
|
expObj: outer{},
|
|
},
|
|
{
|
|
descr: "no type set with nontype field",
|
|
str: `other_field: aaa`,
|
|
expErr: "type field not set",
|
|
expObj: outer{},
|
|
},
|
|
{
|
|
descr: "no type set with default",
|
|
str: `a: 1`,
|
|
expObj: outerWDefault{Foo: foo{A: 1}},
|
|
expTypeTag: "foo",
|
|
expElem: foo{A: 1},
|
|
expMarshalOut: "type: foo\na: 1",
|
|
},
|
|
{
|
|
descr: "invalid type value",
|
|
str: "type: INVALID",
|
|
expErr: "invalid type value",
|
|
expObj: outer{},
|
|
},
|
|
{
|
|
descr: "foo set",
|
|
str: "type: foo\na: 1",
|
|
expObj: outer{Foo: foo{A: 1}},
|
|
expTypeTag: "foo",
|
|
expElem: foo{A: 1},
|
|
},
|
|
{
|
|
descr: "bar set",
|
|
str: "type: bar\nb: 1",
|
|
expObj: outer{Bar: &bar{B: 1}},
|
|
expTypeTag: "bar",
|
|
expElem: &bar{B: 1},
|
|
},
|
|
{
|
|
descr: "foo and other_field set",
|
|
str: "type: foo\na: 1\nother_field: aaa",
|
|
expObj: outer{Foo: foo{A: 1}, Other: "aaa"},
|
|
expTypeTag: "foo",
|
|
expElem: foo{A: 1},
|
|
},
|
|
{
|
|
descr: "type is same as field name",
|
|
str: "type: baz\nbaz: 3",
|
|
expObj: outer{Baz: baz{Baz: 3}},
|
|
expTypeTag: "baz",
|
|
expElem: baz{Baz: 3},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.descr, func(t *testing.T) {
|
|
|
|
intoV := reflect.New(reflect.TypeOf(test.expObj))
|
|
|
|
err := yaml.Unmarshal([]byte(test.str), intoV.Interface())
|
|
if test.expErr != "" {
|
|
if err == nil || !strings.HasPrefix(err.Error(), test.expErr) {
|
|
t.Fatalf("expected error %q when unmarshaling but got: %v", test.expErr, err)
|
|
}
|
|
return
|
|
} else if test.expErr == "" && err != nil {
|
|
t.Fatalf("unmarshaling %q returned unexpected error: %v", test.str, err)
|
|
}
|
|
|
|
into := intoV.Elem().Interface()
|
|
if !reflect.DeepEqual(into, test.expObj) {
|
|
t.Fatalf("test expected value:\n%s\nbut got value:\n%s", spew.Sprint(test.expObj), spew.Sprint(into))
|
|
}
|
|
|
|
elem, typeTag, err := Element(into)
|
|
if err != nil {
|
|
t.Fatalf("error when calling Element(%s): %v", spew.Sprint(into), err)
|
|
} else if !reflect.DeepEqual(elem, test.expElem) {
|
|
t.Fatalf("test expected elem value:\n%s\nbut got value:\n%s", spew.Sprint(test.expElem), spew.Sprint(elem))
|
|
} else if typeTag != test.expTypeTag {
|
|
t.Fatalf("test expected typeTag value %q but got %q", test.expTypeTag, typeTag)
|
|
}
|
|
|
|
expMarshalOut := test.expMarshalOut
|
|
if expMarshalOut == "" {
|
|
expMarshalOut = test.str
|
|
}
|
|
expMarshalOut = strings.TrimSpace(expMarshalOut)
|
|
|
|
b, err := yaml.Marshal(into)
|
|
if err != nil {
|
|
t.Fatalf("error marshaling %s: %v", spew.Sprint(into), err)
|
|
}
|
|
marshalOut := strings.TrimSpace(string(b))
|
|
if marshalOut != expMarshalOut {
|
|
t.Fatalf("test expected to marshal to %q, but instead marshaled to %q", expMarshalOut, marshalOut)
|
|
}
|
|
})
|
|
}
|
|
}
|