1038 lines
19 KiB
Go
1038 lines
19 KiB
Go
package toml_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pelletier/go-toml/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMarshal(t *testing.T) {
|
|
someInt := 42
|
|
|
|
type structInline struct {
|
|
A interface{} `toml:",inline"`
|
|
}
|
|
|
|
type comments struct {
|
|
One int
|
|
Two int `comment:"Before kv"`
|
|
Three []int `comment:"Before array"`
|
|
}
|
|
|
|
examples := []struct {
|
|
desc string
|
|
v interface{}
|
|
expected string
|
|
err bool
|
|
}{
|
|
{
|
|
desc: "simple map and string",
|
|
v: map[string]string{
|
|
"hello": "world",
|
|
},
|
|
expected: "hello = 'world'",
|
|
},
|
|
{
|
|
desc: "map with new line in key",
|
|
v: map[string]string{
|
|
"hel\nlo": "world",
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: `map with " in key`,
|
|
v: map[string]string{
|
|
`hel"lo`: "world",
|
|
},
|
|
expected: `'hel"lo' = 'world'`,
|
|
},
|
|
{
|
|
desc: "map in map and string",
|
|
v: map[string]map[string]string{
|
|
"table": {
|
|
"hello": "world",
|
|
},
|
|
},
|
|
expected: `
|
|
[table]
|
|
hello = 'world'`,
|
|
},
|
|
{
|
|
desc: "map in map in map and string",
|
|
v: map[string]map[string]map[string]string{
|
|
"this": {
|
|
"is": {
|
|
"a": "test",
|
|
},
|
|
},
|
|
},
|
|
expected: `
|
|
[this]
|
|
[this.is]
|
|
a = 'test'`,
|
|
},
|
|
{
|
|
desc: "map in map in map and string with values",
|
|
v: map[string]interface{}{
|
|
"this": map[string]interface{}{
|
|
"is": map[string]string{
|
|
"a": "test",
|
|
},
|
|
"also": "that",
|
|
},
|
|
},
|
|
expected: `
|
|
[this]
|
|
also = 'that'
|
|
[this.is]
|
|
a = 'test'`,
|
|
},
|
|
{
|
|
desc: "simple string array",
|
|
v: map[string][]string{
|
|
"array": {"one", "two", "three"},
|
|
},
|
|
expected: `array = ['one', 'two', 'three']`,
|
|
},
|
|
{
|
|
desc: "empty string array",
|
|
v: map[string][]string{},
|
|
expected: ``,
|
|
},
|
|
{
|
|
desc: "map",
|
|
v: map[string][]string{},
|
|
expected: ``,
|
|
},
|
|
{
|
|
desc: "nested string arrays",
|
|
v: map[string][][]string{
|
|
"array": {{"one", "two"}, {"three"}},
|
|
},
|
|
expected: `array = [['one', 'two'], ['three']]`,
|
|
},
|
|
{
|
|
desc: "mixed strings and nested string arrays",
|
|
v: map[string][]interface{}{
|
|
"array": {"a string", []string{"one", "two"}, "last"},
|
|
},
|
|
expected: `array = ['a string', ['one', 'two'], 'last']`,
|
|
},
|
|
{
|
|
desc: "array of maps",
|
|
v: map[string][]map[string]string{
|
|
"top": {
|
|
{"map1.1": "v1.1"},
|
|
{"map2.1": "v2.1"},
|
|
},
|
|
},
|
|
expected: `
|
|
[[top]]
|
|
'map1.1' = 'v1.1'
|
|
[[top]]
|
|
'map2.1' = 'v2.1'
|
|
`,
|
|
},
|
|
{
|
|
desc: "map with two keys",
|
|
v: map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
},
|
|
expected: `
|
|
key1 = 'value1'
|
|
key2 = 'value2'`,
|
|
},
|
|
{
|
|
desc: "simple struct",
|
|
v: struct {
|
|
A string
|
|
}{
|
|
A: "foo",
|
|
},
|
|
expected: `A = 'foo'`,
|
|
},
|
|
{
|
|
desc: "one level of structs within structs",
|
|
v: struct {
|
|
A interface{}
|
|
}{
|
|
A: struct {
|
|
K1 string
|
|
K2 string
|
|
}{
|
|
K1: "v1",
|
|
K2: "v2",
|
|
},
|
|
},
|
|
expected: `
|
|
[A]
|
|
K1 = 'v1'
|
|
K2 = 'v2'
|
|
`,
|
|
},
|
|
{
|
|
desc: "structs in array with interfaces",
|
|
v: map[string]interface{}{
|
|
"root": map[string]interface{}{
|
|
"nested": []interface{}{
|
|
map[string]interface{}{"name": "Bob"},
|
|
map[string]interface{}{"name": "Alice"},
|
|
},
|
|
},
|
|
},
|
|
expected: `
|
|
[root]
|
|
[[root.nested]]
|
|
name = 'Bob'
|
|
[[root.nested]]
|
|
name = 'Alice'
|
|
`,
|
|
},
|
|
{
|
|
desc: "string escapes",
|
|
v: map[string]interface{}{
|
|
"a": "'\b\f\r\t\"\\",
|
|
},
|
|
expected: `a = "'\b\f\r\t\"\\"`,
|
|
},
|
|
{
|
|
desc: "string utf8 low",
|
|
v: map[string]interface{}{
|
|
"a": "'Ę",
|
|
},
|
|
expected: `a = "'Ę"`,
|
|
},
|
|
{
|
|
desc: "string utf8 low 2",
|
|
v: map[string]interface{}{
|
|
"a": "'\u10A85",
|
|
},
|
|
expected: "a = \"'\u10A85\"",
|
|
},
|
|
{
|
|
desc: "string utf8 low 2",
|
|
v: map[string]interface{}{
|
|
"a": "'\u10A85",
|
|
},
|
|
expected: "a = \"'\u10A85\"",
|
|
},
|
|
{
|
|
desc: "emoji",
|
|
v: map[string]interface{}{
|
|
"a": "'😀",
|
|
},
|
|
expected: "a = \"'😀\"",
|
|
},
|
|
{
|
|
desc: "control char",
|
|
v: map[string]interface{}{
|
|
"a": "'\u001A",
|
|
},
|
|
expected: `a = "'\u001A"`,
|
|
},
|
|
{
|
|
desc: "multi-line string",
|
|
v: map[string]interface{}{
|
|
"a": "hello\nworld",
|
|
},
|
|
expected: `a = "hello\nworld"`,
|
|
},
|
|
{
|
|
desc: "multi-line forced",
|
|
v: struct {
|
|
A string `toml:",multiline"`
|
|
}{
|
|
A: "hello\nworld",
|
|
},
|
|
expected: `A = """
|
|
hello
|
|
world"""`,
|
|
},
|
|
{
|
|
desc: "inline field",
|
|
v: struct {
|
|
A map[string]string `toml:",inline"`
|
|
B map[string]string
|
|
}{
|
|
A: map[string]string{
|
|
"isinline": "yes",
|
|
},
|
|
B: map[string]string{
|
|
"isinline": "no",
|
|
},
|
|
},
|
|
expected: `
|
|
A = {isinline = 'yes'}
|
|
[B]
|
|
isinline = 'no'
|
|
`,
|
|
},
|
|
{
|
|
desc: "mutiline array int",
|
|
v: struct {
|
|
A []int `toml:",multiline"`
|
|
B []int
|
|
}{
|
|
A: []int{1, 2, 3, 4},
|
|
B: []int{1, 2, 3, 4},
|
|
},
|
|
expected: `
|
|
A = [
|
|
1,
|
|
2,
|
|
3,
|
|
4
|
|
]
|
|
B = [1, 2, 3, 4]
|
|
`,
|
|
},
|
|
{
|
|
desc: "mutiline array in array",
|
|
v: struct {
|
|
A [][]int `toml:",multiline"`
|
|
}{
|
|
A: [][]int{{1, 2}, {3, 4}},
|
|
},
|
|
expected: `
|
|
A = [
|
|
[1, 2],
|
|
[3, 4]
|
|
]
|
|
`,
|
|
},
|
|
{
|
|
desc: "nil interface not supported at root",
|
|
v: nil,
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "nil interface not supported in slice",
|
|
v: map[string]interface{}{
|
|
"a": []interface{}{"a", nil, 2},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "nil pointer in slice uses zero value",
|
|
v: struct {
|
|
A []*int
|
|
}{
|
|
A: []*int{nil},
|
|
},
|
|
expected: `A = [0]`,
|
|
},
|
|
{
|
|
desc: "nil pointer in slice uses zero value",
|
|
v: struct {
|
|
A []*int
|
|
}{
|
|
A: []*int{nil},
|
|
},
|
|
expected: `A = [0]`,
|
|
},
|
|
{
|
|
desc: "pointer in slice",
|
|
v: struct {
|
|
A []*int
|
|
}{
|
|
A: []*int{&someInt},
|
|
},
|
|
expected: `A = [42]`,
|
|
},
|
|
{
|
|
desc: "inline table in inline table",
|
|
v: structInline{
|
|
A: structInline{
|
|
A: structInline{
|
|
A: "hello",
|
|
},
|
|
},
|
|
},
|
|
expected: `A = {A = {A = 'hello'}}`,
|
|
},
|
|
{
|
|
desc: "empty slice in map",
|
|
v: map[string][]string{
|
|
"a": {},
|
|
},
|
|
expected: `a = []`,
|
|
},
|
|
{
|
|
desc: "map in slice",
|
|
v: map[string][]map[string]string{
|
|
"a": {{"hello": "world"}},
|
|
},
|
|
expected: `
|
|
[[a]]
|
|
hello = 'world'`,
|
|
},
|
|
{
|
|
desc: "newline in map in slice",
|
|
v: map[string][]map[string]string{
|
|
"a\n": {{"hello": "world"}},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "newline in map in slice",
|
|
v: map[string][]map[string]*customTextMarshaler{
|
|
"a": {{"hello": &customTextMarshaler{1}}},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "empty slice of empty struct",
|
|
v: struct {
|
|
A []struct{}
|
|
}{
|
|
A: []struct{}{},
|
|
},
|
|
expected: `A = []`,
|
|
},
|
|
{
|
|
desc: "nil field is ignored",
|
|
v: struct {
|
|
A interface{}
|
|
}{
|
|
A: nil,
|
|
},
|
|
expected: ``,
|
|
},
|
|
{
|
|
desc: "private fields are ignored",
|
|
v: struct {
|
|
Public string
|
|
private string
|
|
}{
|
|
Public: "shown",
|
|
private: "hidden",
|
|
},
|
|
expected: `Public = 'shown'`,
|
|
},
|
|
{
|
|
desc: "fields tagged - are ignored",
|
|
v: struct {
|
|
Public string `toml:"-"`
|
|
private string
|
|
}{
|
|
Public: "hidden",
|
|
},
|
|
expected: ``,
|
|
},
|
|
{
|
|
desc: "nil value in map is ignored",
|
|
v: map[string]interface{}{
|
|
"A": nil,
|
|
},
|
|
expected: ``,
|
|
},
|
|
{
|
|
desc: "new line in table key",
|
|
v: map[string]interface{}{
|
|
"hello\nworld": 42,
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "new line in parent of nested table key",
|
|
v: map[string]interface{}{
|
|
"hello\nworld": map[string]interface{}{
|
|
"inner": 42,
|
|
},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "new line in nested table key",
|
|
v: map[string]interface{}{
|
|
"parent": map[string]interface{}{
|
|
"in\ner": map[string]interface{}{
|
|
"foo": 42,
|
|
},
|
|
},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "invalid map key",
|
|
v: map[int]interface{}{},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "unhandled type",
|
|
v: struct {
|
|
A chan int
|
|
}{
|
|
A: make(chan int),
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "time",
|
|
v: struct {
|
|
T time.Time
|
|
}{
|
|
T: time.Time{},
|
|
},
|
|
expected: `T = '0001-01-01T00:00:00Z'`,
|
|
},
|
|
{
|
|
desc: "bool",
|
|
v: struct {
|
|
A bool
|
|
B bool
|
|
}{
|
|
A: false,
|
|
B: true,
|
|
},
|
|
expected: `
|
|
A = false
|
|
B = true`,
|
|
},
|
|
{
|
|
desc: "numbers",
|
|
v: struct {
|
|
A float32
|
|
B uint64
|
|
C uint32
|
|
D uint16
|
|
E uint8
|
|
F uint
|
|
G int64
|
|
H int32
|
|
I int16
|
|
J int8
|
|
K int
|
|
L float64
|
|
}{
|
|
A: 1.1,
|
|
B: 42,
|
|
C: 42,
|
|
D: 42,
|
|
E: 42,
|
|
F: 42,
|
|
G: 42,
|
|
H: 42,
|
|
I: 42,
|
|
J: 42,
|
|
K: 42,
|
|
L: 2.2,
|
|
},
|
|
expected: `
|
|
A = 1.1
|
|
B = 42
|
|
C = 42
|
|
D = 42
|
|
E = 42
|
|
F = 42
|
|
G = 42
|
|
H = 42
|
|
I = 42
|
|
J = 42
|
|
K = 42
|
|
L = 2.2`,
|
|
},
|
|
{
|
|
desc: "comments",
|
|
v: struct {
|
|
Table comments `comment:"Before table"`
|
|
}{
|
|
Table: comments{
|
|
One: 1,
|
|
Two: 2,
|
|
Three: []int{1, 2, 3},
|
|
},
|
|
},
|
|
expected: `
|
|
# Before table
|
|
[Table]
|
|
One = 1
|
|
# Before kv
|
|
Two = 2
|
|
# Before array
|
|
Three = [1, 2, 3]
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, e := range examples {
|
|
e := e
|
|
t.Run(e.desc, func(t *testing.T) {
|
|
b, err := toml.Marshal(e.v)
|
|
if e.err {
|
|
require.Error(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
equalStringsIgnoreNewlines(t, e.expected, string(b))
|
|
|
|
// make sure the output is always valid TOML
|
|
defaultMap := map[string]interface{}{}
|
|
err = toml.Unmarshal(b, &defaultMap)
|
|
require.NoError(t, err)
|
|
|
|
testWithAllFlags(t, func(t *testing.T, flags int) {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
enc := toml.NewEncoder(&buf)
|
|
setFlags(enc, flags)
|
|
|
|
err := enc.Encode(e.v)
|
|
require.NoError(t, err)
|
|
|
|
inlineMap := map[string]interface{}{}
|
|
err = toml.Unmarshal(buf.Bytes(), &inlineMap)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, defaultMap, inlineMap)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
type flagsSetters []struct {
|
|
name string
|
|
f func(enc *toml.Encoder, flag bool) *toml.Encoder
|
|
}
|
|
|
|
var allFlags = flagsSetters{
|
|
{"arrays-multiline", (*toml.Encoder).SetArraysMultiline},
|
|
{"tables-inline", (*toml.Encoder).SetTablesInline},
|
|
{"indent-tables", (*toml.Encoder).SetIndentTables},
|
|
}
|
|
|
|
func setFlags(enc *toml.Encoder, flags int) {
|
|
for i := 0; i < len(allFlags); i++ {
|
|
enabled := flags&1 > 0
|
|
allFlags[i].f(enc, enabled)
|
|
}
|
|
}
|
|
|
|
func testWithAllFlags(t *testing.T, testfn func(t *testing.T, flags int)) {
|
|
t.Helper()
|
|
testWithFlags(t, 0, allFlags, testfn)
|
|
}
|
|
|
|
func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t *testing.T, flags int)) {
|
|
t.Helper()
|
|
|
|
if len(setters) == 0 {
|
|
testfn(t, flags)
|
|
|
|
return
|
|
}
|
|
|
|
s := setters[0]
|
|
|
|
for _, enabled := range []bool{false, true} {
|
|
name := fmt.Sprintf("%s=%t", s.name, enabled)
|
|
newFlags := flags << 1
|
|
|
|
if enabled {
|
|
newFlags++
|
|
}
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
testWithFlags(t, newFlags, setters[1:], testfn)
|
|
})
|
|
}
|
|
}
|
|
|
|
func equalStringsIgnoreNewlines(t *testing.T, expected string, actual string) {
|
|
t.Helper()
|
|
cutset := "\n"
|
|
assert.Equal(t, strings.Trim(expected, cutset), strings.Trim(actual, cutset))
|
|
}
|
|
|
|
//nolint:funlen
|
|
func TestMarshalIndentTables(t *testing.T) {
|
|
examples := []struct {
|
|
desc string
|
|
v interface{}
|
|
expected string
|
|
}{
|
|
{
|
|
desc: "one kv",
|
|
v: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
expected: `foo = 'bar'`,
|
|
},
|
|
{
|
|
desc: "one level table",
|
|
v: map[string]map[string]string{
|
|
"foo": {
|
|
"one": "value1",
|
|
"two": "value2",
|
|
},
|
|
},
|
|
expected: `
|
|
[foo]
|
|
one = 'value1'
|
|
two = 'value2'
|
|
`,
|
|
},
|
|
{
|
|
desc: "two levels table",
|
|
v: map[string]interface{}{
|
|
"root": "value0",
|
|
"level1": map[string]interface{}{
|
|
"one": "value1",
|
|
"level2": map[string]interface{}{
|
|
"two": "value2",
|
|
},
|
|
},
|
|
},
|
|
expected: `
|
|
root = 'value0'
|
|
[level1]
|
|
one = 'value1'
|
|
[level1.level2]
|
|
two = 'value2'
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, e := range examples {
|
|
e := e
|
|
t.Run(e.desc, func(t *testing.T) {
|
|
var buf strings.Builder
|
|
enc := toml.NewEncoder(&buf)
|
|
enc.SetIndentTables(true)
|
|
err := enc.Encode(e.v)
|
|
require.NoError(t, err)
|
|
equalStringsIgnoreNewlines(t, e.expected, buf.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
type customTextMarshaler struct {
|
|
value int64
|
|
}
|
|
|
|
func (c *customTextMarshaler) MarshalText() ([]byte, error) {
|
|
if c.value == 1 {
|
|
return nil, fmt.Errorf("cannot represent 1 because this is a silly test")
|
|
}
|
|
return []byte(fmt.Sprintf("::%d", c.value)), nil
|
|
}
|
|
|
|
func TestMarshalTextMarshaler_NoRoot(t *testing.T) {
|
|
c := customTextMarshaler{}
|
|
_, err := toml.Marshal(&c)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestMarshalTextMarshaler_Error(t *testing.T) {
|
|
m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
|
|
_, err := toml.Marshal(m)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
|
|
type s struct {
|
|
A map[string]interface{} `inline:"true"`
|
|
}
|
|
|
|
d := s{
|
|
A: map[string]interface{}{"a": &customTextMarshaler{value: 1}},
|
|
}
|
|
|
|
_, err := toml.Marshal(d)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestMarshalTextMarshaler(t *testing.T) {
|
|
m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
|
|
r, err := toml.Marshal(m)
|
|
require.NoError(t, err)
|
|
equalStringsIgnoreNewlines(t, "a = '::2'", string(r))
|
|
}
|
|
|
|
type brokenWriter struct{}
|
|
|
|
func (b *brokenWriter) Write([]byte) (int, error) {
|
|
return 0, fmt.Errorf("dead")
|
|
}
|
|
|
|
func TestEncodeToBrokenWriter(t *testing.T) {
|
|
w := brokenWriter{}
|
|
enc := toml.NewEncoder(&w)
|
|
err := enc.Encode(map[string]string{"hello": "world"})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestEncoderSetIndentSymbol(t *testing.T) {
|
|
var w strings.Builder
|
|
enc := toml.NewEncoder(&w)
|
|
enc.SetIndentTables(true)
|
|
enc.SetIndentSymbol(">>>")
|
|
err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}})
|
|
require.NoError(t, err)
|
|
expected := `
|
|
[parent]
|
|
>>>hello = 'world'`
|
|
equalStringsIgnoreNewlines(t, expected, w.String())
|
|
}
|
|
|
|
func TestEncoderOmitempty(t *testing.T) {
|
|
type doc struct {
|
|
String string `toml:",omitempty,multiline"`
|
|
Bool bool `toml:",omitempty,multiline"`
|
|
Int int `toml:",omitempty,multiline"`
|
|
Int8 int8 `toml:",omitempty,multiline"`
|
|
Int16 int16 `toml:",omitempty,multiline"`
|
|
Int32 int32 `toml:",omitempty,multiline"`
|
|
Int64 int64 `toml:",omitempty,multiline"`
|
|
Uint uint `toml:",omitempty,multiline"`
|
|
Uint8 uint8 `toml:",omitempty,multiline"`
|
|
Uint16 uint16 `toml:",omitempty,multiline"`
|
|
Uint32 uint32 `toml:",omitempty,multiline"`
|
|
Uint64 uint64 `toml:",omitempty,multiline"`
|
|
Float32 float32 `toml:",omitempty,multiline"`
|
|
Float64 float64 `toml:",omitempty,multiline"`
|
|
MapNil map[string]string `toml:",omitempty,multiline"`
|
|
Slice []string `toml:",omitempty,multiline"`
|
|
Ptr *string `toml:",omitempty,multiline"`
|
|
Iface interface{} `toml:",omitempty,multiline"`
|
|
Struct struct{} `toml:",omitempty,multiline"`
|
|
}
|
|
|
|
d := doc{}
|
|
|
|
b, err := toml.Marshal(d)
|
|
require.NoError(t, err)
|
|
|
|
expected := `[Struct]`
|
|
|
|
equalStringsIgnoreNewlines(t, expected, string(b))
|
|
}
|
|
|
|
func TestEncoderTagFieldName(t *testing.T) {
|
|
type doc struct {
|
|
String string `toml:"hello"`
|
|
OkSym string `toml:"#"`
|
|
Bad string `toml:"\"`
|
|
}
|
|
|
|
d := doc{String: "world"}
|
|
|
|
b, err := toml.Marshal(d)
|
|
require.NoError(t, err)
|
|
|
|
expected := `
|
|
hello = 'world'
|
|
'#' = ''
|
|
Bad = ''
|
|
`
|
|
|
|
equalStringsIgnoreNewlines(t, expected, string(b))
|
|
}
|
|
|
|
func TestIssue436(t *testing.T) {
|
|
data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`)
|
|
|
|
var v interface{}
|
|
err := json.Unmarshal(data, &v)
|
|
require.NoError(t, err)
|
|
|
|
var buf bytes.Buffer
|
|
err = toml.NewEncoder(&buf).Encode(v)
|
|
require.NoError(t, err)
|
|
|
|
expected := `
|
|
[[a]]
|
|
[a.b]
|
|
c = 'd'
|
|
`
|
|
equalStringsIgnoreNewlines(t, expected, buf.String())
|
|
}
|
|
|
|
func TestIssue424(t *testing.T) {
|
|
type Message1 struct {
|
|
Text string
|
|
}
|
|
|
|
type Message2 struct {
|
|
Text string `multiline:"true"`
|
|
}
|
|
|
|
msg1 := Message1{"Hello\\World"}
|
|
msg2 := Message2{"Hello\\World"}
|
|
|
|
toml1, err := toml.Marshal(msg1)
|
|
require.NoError(t, err)
|
|
|
|
toml2, err := toml.Marshal(msg2)
|
|
require.NoError(t, err)
|
|
|
|
msg1parsed := Message1{}
|
|
err = toml.Unmarshal(toml1, &msg1parsed)
|
|
require.NoError(t, err)
|
|
require.Equal(t, msg1, msg1parsed)
|
|
|
|
msg2parsed := Message2{}
|
|
err = toml.Unmarshal(toml2, &msg2parsed)
|
|
require.NoError(t, err)
|
|
require.Equal(t, msg2, msg2parsed)
|
|
}
|
|
|
|
func TestIssue567(t *testing.T) {
|
|
var m map[string]interface{}
|
|
err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
|
|
require.NoError(t, err)
|
|
require.IsType(t, m["A"], toml.LocalTime{})
|
|
}
|
|
|
|
func TestIssue590(t *testing.T) {
|
|
type CustomType int
|
|
var cfg struct {
|
|
Option CustomType `toml:"option"`
|
|
}
|
|
err := toml.Unmarshal([]byte("option = 42"), &cfg)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestIssue571(t *testing.T) {
|
|
type Foo struct {
|
|
Float32 float32
|
|
Float64 float64
|
|
}
|
|
|
|
const closeEnough = 1e-9
|
|
|
|
foo := Foo{
|
|
Float32: 42,
|
|
Float64: 43,
|
|
}
|
|
b, err := toml.Marshal(foo)
|
|
require.NoError(t, err)
|
|
|
|
var foo2 Foo
|
|
err = toml.Unmarshal(b, &foo2)
|
|
require.NoError(t, err)
|
|
|
|
assert.InDelta(t, 42, foo2.Float32, closeEnough)
|
|
assert.InDelta(t, 43, foo2.Float64, closeEnough)
|
|
}
|
|
|
|
func TestIssue678(t *testing.T) {
|
|
type Config struct {
|
|
BigInt big.Int
|
|
}
|
|
|
|
cfg := &Config{
|
|
BigInt: *big.NewInt(123),
|
|
}
|
|
|
|
out, err := toml.Marshal(cfg)
|
|
require.NoError(t, err)
|
|
equalStringsIgnoreNewlines(t, "BigInt = '123'", string(out))
|
|
|
|
cfg2 := &Config{}
|
|
err = toml.Unmarshal(out, cfg2)
|
|
require.NoError(t, err)
|
|
require.Equal(t, cfg, cfg2)
|
|
}
|
|
|
|
func TestMarshalNestedAnonymousStructs(t *testing.T) {
|
|
type Embedded struct {
|
|
Value string `toml:"value" json:"value"`
|
|
Top struct {
|
|
Value string `toml:"value" json:"value"`
|
|
} `toml:"top" json:"top"`
|
|
}
|
|
|
|
type Named struct {
|
|
Value string `toml:"value" json:"value"`
|
|
}
|
|
|
|
var doc struct {
|
|
Embedded
|
|
Named `toml:"named" json:"named"`
|
|
Anonymous struct {
|
|
Value string `toml:"value" json:"value"`
|
|
} `toml:"anonymous" json:"anonymous"`
|
|
}
|
|
|
|
expected := `value = ''
|
|
[top]
|
|
value = ''
|
|
|
|
[named]
|
|
value = ''
|
|
|
|
[anonymous]
|
|
value = ''
|
|
|
|
`
|
|
|
|
result, err := toml.Marshal(doc)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, string(result))
|
|
}
|
|
|
|
func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
|
|
type Embedded struct {
|
|
Value string `toml:"value" json:"value"`
|
|
Top struct {
|
|
Value string `toml:"value" json:"value"`
|
|
} `toml:"top" json:"top"`
|
|
}
|
|
|
|
var doc struct {
|
|
Value string `toml:"value" json:"value"`
|
|
Embedded
|
|
}
|
|
doc.Embedded.Value = "shadowed"
|
|
doc.Value = "shadows"
|
|
|
|
expected := `value = 'shadows'
|
|
[top]
|
|
value = ''
|
|
|
|
`
|
|
|
|
result, err := toml.Marshal(doc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, string(result))
|
|
}
|
|
|
|
func ExampleMarshal() {
|
|
type MyConfig struct {
|
|
Version int
|
|
Name string
|
|
Tags []string
|
|
}
|
|
|
|
cfg := MyConfig{
|
|
Version: 2,
|
|
Name: "go-toml",
|
|
Tags: []string{"go", "toml"},
|
|
}
|
|
|
|
b, err := toml.Marshal(cfg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(string(b))
|
|
|
|
// Output:
|
|
// Version = 2
|
|
// Name = 'go-toml'
|
|
// Tags = ['go', 'toml']
|
|
}
|