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) } }) } }