From e32a2e04744250647a72bf17da1b09befc03b6b1 Mon Sep 17 00:00:00 2001 From: tro3 Date: Wed, 29 Mar 2017 14:49:41 -0700 Subject: [PATCH] Reflection-based marshaling / unmarshaling (#149) Fixes #146 --- marshal.go | 459 +++++++++++++++++++++++++++++++++++++++ marshal_test.go | 535 ++++++++++++++++++++++++++++++++++++++++++++++ marshal_test.toml | 38 ++++ 3 files changed, 1032 insertions(+) create mode 100644 marshal.go create mode 100644 marshal_test.go create mode 100644 marshal_test.toml diff --git a/marshal.go b/marshal.go new file mode 100644 index 00000000..4301a451 --- /dev/null +++ b/marshal.go @@ -0,0 +1,459 @@ +package toml + +import ( + "errors" + "fmt" + "reflect" + "strings" + "time" +) + +/* +TomlTree structural types and corresponding marshal types +------------------------------------------------------------------------------- +*TomlTree (*)struct, (*)map[string]interface{} +[]*TomlTree (*)[](*)struct, (*)[](*)map[string]interface{} +[]interface{} (as interface{}) (*)[]primitive, (*)[]([]interface{}) +interface{} (*)primitive + +TomlTree primitive types and corresponding marshal types +----------------------------------------------------------- +uint64 uint, uint8-uint64, pointers to same +int64 int, int8-uint64, pointers to same +float64 float32, float64, pointers to same +string string, pointers to same +bool bool, pointers to same +time.Time time.Time{}, pointers to same +*/ + +type tomlOpts struct { + name string + include bool + omitempty bool +} + +var timeType = reflect.TypeOf(time.Time{}) + +// Check if the given marshall type maps to a TomlTree primitive +func isPrimitive(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Ptr: + return isPrimitive(mtype.Elem()) + case reflect.Bool: + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Struct: + return mtype == timeType + default: + return false + } +} + +// Check if the given marshall type maps to a TomlTree slice +func isTreeSlice(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Slice: + return !isOtherSlice(mtype) + default: + return false + } +} + +// Check if the given marshall type maps to a non-TomlTree slice +func isOtherSlice(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Ptr: + return isOtherSlice(mtype.Elem()) + case reflect.Slice: + return isPrimitive(mtype.Elem()) || isOtherSlice(mtype.Elem()) + default: + return false + } +} + +// Check if the given marshall type maps to a TomlTree +func isTree(mtype reflect.Type) bool { + switch mtype.Kind() { + case reflect.Map: + return true + case reflect.Struct: + return !isPrimitive(mtype) + default: + return false + } +} + +/* +Marshal returns the TOML encoding of v. Behavior is similar to the Go json +encoder, except that there is no concept of a Marshaler interface or MarshalTOML +function for sub-structs, and currently only definite types can be marshaled +(i.e. no `interface{}`). + +Note that pointers are automatically assigned the "omitempty" option, as TOML +explicity does not handle null values (saying instead the label should be +dropped). +*/ +func Marshal(v interface{}) ([]byte, error) { + mtype := reflect.TypeOf(v) + if mtype.Kind() != reflect.Struct { + return []byte{}, errors.New("Only a struct can be marshaled to TOML") + } + sval := reflect.ValueOf(v) + t, err := valueToTree(mtype, sval) + if err != nil { + return []byte{}, err + } + s, err := t.ToTomlString() + return []byte(s), err +} + +// Convert given marshal struct or map value to toml tree +func valueToTree(mtype reflect.Type, mval reflect.Value) (*TomlTree, error) { + if mtype.Kind() == reflect.Ptr { + return valueToTree(mtype.Elem(), mval.Elem()) + } + tval := newTomlTree() + switch mtype.Kind() { + case reflect.Struct: + for i := 0; i < mtype.NumField(); i++ { + mtypef, mvalf := mtype.Field(i), mval.Field(i) + opts := tomlOptions(mtypef) + if opts.include && (!opts.omitempty || !isZero(mvalf)) { + val, err := valueToToml(mtypef.Type, mvalf) + if err != nil { + return nil, err + } + tval.Set(opts.name, val) + } + } + case reflect.Map: + for _, key := range mval.MapKeys() { + mvalf := mval.MapIndex(key) + val, err := valueToToml(mtype.Elem(), mvalf) + if err != nil { + return nil, err + } + tval.Set(key.String(), val) + } + } + return tval, nil +} + +// Convert given marshal slice to slice of Toml trees +func valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*TomlTree, error) { + tval := make([]*TomlTree, mval.Len(), mval.Len()) + for i := 0; i < mval.Len(); i++ { + val, err := valueToTree(mtype.Elem(), mval.Index(i)) + if err != nil { + return nil, err + } + tval[i] = val + } + return tval, nil +} + +// Convert given marshal slice to slice of toml values +func valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { + tval := make([]interface{}, mval.Len(), mval.Len()) + for i := 0; i < mval.Len(); i++ { + val, err := valueToToml(mtype.Elem(), mval.Index(i)) + if err != nil { + return nil, err + } + tval[i] = val + } + return tval, nil +} + +// Convert given marshal value to toml value +func valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { + if mtype.Kind() == reflect.Ptr { + return valueToToml(mtype.Elem(), mval.Elem()) + } + switch { + case isTree(mtype): + return valueToTree(mtype, mval) + case isTreeSlice(mtype): + return valueToTreeSlice(mtype, mval) + case isOtherSlice(mtype): + return valueToOtherSlice(mtype, mval) + default: + switch mtype.Kind() { + case reflect.Bool: + return mval.Bool(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return mval.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return mval.Uint(), nil + case reflect.Float32, reflect.Float64: + return mval.Float(), nil + case reflect.String: + return mval.String(), nil + case reflect.Struct: + return mval.Interface().(time.Time), nil + default: + return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) + } + } +} + +/* +Unmarshal parses the TOML-encoded data and stores the result in the value +pointed to by v. Behavior is similar to the Go json encoder, except that there +is no concept of an Unmarshaler interface or UnmarshalTOML function for +sub-structs, and currently only definite types can be unmarshaled to (i.e. no +`interface{}`). +*/ +func Unmarshal(data []byte, v interface{}) error { + mtype := reflect.TypeOf(v) + if mtype.Kind() != reflect.Ptr || mtype.Elem().Kind() != reflect.Struct { + return errors.New("Only a pointer to struct can be unmarshaled from TOML") + } + + t, err := Load(string(data)) + if err != nil { + return err + } + + sval, err := valueFromTree(mtype.Elem(), t) + if err != nil { + return err + } + reflect.ValueOf(v).Elem().Set(sval) + return nil +} + +// Convert toml tree to marshal struct or map, using marshal type +func valueFromTree(mtype reflect.Type, tval *TomlTree) (reflect.Value, error) { + if mtype.Kind() == reflect.Ptr { + return unwrapPointer(mtype, tval) + } + var mval reflect.Value + switch mtype.Kind() { + case reflect.Struct: + mval = reflect.New(mtype).Elem() + for i := 0; i < mtype.NumField(); i++ { + mtypef := mtype.Field(i) + opts := tomlOptions(mtypef) + if opts.include { + key := opts.name + exists := tval.Has(key) + if exists { + val := tval.Get(key) + mvalf, err := valueFromToml(mtypef.Type, val) + if err != nil { + return mval, formatError(err, tval.GetPosition(key)) + } + mval.Field(i).Set(mvalf) + } + } + } + case reflect.Map: + mval = reflect.MakeMap(mtype) + for _, key := range tval.Keys() { + val := tval.Get(key) + mvalf, err := valueFromToml(mtype.Elem(), val) + if err != nil { + return mval, formatError(err, tval.GetPosition(key)) + } + mval.SetMapIndex(reflect.ValueOf(key), mvalf) + } + } + return mval, nil +} + +// Convert toml value to marshal struct/map slice, using marshal type +func valueFromTreeSlice(mtype reflect.Type, tval []*TomlTree) (reflect.Value, error) { + mval := reflect.MakeSlice(mtype, len(tval), len(tval)) + for i := 0; i < len(tval); i++ { + val, err := valueFromTree(mtype.Elem(), tval[i]) + if err != nil { + return mval, err + } + mval.Index(i).Set(val) + } + return mval, nil +} + +// Convert toml value to marshal primitive slice, using marshal type +func valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { + mval := reflect.MakeSlice(mtype, len(tval), len(tval)) + for i := 0; i < len(tval); i++ { + val, err := valueFromToml(mtype.Elem(), tval[i]) + if err != nil { + return mval, err + } + mval.Index(i).Set(val) + } + return mval, nil +} + +// Convert toml value to marshal value, using marshal type +func valueFromToml(mtype reflect.Type, tval interface{}) (reflect.Value, error) { + if mtype.Kind() == reflect.Ptr { + return unwrapPointer(mtype, tval) + } + switch { + case isTree(mtype): + return valueFromTree(mtype, tval.(*TomlTree)) + case isTreeSlice(mtype): + return valueFromTreeSlice(mtype, tval.([]*TomlTree)) + case isOtherSlice(mtype): + return valueFromOtherSlice(mtype, tval.([]interface{})) + default: + switch mtype.Kind() { + case reflect.Bool: + val, ok := tval.(bool) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to bool", tval, tval) + } + return reflect.ValueOf(val), nil + case reflect.Int: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to int", tval, tval) + } + return reflect.ValueOf(int(val)), nil + case reflect.Int8: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to int", tval, tval) + } + return reflect.ValueOf(int8(val)), nil + case reflect.Int16: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to int", tval, tval) + } + return reflect.ValueOf(int16(val)), nil + case reflect.Int32: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to int", tval, tval) + } + return reflect.ValueOf(int32(val)), nil + case reflect.Int64: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to int", tval, tval) + } + return reflect.ValueOf(val), nil + case reflect.Uint: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to uint", tval, tval) + } + return reflect.ValueOf(uint(val)), nil + case reflect.Uint8: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to uint", tval, tval) + } + return reflect.ValueOf(uint8(val)), nil + case reflect.Uint16: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to uint", tval, tval) + } + return reflect.ValueOf(uint16(val)), nil + case reflect.Uint32: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to uint", tval, tval) + } + return reflect.ValueOf(uint32(val)), nil + case reflect.Uint64: + val, ok := tval.(int64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to uint", tval, tval) + } + return reflect.ValueOf(uint64(val)), nil + case reflect.Float32: + val, ok := tval.(float64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to float", tval, tval) + } + return reflect.ValueOf(float32(val)), nil + case reflect.Float64: + val, ok := tval.(float64) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to float", tval, tval) + } + return reflect.ValueOf(val), nil + case reflect.String: + val, ok := tval.(string) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to string", tval, tval) + } + return reflect.ValueOf(val), nil + case reflect.Struct: + val, ok := tval.(time.Time) + if !ok { + return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to time", tval, tval) + } + return reflect.ValueOf(val), nil + default: + return reflect.ValueOf(nil), fmt.Errorf("Unmarshal can't handle %v(%v)", mtype, mtype.Kind()) + } + } +} + +func unwrapPointer(mtype reflect.Type, tval interface{}) (reflect.Value, error) { + val, err := valueFromToml(mtype.Elem(), tval) + if err != nil { + return reflect.ValueOf(nil), err + } + mval := reflect.New(mtype.Elem()) + mval.Elem().Set(val) + return mval, nil +} + +func tomlOptions(vf reflect.StructField) tomlOpts { + tag := vf.Tag.Get("toml") + parse := strings.Split(tag, ",") + result := tomlOpts{vf.Name, true, false} + if parse[0] != "" { + if parse[0] == "-" && len(parse) == 1 { + result.include = false + } else { + result.name = strings.Trim(parse[0], " ") + } + } + if vf.PkgPath != "" { + result.include = false + } + if len(parse) > 1 && strings.Trim(parse[1], " ") == "omitempty" { + result.omitempty = true + } + if vf.Type.Kind() == reflect.Ptr { + result.omitempty = true + } + return result +} + +func isZero(val reflect.Value) bool { + switch val.Type().Kind() { + case reflect.Map: + fallthrough + case reflect.Array: + fallthrough + case reflect.Slice: + return val.Len() == 0 + default: + return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) + } +} + +func formatError(err error, pos Position) error { + if err.Error()[0] == '(' { // Error already contains position information + return err + } + return fmt.Errorf("%s: %s", pos, err) +} diff --git a/marshal_test.go b/marshal_test.go new file mode 100644 index 00000000..c8dee94d --- /dev/null +++ b/marshal_test.go @@ -0,0 +1,535 @@ +package toml + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "reflect" + "testing" + "time" +) + +type basicMarshalTestStruct struct { + String string `toml:"string"` + StringList []string `toml:"strlist"` + Sub basicMarshalTestSubStruct `toml:"subdoc"` + SubList []basicMarshalTestSubStruct `toml:"sublist"` +} + +type basicMarshalTestSubStruct struct { + String2 string +} + +var basicTestData = basicMarshalTestStruct{ + String: "Hello", + StringList: []string{"Howdy", "Hey There"}, + Sub: basicMarshalTestSubStruct{"One"}, + SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, +} + +var basicTestToml = []byte(`string = "Hello" +strlist = ["Howdy","Hey There"] + +[subdoc] + String2 = "One" + +[[sublist]] + String2 = "Two" + +[[sublist]] + String2 = "Three" +`) + +func TestBasicMarshal(t *testing.T) { + result, err := Marshal(basicTestData) + if err != nil { + t.Fatal(err) + } + expected := basicTestToml + if !bytes.Equal(result, expected) { + t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) + } +} + +func TestBasicUnmarshal(t *testing.T) { + result := basicMarshalTestStruct{} + err := Unmarshal(basicTestToml, &result) + expected := basicTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad unmarshal: expected %v, got %v", expected, result) + } +} + +type testDoc struct { + Title string `toml:"title"` + Basics testDocBasics `toml:"basic"` + BasicLists testDocBasicLists `toml:"basic_lists"` + BasicMap map[string]string `toml:"basic_map"` + Subdocs testDocSubs `toml:"subdoc"` + SubDocList []testSubDoc `toml:"subdoclist"` + SubDocPtrs []*testSubDoc `toml:"subdocptrs"` + err int `toml:"shouldntBeHere"` + unexported int `toml:"shouldntBeHere"` + Unexported2 int `toml:"-"` +} + +type testDocBasics struct { + Bool bool `toml:"bool"` + Date time.Time `toml:"date"` + Float float32 `toml:"float"` + Int int `toml:"int"` + Uint uint `toml:"uint"` + String *string `toml:"string"` + unexported int `toml:"shouldntBeHere"` +} + +type testDocBasicLists struct { + Bools []bool `toml:"bools"` + Dates []time.Time `toml:"dates"` + Floats []*float32 `toml:"floats"` + Ints []int `toml:"ints"` + Strings []string `toml:"strings"` + UInts []uint `toml:"uints"` +} + +type testDocSubs struct { + First testSubDoc `toml:"first"` + Second *testSubDoc `toml:"second"` +} + +type testSubDoc struct { + Name string `toml:"name"` + unexported int `toml:"shouldntBeHere"` +} + +var biteMe = "Bite me" +var float1 float32 = 12.3 +var float2 float32 = 45.6 +var float3 float32 = 78.9 +var subdoc = testSubDoc{"Second", 0} + +var docData = testDoc{ + Title: "TOML Marshal Testing", + unexported: 0, + Unexported2: 0, + Basics: testDocBasics{ + Bool: true, + Date: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + Float: 123.4, + Int: 5000, + Uint: 5001, + String: &biteMe, + unexported: 0, + }, + BasicLists: testDocBasicLists{ + Bools: []bool{true, false, true}, + Dates: []time.Time{ + time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC), + time.Date(1980, 5, 27, 7, 32, 0, 0, time.UTC), + }, + Floats: []*float32{&float1, &float2, &float3}, + Ints: []int{8001, 8001, 8002}, + Strings: []string{"One", "Two", "Three"}, + UInts: []uint{5002, 5003}, + }, + BasicMap: map[string]string{ + "one": "one", + "two": "two", + }, + Subdocs: testDocSubs{ + First: testSubDoc{"First", 0}, + Second: &subdoc, + }, + SubDocList: []testSubDoc{ + testSubDoc{"List.First", 0}, + testSubDoc{"List.Second", 0}, + }, + SubDocPtrs: []*testSubDoc{&subdoc}, +} + +func TestDocMarshal(t *testing.T) { + result, err := Marshal(docData) + if err != nil { + t.Fatal(err) + } + expected, _ := ioutil.ReadFile("marshal_test.toml") + if !bytes.Equal(result, expected) { + t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) + } +} + +func TestDocUnmarshal(t *testing.T) { + result := testDoc{} + tomlData, _ := ioutil.ReadFile("marshal_test.toml") + err := Unmarshal(tomlData, &result) + expected := docData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + resStr, _ := json.MarshalIndent(result, "", " ") + expStr, _ := json.MarshalIndent(expected, "", " ") + t.Errorf("Bad unmarshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expStr, resStr) + } +} + +type tomlTypeCheckTest struct { + name string + item interface{} + typ int //0=primitive, 1=otherslice, 2=treeslice, 3=tree +} + +func TestTypeChecks(t *testing.T) { + tests := []tomlTypeCheckTest{ + {"integer", 2, 0}, + {"time", time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), 0}, + {"stringlist", []string{"hello", "hi"}, 1}, + {"timelist", []time.Time{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)}, 1}, + {"objectlist", []tomlTypeCheckTest{}, 2}, + {"object", tomlTypeCheckTest{}, 3}, + } + + for _, test := range tests { + expected := []bool{false, false, false, false} + expected[test.typ] = true + result := []bool{ + isPrimitive(reflect.TypeOf(test.item)), + isOtherSlice(reflect.TypeOf(test.item)), + isTreeSlice(reflect.TypeOf(test.item)), + isTree(reflect.TypeOf(test.item)), + } + if !reflect.DeepEqual(expected, result) { + t.Errorf("Bad type check on %q: expected %v, got %v", test.name, expected, result) + } + } +} + +type unexportedMarshalTestStruct struct { + String string `toml:"string"` + StringList []string `toml:"strlist"` + Sub basicMarshalTestSubStruct `toml:"subdoc"` + SubList []basicMarshalTestSubStruct `toml:"sublist"` + unexported int `toml:"shouldntBeHere"` + Unexported2 int `toml:"-"` +} + +var unexportedTestData = unexportedMarshalTestStruct{ + String: "Hello", + StringList: []string{"Howdy", "Hey There"}, + Sub: basicMarshalTestSubStruct{"One"}, + SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, + unexported: 0, + Unexported2: 0, +} + +var unexportedTestToml = []byte(`string = "Hello" +strlist = ["Howdy","Hey There"] +unexported = 1 +shouldntBeHere = 2 + +[subdoc] + String2 = "One" + +[[sublist]] + String2 = "Two" + +[[sublist]] + String2 = "Three" +`) + +func TestUnexportedUnmarshal(t *testing.T) { + result := unexportedMarshalTestStruct{} + err := Unmarshal(unexportedTestToml, &result) + expected := unexportedTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad unexported unmarshal: expected %v, got %v", expected, result) + } +} + +type errStruct struct { + Bool bool `toml:"bool"` + Date time.Time `toml:"date"` + Float float64 `toml:"float"` + Int int16 `toml:"int"` + String *string `toml:"string"` +} + +var errTomls = []string{ + "bool = truly\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:3200Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123a4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = j000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = Bite me", + "bool = 1\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1\nfloat = 123.4\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\n\"sorry\"\nint = 5000\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = \"sorry\"\nstring = \"Bite me\"", + "bool = true\ndate = 1979-05-27T07:32:00Z\nfloat = 123.4\nint = 5000\nstring = 1", +} + +type mapErr struct { + Vals map[string]float64 +} + +type intErr struct { + Int1 int + Int2 int8 + Int3 int16 + Int4 int32 + Int5 int64 + UInt1 uint + UInt2 uint8 + UInt3 uint16 + UInt4 uint32 + UInt5 uint64 + Flt1 float32 + Flt2 float64 +} + +var intErrTomls = []string{ + "Int1 = []\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = []\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = []\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = []\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = []\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = []\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = []\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = []\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = []\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = []\nFlt1 = 1.0\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = []\nFlt2 = 2.0", + "Int1 = 1\nInt2 = 2\nInt3 = 3\nInt4 = 4\nInt5 = 5\nUInt1 = 1\nUInt2 = 2\nUInt3 = 3\nUInt4 = 4\nUInt5 = 5\nFlt1 = 1.0\nFlt2 = []", +} + +func TestErrUnmarshal(t *testing.T) { + for ind, toml := range errTomls { + result := errStruct{} + err := Unmarshal([]byte(toml), &result) + if err == nil { + t.Errorf("Expected err from case %d\n", ind) + } + } + result2 := mapErr{} + err := Unmarshal([]byte("[Vals]\nfred=\"1.2\""), &result2) + if err == nil { + t.Errorf("Expected err from map") + } + for ind, toml := range intErrTomls { + result3 := intErr{} + err := Unmarshal([]byte(toml), &result3) + if err == nil { + t.Errorf("Expected int err from case %d\n", ind) + } + } +} + +type emptyMarshalTestStruct struct { + Title string `toml:"title"` + Bool bool `toml:"bool"` + Int int `toml:"int"` + String string `toml:"string"` + StringList []string `toml:"stringlist"` + Ptr *basicMarshalTestStruct `toml:"ptr"` + Map map[string]string `toml:"map"` +} + +var emptyTestData = emptyMarshalTestStruct{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, +} + +var emptyTestToml = []byte(`bool = false +int = 0 +string = "" +stringlist = [] +title = "Placeholder" + +[map] +`) + +type emptyMarshalTestStruct2 struct { + Title string `toml:"title"` + Bool bool `toml:"bool,omitempty"` + Int int `toml:"int, omitempty"` + String string `toml:"string,omitempty "` + StringList []string `toml:"stringlist,omitempty"` + Ptr *basicMarshalTestStruct `toml:"ptr,omitempty"` + Map map[string]string `toml:"map,omitempty"` +} + +var emptyTestData2 = emptyMarshalTestStruct2{ + Title: "Placeholder", + Bool: false, + Int: 0, + String: "", + StringList: []string{}, + Ptr: nil, + Map: map[string]string{}, +} + +var emptyTestToml2 = []byte(`title = "Placeholder" +`) + +func TestEmptyMarshal(t *testing.T) { + result, err := Marshal(emptyTestData) + if err != nil { + t.Fatal(err) + } + expected := emptyTestToml + if !bytes.Equal(result, expected) { + t.Errorf("Bad empty marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) + } +} + +func TestEmptyMarshalOmit(t *testing.T) { + result, err := Marshal(emptyTestData2) + if err != nil { + t.Fatal(err) + } + expected := emptyTestToml2 + if !bytes.Equal(result, expected) { + t.Errorf("Bad empty omit marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) + } +} + +func TestEmptyUnmarshal(t *testing.T) { + result := emptyMarshalTestStruct{} + err := Unmarshal(emptyTestToml, &result) + expected := emptyTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad empty unmarshal: expected %v, got %v", expected, result) + } +} + +func TestEmptyUnmarshalOmit(t *testing.T) { + result := emptyMarshalTestStruct2{} + err := Unmarshal(emptyTestToml, &result) + expected := emptyTestData2 + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad empty omit unmarshal: expected %v, got %v", expected, result) + } +} + +type pointerMarshalTestStruct struct { + Str *string + List *[]string + ListPtr *[]*string + Map *map[string]string + MapPtr *map[string]*string + EmptyStr *string + EmptyList *[]string + EmptyMap *map[string]string + DblPtr *[]*[]*string +} + +var pointerStr = "Hello" +var pointerList = []string{"Hello back"} +var pointerListPtr = []*string{&pointerStr} +var pointerMap = map[string]string{"response": "Goodbye"} +var pointerMapPtr = map[string]*string{"alternate": &pointerStr} +var pointerTestData = pointerMarshalTestStruct{ + Str: &pointerStr, + List: &pointerList, + ListPtr: &pointerListPtr, + Map: &pointerMap, + MapPtr: &pointerMapPtr, + EmptyStr: nil, + EmptyList: nil, + EmptyMap: nil, +} + +var pointerTestToml = []byte(`List = ["Hello back"] +ListPtr = ["Hello"] +Str = "Hello" + +[Map] + response = "Goodbye" + +[MapPtr] + alternate = "Hello" +`) + +func TestPointerMarshal(t *testing.T) { + result, err := Marshal(pointerTestData) + if err != nil { + t.Fatal(err) + } + expected := pointerTestToml + if !bytes.Equal(result, expected) { + t.Errorf("Bad pointer marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) + } +} + +func TestPointerUnmarshal(t *testing.T) { + result := pointerMarshalTestStruct{} + err := Unmarshal(pointerTestToml, &result) + expected := pointerTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad pointer unmarshal: expected %v, got %v", expected, result) + } +} + +type nestedMarshalTestStruct struct { + String [][]string + //Struct [][]basicMarshalTestSubStruct + StringPtr *[]*[]*string + // StructPtr *[]*[]*basicMarshalTestSubStruct +} + +var str1 = "Three" +var str2 = "Four" +var strPtr = []*string{&str1, &str2} +var strPtr2 = []*[]*string{&strPtr} + +var nestedTestData = nestedMarshalTestStruct{ + String: [][]string{[]string{"Five", "Six"}, []string{"One", "Two"}}, + StringPtr: &strPtr2, +} + +var nestedTestToml = []byte(`String = [["Five","Six"],["One","Two"]] +StringPtr = [["Three","Four"]] +`) + +func TestNestedMarshal(t *testing.T) { + result, err := Marshal(nestedTestData) + if err != nil { + t.Fatal(err) + } + expected := nestedTestToml + if !bytes.Equal(result, expected) { + t.Errorf("Bad nested marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result) + } +} + +func TestNestedUnmarshal(t *testing.T) { + result := nestedMarshalTestStruct{} + err := Unmarshal(nestedTestToml, &result) + expected := nestedTestData + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Bad nested unmarshal: expected %v, got %v", expected, result) + } +} diff --git a/marshal_test.toml b/marshal_test.toml new file mode 100644 index 00000000..1c5f98e7 --- /dev/null +++ b/marshal_test.toml @@ -0,0 +1,38 @@ +title = "TOML Marshal Testing" + +[basic] + bool = true + date = 1979-05-27T07:32:00Z + float = 123.4 + int = 5000 + string = "Bite me" + uint = 5001 + +[basic_lists] + bools = [true,false,true] + dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] + floats = [12.3,45.6,78.9] + ints = [8001,8001,8002] + strings = ["One","Two","Three"] + uints = [5002,5003] + +[basic_map] + one = "one" + two = "two" + +[subdoc] + + [subdoc.first] + name = "First" + + [subdoc.second] + name = "Second" + +[[subdoclist]] + name = "List.First" + +[[subdoclist]] + name = "List.Second" + +[[subdocptrs]] + name = "Second"