diff --git a/custom_marshaler_test.go b/custom_marshaler_test.go deleted file mode 100644 index 08aff472..00000000 --- a/custom_marshaler_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package toml_test - -import ( - "bytes" - "errors" - "fmt" - "github.com/BurntSushi/toml" - "strconv" - "strings" - "testing" -) - -// Test for hotfix-341 -func TestCustomEncode(t *testing.T) { - - var enum = Enum(OtherValue) - - outer := Outer{ - String: &InnerString{value: "value"}, - Int: &InnerInt{value: 10}, - Bool: &InnerBool{value: true}, - Enum: &enum, - ArrayS: &InnerArrayString{value: []string{"text1", "text2"}}, - ArrayI: &InnerArrayInt{value: []int64{5, 7, 3}}, - } - - var buf bytes.Buffer - err := toml.NewEncoder(&buf).Encode(outer) - if err != nil { - t.Errorf("Encode failed: %s", err) - } - - have := strings.TrimSpace(buf.String()) - want := strings.TrimSpace("String = \"value\"\nInt = 10\nBool = true\nEnum = \"OTHER_VALUE\"\nArrayS = [\"text1\", \"text2\"]\nArrayI = [5, 7, 3]\n") - if want != have { - t.Errorf("\nhave:\n%s\nwant:\n%s\n", have, want) - } -} - -// Test for hotfix-341 -func TestCustomDecode(t *testing.T) { - - const testToml = "Bool = true\nString = \"test\"\nInt = 10\nEnum = \"OTHER_VALUE\"\nArrayS = [\"text1\", \"text2\"]\nArrayI = [5, 7, 3]" - - outer := Outer{} - _, err := toml.Decode(testToml, &outer) - - if err != nil { - t.Fatal(fmt.Sprintf("Decode failed: %s", err)) - } - if outer.String.value != "test" { - t.Errorf("\nhave:\n%s\nwant:\n%s\n", outer.String.value, "test") - } - if outer.Bool.value != true { - t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Bool.value, true) - } - if outer.Int.value != 10 { - t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Int.value, 10) - } - - if *outer.Enum != OtherValue { - t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Enum, OtherValue) - } - if fmt.Sprint(outer.ArrayS.value) != fmt.Sprint([]string{"text1", "text2"}) { - t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.ArrayS.value, []string{"text1", "text2"}) - } - if fmt.Sprint(outer.ArrayI.value) != fmt.Sprint([]int64{5, 7, 3}) { - t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.ArrayI.value, []int64{5, 7, 3}) - } -} - -/* Implementing MarshalTOML and UnmarshalTOML structs - An useful use could be to map a TOML value to an internal value, like emuns. -*/ - -type Enum int - -const ( - NoValue Enum = iota - SomeValue - OtherValue -) - -func (e *Enum) Value() string { - switch *e { - case SomeValue: - return "SOME_VALUE" - case OtherValue: - return "OTHER_VALUE" - case NoValue: - return "" - } - return "" -} - -func (e *Enum) MarshalTOML() ([]byte, error) { - return []byte("\"" + e.Value() + "\""), nil -} - -func (e *Enum) UnmarshalTOML(value interface{}) error { - sValue, ok := value.(string) - if !ok { - return fmt.Errorf("value %v is not a string type", value) - } - for _, enum := range []Enum{NoValue, SomeValue, OtherValue} { - if enum.Value() == sValue { - *e = enum - return nil - } - } - return errors.New("invalid enum value") -} - -type InnerString struct { - value string -} - -func (s *InnerString) MarshalTOML() ([]byte, error) { - return []byte("\"" + s.value + "\""), nil -} -func (s *InnerString) UnmarshalTOML(value interface{}) error { - sValue, ok := value.(string) - if !ok { - return fmt.Errorf("value %v is not a string type", value) - } - s.value = sValue - return nil -} - -type InnerInt struct { - value int -} - -func (i *InnerInt) MarshalTOML() ([]byte, error) { - return []byte(strconv.Itoa(i.value)), nil -} -func (i *InnerInt) UnmarshalTOML(value interface{}) error { - iValue, ok := value.(int64) - if !ok { - return fmt.Errorf("value %v is not a int type", value) - } - i.value = int(iValue) - return nil -} - -type InnerBool struct { - value bool -} - -func (b *InnerBool) MarshalTOML() ([]byte, error) { - return []byte(strconv.FormatBool(b.value)), nil -} -func (b *InnerBool) UnmarshalTOML(value interface{}) error { - bValue, ok := value.(bool) - if !ok { - return fmt.Errorf("value %v is not a bool type", value) - } - b.value = bValue - return nil -} - - -type InnerArrayString struct { - value []string -} - -func (as *InnerArrayString) MarshalTOML() ([]byte, error) { - return []byte("[\"" + strings.Join(as.value, "\", \"") + "\"]"), nil -} - -func (as *InnerArrayString) UnmarshalTOML(value interface{}) error { - if value != nil { - asValue, ok := value.([]interface{}) - if !ok { - return fmt.Errorf("value %v is not a [] type", value) - } - as.value = []string{} - for _, value := range asValue { - as.value = append(as.value, value.(string)) - } - } - return nil -} - -type InnerArrayInt struct { - value []int64 -} - -func (ai *InnerArrayInt) MarshalTOML() ([]byte, error) { - strArr := []string{} - for _, intV := range ai.value { - strArr = append(strArr, strconv.FormatInt(intV, 10)) - } - return []byte("[" + strings.Join(strArr, ", ") + "]"), nil -} - -func (ai *InnerArrayInt) UnmarshalTOML(value interface{}) error { - if value != nil { - asValue, ok := value.([]interface{}) - if !ok { - return fmt.Errorf("value %v is not a [] type", value) - } - ai.value = []int64{} - for _, value := range asValue { - ai.value = append(ai.value, value.(int64)) - } - } - return nil -} - -type Outer struct { - String *InnerString - Int *InnerInt - Bool *InnerBool - Enum *Enum - ArrayS *InnerArrayString - ArrayI *InnerArrayInt -} diff --git a/decode_test.go b/decode_test.go index e27c4351..f2a12ede 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1,12 +1,15 @@ package toml import ( + "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "math" "os" "reflect" + "strconv" "strings" "testing" "time" @@ -1014,6 +1017,129 @@ func TestMetaDotConflict(t *testing.T) { } } +type ( + Outer struct { + Int *InnerInt + Enum *Enum + Slice *InnerArrayString + } + Enum int + InnerString struct{ value string } + InnerInt struct{ value int } + InnerBool struct{ value bool } + InnerArrayString struct{ value []string } +) + +const ( + NoValue Enum = iota + OtherValue +) + +func (e *Enum) Value() string { + switch *e { + case OtherValue: + return "OTHER_VALUE" + } + return "" +} + +func (e *Enum) MarshalTOML() ([]byte, error) { + return []byte(`"` + e.Value() + `"`), nil +} + +func (e *Enum) UnmarshalTOML(value interface{}) error { + sValue, ok := value.(string) + if !ok { + return fmt.Errorf("value %v is not a string type", value) + } + for _, enum := range []Enum{NoValue, OtherValue} { + if enum.Value() == sValue { + *e = enum + return nil + } + } + return errors.New("invalid enum value") +} + +func (i *InnerInt) MarshalTOML() ([]byte, error) { + return []byte(strconv.Itoa(i.value)), nil +} +func (i *InnerInt) UnmarshalTOML(value interface{}) error { + iValue, ok := value.(int64) + if !ok { + return fmt.Errorf("value %v is not a int type", value) + } + i.value = int(iValue) + return nil +} + +func (as *InnerArrayString) MarshalTOML() ([]byte, error) { + return []byte("[\"" + strings.Join(as.value, "\", \"") + "\"]"), nil +} + +func (as *InnerArrayString) UnmarshalTOML(value interface{}) error { + if value != nil { + asValue, ok := value.([]interface{}) + if !ok { + return fmt.Errorf("value %v is not a [] type", value) + } + as.value = []string{} + for _, value := range asValue { + as.value = append(as.value, value.(string)) + } + } + return nil +} + +// Test for #341 +func TestCustomEncode(t *testing.T) { + enum := OtherValue + outer := Outer{ + Int: &InnerInt{value: 10}, + Enum: &enum, + Slice: &InnerArrayString{value: []string{"text1", "text2"}}, + } + + var buf bytes.Buffer + err := NewEncoder(&buf).Encode(outer) + if err != nil { + t.Errorf("Encode failed: %s", err) + } + + have := strings.TrimSpace(buf.String()) + want := strings.ReplaceAll(strings.TrimSpace(` + Int = 10 + Enum = "OTHER_VALUE" + Slice = ["text1", "text2"] + `), "\t", "") + if want != have { + t.Errorf("\nhave: %s\nwant: %s\n", have, want) + } +} + +// Test for #341 +func TestCustomDecode(t *testing.T) { + var outer Outer + _, err := Decode(` + Int = 10 + Enum = "OTHER_VALUE" + Slice = ["text1", "text2"] + `, &outer) + if err != nil { + t.Fatal(fmt.Sprintf("Decode failed: %s", err)) + } + + if outer.Int.value != 10 { + t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Int.value, 10) + } + if *outer.Enum != OtherValue { + t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Enum, OtherValue) + } + if fmt.Sprint(outer.Slice.value) != fmt.Sprint([]string{"text1", "text2"}) { + t.Errorf("\nhave:\n%v\nwant:\n%v\n", outer.Slice.value, []string{"text1", "text2"}) + } +} + // errorContains checks if the error message in have contains the text in // want. // diff --git a/encode_test.go b/encode_test.go index 6a1e57d3..93fb7d27 100644 --- a/encode_test.go +++ b/encode_test.go @@ -763,6 +763,373 @@ func TestEncodeJSONNumber(t *testing.T) { } } +func TestEncode(t *testing.T) { + type Embedded struct { + Int int `toml:"_int"` + } + type NonStruct int + + date := time.Date(2014, 5, 11, 19, 30, 40, 0, time.UTC) + dateStr := "2014-05-11T19:30:40Z" + + tests := map[string]struct { + input interface{} + wantOutput string + wantError error + }{ + "bool field": { + input: struct { + BoolTrue bool + BoolFalse bool + }{true, false}, + wantOutput: "BoolTrue = true\nBoolFalse = false\n", + }, + "int fields": { + input: struct { + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + }{1, 2, 3, 4, 5}, + wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", + }, + "uint fields": { + input: struct { + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + }{1, 2, 3, 4, 5}, + wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + + "\nUint64 = 5\n", + }, + "float fields": { + input: struct { + Float32 float32 + Float64 float64 + }{1.5, 2.5}, + wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", + }, + "string field": { + input: struct{ String string }{"foo"}, + wantOutput: "String = \"foo\"\n", + }, + "string field with \\n escape": { + input: struct{ String string }{"foo\n"}, + wantOutput: "String = \"foo\\n\"\n", + }, + "string field and unexported field": { + input: struct { + String string + unexported int + }{"foo", 0}, + wantOutput: "String = \"foo\"\n", + }, + "datetime field in UTC": { + input: struct{ Date time.Time }{date}, + wantOutput: fmt.Sprintf("Date = %s\n", dateStr), + }, + "datetime field as primitive": { + // Using a map here to fail if isStructOrMap() returns true for + // time.Time. + input: map[string]interface{}{ + "Date": date, + "Int": 1, + }, + wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), + }, + "array fields": { + input: struct { + IntArray0 [0]int + IntArray3 [3]int + }{[0]int{}, [3]int{1, 2, 3}}, + wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", + }, + "slice fields": { + input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ + nil, []int{}, []int{1, 2, 3}, + }, + wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", + }, + "datetime slices": { + input: struct{ DatetimeSlice []time.Time }{ + []time.Time{date, date}, + }, + wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", + dateStr, dateStr), + }, + "nested arrays and slices": { + input: struct { + SliceOfArrays [][2]int + ArrayOfSlices [2][]int + SliceOfArraysOfSlices [][2][]int + ArrayOfSlicesOfArrays [2][][2]int + SliceOfMixedArrays [][2]interface{} + ArrayOfMixedSlices [2][]interface{} + }{ + [][2]int{{1, 2}, {3, 4}}, + [2][]int{{1, 2}, {3, 4}}, + [][2][]int{ + { + {1, 2}, {3, 4}, + }, + { + {5, 6}, {7, 8}, + }, + }, + [2][][2]int{ + { + {1, 2}, {3, 4}, + }, + { + {5, 6}, {7, 8}, + }, + }, + [][2]interface{}{ + {1, 2}, {"a", "b"}, + }, + [2][]interface{}{ + {1, 2}, {"a", "b"}, + }, + }, + wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] +ArrayOfSlices = [[1, 2], [3, 4]] +SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +SliceOfMixedArrays = [[1, 2], ["a", "b"]] +ArrayOfMixedSlices = [[1, 2], ["a", "b"]] +`, + }, + "empty slice": { + input: struct{ Empty []interface{} }{[]interface{}{}}, + wantOutput: "Empty = []\n", + }, + "(error) slice with element type mismatch (string and integer)": { + input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, + wantOutput: "Mixed = [1, \"a\"]\n", + }, + "(error) slice with element type mismatch (integer and float)": { + input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, + wantOutput: "Mixed = [1, 2.5]\n", + }, + "slice with elems of differing Go types, same TOML types": { + input: struct { + MixedInts []interface{} + MixedFloats []interface{} + }{ + []interface{}{ + int(1), int8(2), int16(3), int32(4), int64(5), + uint(1), uint8(2), uint16(3), uint32(4), uint64(5), + }, + []interface{}{float32(1.5), float64(2.5)}, + }, + wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + + "MixedFloats = [1.5, 2.5]\n", + }, + "(error) slice w/ element type mismatch (one is nested array)": { + input: struct{ Mixed []interface{} }{ + []interface{}{1, []interface{}{2}}, + }, + wantOutput: "Mixed = [1, [2]]\n", + }, + "(error) slice with 1 nil element": { + input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, + wantError: errArrayNilElement, + }, + "(error) slice with 1 nil element (and other non-nil elements)": { + input: struct{ NilElement []interface{} }{ + []interface{}{1, nil}, + }, + wantError: errArrayNilElement, + }, + "simple map": { + input: map[string]int{"a": 1, "b": 2}, + wantOutput: "a = 1\nb = 2\n", + }, + "map with interface{} value type": { + input: map[string]interface{}{"a": 1, "b": "c"}, + wantOutput: "a = 1\nb = \"c\"\n", + }, + "map with interface{} value type, some of which are structs": { + input: map[string]interface{}{ + "a": struct{ Int int }{2}, + "b": 1, + }, + wantOutput: "b = 1\n\n[a]\n Int = 2\n", + }, + "nested map": { + input: map[string]map[string]int{ + "a": {"b": 1}, + "c": {"d": 2}, + }, + wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", + }, + "nested struct": { + input: struct{ Struct struct{ Int int } }{ + struct{ Int int }{1}, + }, + wantOutput: "[Struct]\n Int = 1\n", + }, + "nested struct and non-struct field": { + input: struct { + Struct struct{ Int int } + Bool bool + }{struct{ Int int }{1}, true}, + wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", + }, + "2 nested structs": { + input: struct{ Struct1, Struct2 struct{ Int int } }{ + struct{ Int int }{1}, struct{ Int int }{2}, + }, + wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", + }, + "deeply nested structs": { + input: struct { + Struct1, Struct2 struct{ Struct3 *struct{ Int int } } + }{ + struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, + struct{ Struct3 *struct{ Int int } }{nil}, + }, + wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + + "\n\n[Struct2]\n", + }, + "nested struct with nil struct elem": { + input: struct { + Struct struct{ Inner *struct{ Int int } } + }{ + struct{ Inner *struct{ Int int } }{nil}, + }, + wantOutput: "[Struct]\n", + }, + "nested struct with no fields": { + input: struct { + Struct struct{ Inner struct{} } + }{ + struct{ Inner struct{} }{struct{}{}}, + }, + wantOutput: "[Struct]\n [Struct.Inner]\n", + }, + "struct with tags": { + input: struct { + Struct struct { + Int int `toml:"_int"` + } `toml:"_struct"` + Bool bool `toml:"_bool"` + }{ + struct { + Int int `toml:"_int"` + }{1}, true, + }, + wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", + }, + "embedded struct": { + input: struct{ Embedded }{Embedded{1}}, + wantOutput: "_int = 1\n", + }, + "embedded *struct": { + input: struct{ *Embedded }{&Embedded{1}}, + wantOutput: "_int = 1\n", + }, + "nested embedded struct": { + input: struct { + Struct struct{ Embedded } `toml:"_struct"` + }{struct{ Embedded }{Embedded{1}}}, + wantOutput: "[_struct]\n _int = 1\n", + }, + "nested embedded *struct": { + input: struct { + Struct struct{ *Embedded } `toml:"_struct"` + }{struct{ *Embedded }{&Embedded{1}}}, + wantOutput: "[_struct]\n _int = 1\n", + }, + "embedded non-struct": { + input: struct{ NonStruct }{5}, + wantOutput: "NonStruct = 5\n", + }, + "array of tables": { + input: struct { + Structs []*struct{ Int int } `toml:"struct"` + }{ + []*struct{ Int int }{{1}, {3}}, + }, + wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", + }, + "array of tables order": { + input: map[string]interface{}{ + "map": map[string]interface{}{ + "zero": 5, + "arr": []map[string]int{ + { + "friend": 5, + }, + }, + }, + }, + wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", + }, + "empty key name": { + input: map[string]int{"": 1}, + wantOutput: `"" = 1` + "\n", + }, + "key with \\n escape": { + input: map[string]string{"\n": "\n"}, + wantOutput: `"\n" = "\n"` + "\n", + }, + + "empty map name": { + input: map[string]interface{}{ + "": map[string]int{"v": 1}, + }, + wantOutput: "[\"\"]\n v = 1\n", + }, + "(error) top-level slice": { + input: []struct{ Int int }{{1}, {2}, {3}}, + wantError: errNoKey, + }, + "(error) map no string key": { + input: map[int]string{1: ""}, + wantError: errNonString, + }, + + "tbl-in-arr-struct": { + input: struct { + Arr [][]struct{ A, B, C int } + }{[][]struct{ A, B, C int }{{{1, 2, 3}, {4, 5, 6}}}}, + wantOutput: "Arr = [[{A = 1, B = 2, C = 3}, {A = 4, B = 5, C = 6}]]", + }, + + "tbl-in-arr-map": { + input: map[string]interface{}{ + "arr": []interface{}{[]interface{}{ + map[string]interface{}{ + "a": []interface{}{"hello", "world"}, + "b": []interface{}{1.12, 4.1}, + "c": 1, + "d": map[string]interface{}{"e": "E"}, + "f": struct{ A, B int }{1, 2}, + "g": []struct{ A, B int }{{3, 4}, {5, 6}}, + }, + }}, + }, + wantOutput: `arr = [[{a = ["hello", "world"], b = [1.12, 4.1], c = 1, d = {e = "E"}, f = {A = 1, B = 2}, g = [{A = 3, B = 4}, {A = 5, B = 6}]}]]`, + }, + + "slice of slice": { + input: struct { + Slices [][]struct{ Int int } + }{ + [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, + }, + wantOutput: "Slices = [[{Int = 1}], [{Int = 2}], [{Int = 3}]]", + }, + } + for label, test := range tests { + encodeExpected(t, label, test.input, test.wantOutput, test.wantError) + } +} + func encodeExpected(t *testing.T, label string, val interface{}, want string, wantErr error) { t.Helper() diff --git a/move_test.go b/move_test.go deleted file mode 100644 index d7e0123b..00000000 --- a/move_test.go +++ /dev/null @@ -1,374 +0,0 @@ -package toml - -import ( - "fmt" - "testing" - "time" -) - -func TestEncode(t *testing.T) { - type Embedded struct { - Int int `toml:"_int"` - } - type NonStruct int - - date := time.Date(2014, 5, 11, 19, 30, 40, 0, time.UTC) - dateStr := "2014-05-11T19:30:40Z" - - tests := map[string]struct { - input interface{} - wantOutput string - wantError error - }{ - "bool field": { - input: struct { - BoolTrue bool - BoolFalse bool - }{true, false}, - wantOutput: "BoolTrue = true\nBoolFalse = false\n", - }, - "int fields": { - input: struct { - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - }{1, 2, 3, 4, 5}, - wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", - }, - "uint fields": { - input: struct { - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - }{1, 2, 3, 4, 5}, - wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + - "\nUint64 = 5\n", - }, - "float fields": { - input: struct { - Float32 float32 - Float64 float64 - }{1.5, 2.5}, - wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", - }, - "string field": { - input: struct{ String string }{"foo"}, - wantOutput: "String = \"foo\"\n", - }, - "string field with \\n escape": { - input: struct{ String string }{"foo\n"}, - wantOutput: "String = \"foo\\n\"\n", - }, - "string field and unexported field": { - input: struct { - String string - unexported int - }{"foo", 0}, - wantOutput: "String = \"foo\"\n", - }, - "datetime field in UTC": { - input: struct{ Date time.Time }{date}, - wantOutput: fmt.Sprintf("Date = %s\n", dateStr), - }, - "datetime field as primitive": { - // Using a map here to fail if isStructOrMap() returns true for - // time.Time. - input: map[string]interface{}{ - "Date": date, - "Int": 1, - }, - wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), - }, - "array fields": { - input: struct { - IntArray0 [0]int - IntArray3 [3]int - }{[0]int{}, [3]int{1, 2, 3}}, - wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", - }, - "slice fields": { - input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ - nil, []int{}, []int{1, 2, 3}, - }, - wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", - }, - "datetime slices": { - input: struct{ DatetimeSlice []time.Time }{ - []time.Time{date, date}, - }, - wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", - dateStr, dateStr), - }, - "nested arrays and slices": { - input: struct { - SliceOfArrays [][2]int - ArrayOfSlices [2][]int - SliceOfArraysOfSlices [][2][]int - ArrayOfSlicesOfArrays [2][][2]int - SliceOfMixedArrays [][2]interface{} - ArrayOfMixedSlices [2][]interface{} - }{ - [][2]int{{1, 2}, {3, 4}}, - [2][]int{{1, 2}, {3, 4}}, - [][2][]int{ - { - {1, 2}, {3, 4}, - }, - { - {5, 6}, {7, 8}, - }, - }, - [2][][2]int{ - { - {1, 2}, {3, 4}, - }, - { - {5, 6}, {7, 8}, - }, - }, - [][2]interface{}{ - {1, 2}, {"a", "b"}, - }, - [2][]interface{}{ - {1, 2}, {"a", "b"}, - }, - }, - wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] -ArrayOfSlices = [[1, 2], [3, 4]] -SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] -ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] -SliceOfMixedArrays = [[1, 2], ["a", "b"]] -ArrayOfMixedSlices = [[1, 2], ["a", "b"]] -`, - }, - "empty slice": { - input: struct{ Empty []interface{} }{[]interface{}{}}, - wantOutput: "Empty = []\n", - }, - "(error) slice with element type mismatch (string and integer)": { - input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, - wantOutput: "Mixed = [1, \"a\"]\n", - }, - "(error) slice with element type mismatch (integer and float)": { - input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, - wantOutput: "Mixed = [1, 2.5]\n", - }, - "slice with elems of differing Go types, same TOML types": { - input: struct { - MixedInts []interface{} - MixedFloats []interface{} - }{ - []interface{}{ - int(1), int8(2), int16(3), int32(4), int64(5), - uint(1), uint8(2), uint16(3), uint32(4), uint64(5), - }, - []interface{}{float32(1.5), float64(2.5)}, - }, - wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + - "MixedFloats = [1.5, 2.5]\n", - }, - "(error) slice w/ element type mismatch (one is nested array)": { - input: struct{ Mixed []interface{} }{ - []interface{}{1, []interface{}{2}}, - }, - wantOutput: "Mixed = [1, [2]]\n", - }, - "(error) slice with 1 nil element": { - input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, - wantError: errArrayNilElement, - }, - "(error) slice with 1 nil element (and other non-nil elements)": { - input: struct{ NilElement []interface{} }{ - []interface{}{1, nil}, - }, - wantError: errArrayNilElement, - }, - "simple map": { - input: map[string]int{"a": 1, "b": 2}, - wantOutput: "a = 1\nb = 2\n", - }, - "map with interface{} value type": { - input: map[string]interface{}{"a": 1, "b": "c"}, - wantOutput: "a = 1\nb = \"c\"\n", - }, - "map with interface{} value type, some of which are structs": { - input: map[string]interface{}{ - "a": struct{ Int int }{2}, - "b": 1, - }, - wantOutput: "b = 1\n\n[a]\n Int = 2\n", - }, - "nested map": { - input: map[string]map[string]int{ - "a": {"b": 1}, - "c": {"d": 2}, - }, - wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", - }, - "nested struct": { - input: struct{ Struct struct{ Int int } }{ - struct{ Int int }{1}, - }, - wantOutput: "[Struct]\n Int = 1\n", - }, - "nested struct and non-struct field": { - input: struct { - Struct struct{ Int int } - Bool bool - }{struct{ Int int }{1}, true}, - wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", - }, - "2 nested structs": { - input: struct{ Struct1, Struct2 struct{ Int int } }{ - struct{ Int int }{1}, struct{ Int int }{2}, - }, - wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", - }, - "deeply nested structs": { - input: struct { - Struct1, Struct2 struct{ Struct3 *struct{ Int int } } - }{ - struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, - struct{ Struct3 *struct{ Int int } }{nil}, - }, - wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + - "\n\n[Struct2]\n", - }, - "nested struct with nil struct elem": { - input: struct { - Struct struct{ Inner *struct{ Int int } } - }{ - struct{ Inner *struct{ Int int } }{nil}, - }, - wantOutput: "[Struct]\n", - }, - "nested struct with no fields": { - input: struct { - Struct struct{ Inner struct{} } - }{ - struct{ Inner struct{} }{struct{}{}}, - }, - wantOutput: "[Struct]\n [Struct.Inner]\n", - }, - "struct with tags": { - input: struct { - Struct struct { - Int int `toml:"_int"` - } `toml:"_struct"` - Bool bool `toml:"_bool"` - }{ - struct { - Int int `toml:"_int"` - }{1}, true, - }, - wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", - }, - "embedded struct": { - input: struct{ Embedded }{Embedded{1}}, - wantOutput: "_int = 1\n", - }, - "embedded *struct": { - input: struct{ *Embedded }{&Embedded{1}}, - wantOutput: "_int = 1\n", - }, - "nested embedded struct": { - input: struct { - Struct struct{ Embedded } `toml:"_struct"` - }{struct{ Embedded }{Embedded{1}}}, - wantOutput: "[_struct]\n _int = 1\n", - }, - "nested embedded *struct": { - input: struct { - Struct struct{ *Embedded } `toml:"_struct"` - }{struct{ *Embedded }{&Embedded{1}}}, - wantOutput: "[_struct]\n _int = 1\n", - }, - "embedded non-struct": { - input: struct{ NonStruct }{5}, - wantOutput: "NonStruct = 5\n", - }, - "array of tables": { - input: struct { - Structs []*struct{ Int int } `toml:"struct"` - }{ - []*struct{ Int int }{{1}, {3}}, - }, - wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", - }, - "array of tables order": { - input: map[string]interface{}{ - "map": map[string]interface{}{ - "zero": 5, - "arr": []map[string]int{ - { - "friend": 5, - }, - }, - }, - }, - wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", - }, - "empty key name": { - input: map[string]int{"": 1}, - wantOutput: `"" = 1` + "\n", - }, - "key with \\n escape": { - input: map[string]string{"\n": "\n"}, - wantOutput: `"\n" = "\n"` + "\n", - }, - - "empty map name": { - input: map[string]interface{}{ - "": map[string]int{"v": 1}, - }, - wantOutput: "[\"\"]\n v = 1\n", - }, - "(error) top-level slice": { - input: []struct{ Int int }{{1}, {2}, {3}}, - wantError: errNoKey, - }, - "(error) map no string key": { - input: map[int]string{1: ""}, - wantError: errNonString, - }, - - "tbl-in-arr-struct": { - input: struct { - Arr [][]struct{ A, B, C int } - }{[][]struct{ A, B, C int }{{{1, 2, 3}, {4, 5, 6}}}}, - wantOutput: "Arr = [[{A = 1, B = 2, C = 3}, {A = 4, B = 5, C = 6}]]", - }, - - "tbl-in-arr-map": { - input: map[string]interface{}{ - "arr": []interface{}{[]interface{}{ - map[string]interface{}{ - "a": []interface{}{"hello", "world"}, - "b": []interface{}{1.12, 4.1}, - "c": 1, - "d": map[string]interface{}{"e": "E"}, - "f": struct{ A, B int }{1, 2}, - "g": []struct{ A, B int }{{3, 4}, {5, 6}}, - }, - }}, - }, - wantOutput: `arr = [[{a = ["hello", "world"], b = [1.12, 4.1], c = 1, d = {e = "E"}, f = {A = 1, B = 2}, g = [{A = 3, B = 4}, {A = 5, B = 6}]}]]`, - }, - - "slice of slice": { - input: struct { - Slices [][]struct{ Int int } - }{ - [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, - }, - wantOutput: "Slices = [[{Int = 1}], [{Int = 2}], [{Int = 3}]]", - }, - } - for label, test := range tests { - encodeExpected(t, label, test.input, test.wantOutput, test.wantError) - } -}