6d56ac8027
Only 3 consecutive quotation marks need to be quoted. We choose to quote all quotation marks in a sequence if there are 3 or more consecutive present. Fixes #990 --------- Co-authored-by: Thomas Pelletier <thomas@pelletier.dev>
1848 lines
37 KiB
Go
1848 lines
37 KiB
Go
package toml_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pelletier/go-toml/v2"
|
|
"github.com/pelletier/go-toml/v2/internal/assert"
|
|
)
|
|
|
|
type marshalTextKey struct {
|
|
A string
|
|
B string
|
|
}
|
|
|
|
func (k marshalTextKey) MarshalText() ([]byte, error) {
|
|
return []byte(k.A + "-" + k.B), nil
|
|
}
|
|
|
|
type marshalBadTextKey struct{}
|
|
|
|
func (k marshalBadTextKey) MarshalText() ([]byte, error) {
|
|
return nil, fmt.Errorf("error")
|
|
}
|
|
|
|
func toFloat(x interface{}) float64 {
|
|
// Shortened version of testify/toFloat
|
|
var xf float64
|
|
switch xn := x.(type) {
|
|
case float32:
|
|
xf = float64(xn)
|
|
case float64:
|
|
xf = xn
|
|
}
|
|
return xf
|
|
}
|
|
|
|
func inDelta(t *testing.T, expected, actual interface{}, delta float64) {
|
|
dt := toFloat(expected) - toFloat(actual)
|
|
assert.True(t,
|
|
dt < -delta && dt < delta,
|
|
"Difference between %v and %v is %v, but difference was %v",
|
|
expected, actual, delta, dt,
|
|
)
|
|
}
|
|
|
|
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'\n",
|
|
},
|
|
{
|
|
desc: "map with new line in key",
|
|
v: map[string]string{
|
|
"hel\nlo": "world",
|
|
},
|
|
expected: "\"hel\\nlo\" = 'world'\n",
|
|
},
|
|
{
|
|
desc: `map with " in key`,
|
|
v: map[string]string{
|
|
`hel"lo`: "world",
|
|
},
|
|
expected: "'hel\"lo' = 'world'\n",
|
|
},
|
|
{
|
|
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: `map with text key`,
|
|
v: map[marshalTextKey]string{
|
|
{A: "a", B: "1"}: "value 1",
|
|
{A: "a", B: "2"}: "value 2",
|
|
{A: "b", B: "1"}: "value 3",
|
|
},
|
|
expected: `a-1 = 'value 1'
|
|
a-2 = 'value 2'
|
|
b-1 = 'value 3'
|
|
`,
|
|
},
|
|
{
|
|
desc: `table with text key`,
|
|
v: map[marshalTextKey]map[string]string{
|
|
{A: "a", B: "1"}: {"value": "foo"},
|
|
},
|
|
expected: `[a-1]
|
|
value = 'foo'
|
|
`,
|
|
},
|
|
{
|
|
desc: `map with ptr text key`,
|
|
v: map[*marshalTextKey]string{
|
|
{A: "a", B: "1"}: "value 1",
|
|
{A: "a", B: "2"}: "value 2",
|
|
{A: "b", B: "1"}: "value 3",
|
|
},
|
|
expected: `a-1 = 'value 1'
|
|
a-2 = 'value 2'
|
|
b-1 = 'value 3'
|
|
`,
|
|
},
|
|
{
|
|
desc: `map with bad text key`,
|
|
v: map[marshalBadTextKey]string{
|
|
{}: "value 1",
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: `map with bad ptr text key`,
|
|
v: map[*marshalBadTextKey]string{
|
|
{}: "value 1",
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
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: "fixed size string array",
|
|
v: map[string][3]string{
|
|
"array": {"one", "two", "three"},
|
|
},
|
|
expected: `array = ['one', 'two', 'three']
|
|
`,
|
|
},
|
|
{
|
|
desc: "fixed size nested string arrays",
|
|
v: map[string][2][2]string{
|
|
"array": {{"one", "two"}, {"three"}},
|
|
},
|
|
expected: `array = [['one', 'two'], ['three', '']]
|
|
`,
|
|
},
|
|
{
|
|
desc: "mixed strings and fixed size nested string arrays",
|
|
v: map[string][]interface{}{
|
|
"array": {"a string", [2]string{"one", "two"}, "last"},
|
|
},
|
|
expected: `array = ['a string', ['one', 'two'], 'last']
|
|
`,
|
|
},
|
|
{
|
|
desc: "fixed size array of maps",
|
|
v: map[string][2]map[string]string{
|
|
"ftop": {
|
|
{"map1.1": "v1.1"},
|
|
{"map2.1": "v2.1"},
|
|
},
|
|
},
|
|
expected: `[[ftop]]
|
|
'map1.1' = 'v1.1'
|
|
|
|
[[ftop]]
|
|
'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\"\n",
|
|
},
|
|
{
|
|
desc: "string utf8 low 2",
|
|
v: map[string]interface{}{
|
|
"a": "'\u10A85",
|
|
},
|
|
expected: "a = \"'\u10A85\"\n",
|
|
},
|
|
{
|
|
desc: "emoji",
|
|
v: map[string]interface{}{
|
|
"a": "'😀",
|
|
},
|
|
expected: "a = \"'😀\"\n",
|
|
},
|
|
{
|
|
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: "multi-line quotation",
|
|
v: struct {
|
|
A string `toml:",multiline"`
|
|
}{
|
|
A: "hello\n\"world\"",
|
|
},
|
|
expected: `A = """
|
|
hello
|
|
"world""""
|
|
`,
|
|
},
|
|
{
|
|
desc: "multi-line triple quotation",
|
|
v: struct {
|
|
A string `toml:",multiline"`
|
|
}{
|
|
A: "hello\n\"\"\"world\"",
|
|
},
|
|
expected: `A = """
|
|
hello
|
|
\"\"\"world""""
|
|
`,
|
|
},
|
|
{
|
|
desc: "multi-line triple quotation",
|
|
v: struct {
|
|
A string `toml:",multiline"`
|
|
}{
|
|
A: "hello\n\"world\"\"\"",
|
|
},
|
|
expected: `A = """
|
|
hello
|
|
"world\"\"\""""
|
|
`,
|
|
},
|
|
{
|
|
desc: "multi-line sextuple quotation",
|
|
v: struct {
|
|
A string `toml:",multiline"`
|
|
}{
|
|
A: "hello\n\"\"\"\"\"\"world\"",
|
|
},
|
|
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"}},
|
|
},
|
|
expected: `[["a\n"]]
|
|
hello = 'world'
|
|
`,
|
|
},
|
|
{
|
|
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,
|
|
},
|
|
expected: `"hello\nworld" = 42
|
|
`,
|
|
},
|
|
{
|
|
desc: "new line in parent of nested table key",
|
|
v: map[string]interface{}{
|
|
"hello\nworld": map[string]interface{}{
|
|
"inner": 42,
|
|
},
|
|
},
|
|
expected: `["hello\nworld"]
|
|
inner = 42
|
|
`,
|
|
},
|
|
{
|
|
desc: "new line in nested table key",
|
|
v: map[string]interface{}{
|
|
"parent": map[string]interface{}{
|
|
"in\ner": map[string]interface{}{
|
|
"foo": 42,
|
|
},
|
|
},
|
|
},
|
|
expected: `[parent]
|
|
[parent."in\ner"]
|
|
foo = 42
|
|
`,
|
|
},
|
|
{
|
|
desc: "int map key",
|
|
v: map[int]interface{}{1: "a"},
|
|
expected: `1 = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "int8 map key",
|
|
v: map[int8]interface{}{1: "a"},
|
|
expected: `1 = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "int64 map key",
|
|
v: map[int64]interface{}{1: "a"},
|
|
expected: `1 = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "uint map key",
|
|
v: map[uint]interface{}{1: "a"},
|
|
expected: `1 = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "uint8 map key",
|
|
v: map[uint8]interface{}{1: "a"},
|
|
expected: `1 = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "uint64 map key",
|
|
v: map[uint64]interface{}{1: "a"},
|
|
expected: `1 = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "float32 map key",
|
|
v: map[float32]interface{}{
|
|
1.1: "a",
|
|
1.0020: "b",
|
|
},
|
|
expected: `'1.002' = 'b'
|
|
'1.1' = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "float64 map key",
|
|
v: map[float64]interface{}{
|
|
1.1: "a",
|
|
1.0020: "b",
|
|
},
|
|
expected: `'1.002' = 'b'
|
|
'1.1' = 'a'
|
|
`,
|
|
},
|
|
{
|
|
desc: "invalid map key",
|
|
v: map[struct{ int }]interface{}{{1}: "a"},
|
|
err: true,
|
|
},
|
|
{
|
|
desc: "invalid map key but empty",
|
|
v: map[struct{ int }]interface{}{},
|
|
expected: "",
|
|
},
|
|
{
|
|
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: "time nano",
|
|
v: struct {
|
|
T time.Time
|
|
}{
|
|
T: time.Date(1979, time.May, 27, 0, 32, 0, 999999000, time.UTC),
|
|
},
|
|
expected: `T = 1979-05-27T00:32:00.999999Z
|
|
`,
|
|
},
|
|
{
|
|
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 {
|
|
assert.Error(t, err)
|
|
|
|
return
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, e.expected, string(b))
|
|
|
|
// make sure the output is always valid TOML
|
|
defaultMap := map[string]interface{}{}
|
|
err = toml.Unmarshal(b, &defaultMap)
|
|
assert.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)
|
|
assert.NoError(t, err)
|
|
|
|
inlineMap := map[string]interface{}{}
|
|
err = toml.Unmarshal(buf.Bytes(), &inlineMap)
|
|
assert.NoError(t, err)
|
|
|
|
assert.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 TestMarshalFloats(t *testing.T) {
|
|
v := map[string]float32{
|
|
"nan": float32(math.NaN()),
|
|
"+inf": float32(math.Inf(1)),
|
|
"-inf": float32(math.Inf(-1)),
|
|
}
|
|
|
|
expected := `'+inf' = inf
|
|
-inf = -inf
|
|
nan = nan
|
|
`
|
|
|
|
actual, err := toml.Marshal(v)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, string(actual))
|
|
|
|
v64 := map[string]float64{
|
|
"nan": math.NaN(),
|
|
"+inf": math.Inf(1),
|
|
"-inf": math.Inf(-1),
|
|
}
|
|
|
|
actual, err = toml.Marshal(v64)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, string(actual))
|
|
}
|
|
|
|
//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)
|
|
assert.NoError(t, err)
|
|
assert.Equal(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)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestMarshalTextMarshaler_Error(t *testing.T) {
|
|
m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
|
|
_, err := toml.Marshal(m)
|
|
assert.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)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestMarshalTextMarshaler(t *testing.T) {
|
|
m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
|
|
r, err := toml.Marshal(m)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "a = '::2'\n", 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"})
|
|
assert.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"}})
|
|
assert.NoError(t, err)
|
|
expected := `[parent]
|
|
>>>hello = 'world'
|
|
`
|
|
assert.Equal(t, expected, w.String())
|
|
}
|
|
|
|
func TestEncoderSetMarshalJsonNumbers(t *testing.T) {
|
|
var w strings.Builder
|
|
enc := toml.NewEncoder(&w)
|
|
enc.SetMarshalJsonNumbers(true)
|
|
err := enc.Encode(map[string]interface{}{
|
|
"A": json.Number("1.1"),
|
|
"B": json.Number("42e-3"),
|
|
"C": json.Number("42"),
|
|
"D": json.Number("0"),
|
|
"E": json.Number("0.0"),
|
|
"F": json.Number(""),
|
|
})
|
|
assert.NoError(t, err)
|
|
expected := `A = 1.1
|
|
B = 0.042
|
|
C = 42
|
|
D = 0
|
|
E = 0.0
|
|
F = 0
|
|
`
|
|
assert.Equal(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)
|
|
assert.NoError(t, err)
|
|
|
|
expected := ``
|
|
|
|
assert.Equal(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)
|
|
assert.NoError(t, err)
|
|
|
|
expected := `hello = 'world'
|
|
'#' = ''
|
|
Bad = ''
|
|
`
|
|
|
|
assert.Equal(t, expected, string(b))
|
|
}
|
|
|
|
func TestIssue436(t *testing.T) {
|
|
data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`)
|
|
|
|
var v interface{}
|
|
err := json.Unmarshal(data, &v)
|
|
assert.NoError(t, err)
|
|
|
|
var buf bytes.Buffer
|
|
err = toml.NewEncoder(&buf).Encode(v)
|
|
assert.NoError(t, err)
|
|
|
|
expected := `[[a]]
|
|
[a.b]
|
|
c = 'd'
|
|
`
|
|
assert.Equal(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)
|
|
assert.NoError(t, err)
|
|
|
|
toml2, err := toml.Marshal(msg2)
|
|
assert.NoError(t, err)
|
|
|
|
msg1parsed := Message1{}
|
|
err = toml.Unmarshal(toml1, &msg1parsed)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, msg1, msg1parsed)
|
|
|
|
msg2parsed := Message2{}
|
|
err = toml.Unmarshal(toml2, &msg2parsed)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, msg2, msg2parsed)
|
|
}
|
|
|
|
func TestIssue567(t *testing.T) {
|
|
var m map[string]interface{}
|
|
err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t,
|
|
reflect.TypeOf(m["A"]), reflect.TypeOf(toml.LocalTime{}),
|
|
"Expected type '%v', got: %v", reflect.TypeOf(m["A"]), reflect.TypeOf(toml.LocalTime{}),
|
|
)
|
|
}
|
|
|
|
func TestIssue590(t *testing.T) {
|
|
type CustomType int
|
|
var cfg struct {
|
|
Option CustomType `toml:"option"`
|
|
}
|
|
err := toml.Unmarshal([]byte("option = 42"), &cfg)
|
|
assert.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)
|
|
assert.NoError(t, err)
|
|
|
|
var foo2 Foo
|
|
err = toml.Unmarshal(b, &foo2)
|
|
assert.NoError(t, err)
|
|
|
|
inDelta(t, 42, foo2.Float32, closeEnough)
|
|
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)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "BigInt = '123'\n", string(out))
|
|
|
|
cfg2 := &Config{}
|
|
err = toml.Unmarshal(out, cfg2)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, cfg, cfg2)
|
|
}
|
|
|
|
func TestIssue752(t *testing.T) {
|
|
type Fooer interface {
|
|
Foo() string
|
|
}
|
|
|
|
type Container struct {
|
|
Fooer
|
|
}
|
|
|
|
c := Container{}
|
|
|
|
out, err := toml.Marshal(c)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "", string(out))
|
|
}
|
|
|
|
func TestIssue768(t *testing.T) {
|
|
type cfg struct {
|
|
Name string `comment:"This is a multiline comment.\nThis is line 2."`
|
|
}
|
|
|
|
out, err := toml.Marshal(&cfg{})
|
|
assert.NoError(t, err)
|
|
|
|
expected := `# This is a multiline comment.
|
|
# This is line 2.
|
|
Name = ''
|
|
`
|
|
|
|
assert.Equal(t, expected, string(out))
|
|
}
|
|
|
|
func TestIssue786(t *testing.T) {
|
|
type Dependencies struct {
|
|
Dependencies []string `toml:"dependencies,multiline,omitempty"`
|
|
BuildDependencies []string `toml:"buildDependencies,multiline,omitempty"`
|
|
OptionalDependencies []string `toml:"optionalDependencies,multiline,omitempty"`
|
|
}
|
|
|
|
type Test struct {
|
|
Dependencies Dependencies `toml:"dependencies,omitempty"`
|
|
}
|
|
|
|
x := Test{}
|
|
b, err := toml.Marshal(x)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "", string(b))
|
|
|
|
type General struct {
|
|
From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"`
|
|
Randomize bool `toml:"randomize" json:"randomize" comment:"randomize starting time with [0,step)"`
|
|
}
|
|
|
|
type Custom struct {
|
|
Name string `toml:"name" json:"name,omitempty" comment:"names for generator, braces are expanded like in shell"`
|
|
Type string `toml:"type,omitempty" json:"type,omitempty" comment:"type of generator"`
|
|
General
|
|
}
|
|
type Config struct {
|
|
General
|
|
Custom []Custom `toml:"custom,omitempty" json:"custom,omitempty" comment:"generators with custom parameters can be specified separately"`
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
config := &Config{General: General{From: "-2d", Randomize: true}}
|
|
config.Custom = []Custom{{Name: "omit", General: General{Randomize: false}}}
|
|
config.Custom = append(config.Custom, Custom{Name: "present", General: General{From: "-2d", Randomize: true}})
|
|
encoder := toml.NewEncoder(buf)
|
|
encoder.Encode(config)
|
|
|
|
expected := `# from in graphite-web format, the local TZ is used
|
|
from = '-2d'
|
|
# randomize starting time with [0,step)
|
|
randomize = true
|
|
|
|
# generators with custom parameters can be specified separately
|
|
[[custom]]
|
|
# names for generator, braces are expanded like in shell
|
|
name = 'omit'
|
|
# randomize starting time with [0,step)
|
|
randomize = false
|
|
|
|
[[custom]]
|
|
# names for generator, braces are expanded like in shell
|
|
name = 'present'
|
|
# from in graphite-web format, the local TZ is used
|
|
from = '-2d'
|
|
# randomize starting time with [0,step)
|
|
randomize = true
|
|
`
|
|
|
|
assert.Equal(t, expected, buf.String())
|
|
}
|
|
|
|
func TestMarshalIssue888(t *testing.T) {
|
|
type Thing struct {
|
|
FieldA string `comment:"my field A"`
|
|
FieldB string `comment:"my field B"`
|
|
}
|
|
|
|
type Cfg struct {
|
|
Custom []Thing `comment:"custom config"`
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
config := Cfg{
|
|
Custom: []Thing{
|
|
{FieldA: "field a 1", FieldB: "field b 1"},
|
|
{FieldA: "field a 2", FieldB: "field b 2"},
|
|
},
|
|
}
|
|
|
|
encoder := toml.NewEncoder(buf).SetIndentTables(true)
|
|
encoder.Encode(config)
|
|
|
|
expected := `# custom config
|
|
[[Custom]]
|
|
# my field A
|
|
FieldA = 'field a 1'
|
|
# my field B
|
|
FieldB = 'field b 1'
|
|
|
|
[[Custom]]
|
|
# my field A
|
|
FieldA = 'field a 2'
|
|
# my field B
|
|
FieldB = 'field b 2'
|
|
`
|
|
|
|
assert.Equal(t, expected, buf.String())
|
|
}
|
|
|
|
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)
|
|
assert.NoError(t, err)
|
|
assert.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)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, string(result))
|
|
}
|
|
|
|
func TestMarshalNestedAnonymousStructs_PointerEmbedded(t *testing.T) {
|
|
type Embedded struct {
|
|
Value string `toml:"value" json:"value"`
|
|
Omitted string `toml:"omitted,omitempty"`
|
|
Ptr *string `toml:"ptr"`
|
|
}
|
|
|
|
type Named struct {
|
|
Value string `toml:"value" json:"value"`
|
|
}
|
|
|
|
type Doc struct {
|
|
*Embedded
|
|
*Named `toml:"named" json:"named"`
|
|
Anonymous struct {
|
|
*Embedded
|
|
Value *string `toml:"value" json:"value"`
|
|
} `toml:"anonymous,omitempty" json:"anonymous,omitempty"`
|
|
}
|
|
|
|
doc := &Doc{
|
|
Embedded: &Embedded{Value: "foo"},
|
|
}
|
|
|
|
expected := `value = 'foo'
|
|
`
|
|
|
|
result, err := toml.Marshal(doc)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, string(result))
|
|
}
|
|
|
|
func TestLocalTime(t *testing.T) {
|
|
v := map[string]toml.LocalTime{
|
|
"a": {
|
|
Hour: 1,
|
|
Minute: 2,
|
|
Second: 3,
|
|
Nanosecond: 4,
|
|
},
|
|
}
|
|
|
|
expected := `a = 01:02:03.000000004
|
|
`
|
|
|
|
out, err := toml.Marshal(v)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, string(out))
|
|
}
|
|
|
|
func TestMarshalUint64Overflow(t *testing.T) {
|
|
// The TOML spec only asserts implementation to provide support for the
|
|
// int64 range. To avoid generating TOML documents that would not be
|
|
// supported by standard-compliant parsers, uint64 > max int64 cannot be
|
|
// marshaled.
|
|
x := map[string]interface{}{
|
|
"foo": uint64(math.MaxInt64) + 1,
|
|
}
|
|
|
|
_, err := toml.Marshal(x)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestIndentWithInlineTable(t *testing.T) {
|
|
x := map[string][]map[string]string{
|
|
"one": {
|
|
{"0": "0"},
|
|
{"1": "1"},
|
|
},
|
|
}
|
|
expected := `one = [
|
|
{0 = '0'},
|
|
{1 = '1'}
|
|
]
|
|
`
|
|
var buf bytes.Buffer
|
|
enc := toml.NewEncoder(&buf)
|
|
enc.SetIndentTables(true)
|
|
enc.SetTablesInline(true)
|
|
enc.SetArraysMultiline(true)
|
|
assert.NoError(t, enc.Encode(x))
|
|
assert.Equal(t, expected, buf.String())
|
|
}
|
|
|
|
type C3 struct {
|
|
Value int `toml:",commented"`
|
|
Values []int `toml:",commented"`
|
|
}
|
|
|
|
type C2 struct {
|
|
Int int64
|
|
String string
|
|
ArrayInts []int
|
|
Structs []C3
|
|
}
|
|
|
|
type C1 struct {
|
|
Int int64 `toml:",commented"`
|
|
String string `toml:",commented"`
|
|
ArrayInts []int `toml:",commented"`
|
|
Structs []C3 `toml:",commented"`
|
|
}
|
|
|
|
type Commented struct {
|
|
Int int64 `toml:",commented"`
|
|
String string `toml:",commented"`
|
|
|
|
C1 C1
|
|
C2 C2 `toml:",commented"` // same as C1, but commented at top level
|
|
}
|
|
|
|
func TestMarshalCommented(t *testing.T) {
|
|
c := Commented{
|
|
Int: 42,
|
|
String: "root",
|
|
|
|
C1: C1{
|
|
Int: 11,
|
|
String: "C1",
|
|
ArrayInts: []int{1, 2, 3},
|
|
Structs: []C3{
|
|
{Value: 100},
|
|
{Values: []int{4, 5, 6}},
|
|
},
|
|
},
|
|
C2: C2{
|
|
Int: 22,
|
|
String: "C2",
|
|
ArrayInts: []int{1, 2, 3},
|
|
Structs: []C3{
|
|
{Value: 100},
|
|
{Values: []int{4, 5, 6}},
|
|
},
|
|
},
|
|
}
|
|
|
|
out, err := toml.Marshal(c)
|
|
assert.NoError(t, err)
|
|
|
|
expected := `# Int = 42
|
|
# String = 'root'
|
|
|
|
[C1]
|
|
# Int = 11
|
|
# String = 'C1'
|
|
# ArrayInts = [1, 2, 3]
|
|
|
|
# [[C1.Structs]]
|
|
# Value = 100
|
|
# Values = []
|
|
|
|
# [[C1.Structs]]
|
|
# Value = 0
|
|
# Values = [4, 5, 6]
|
|
|
|
# [C2]
|
|
# Int = 22
|
|
# String = 'C2'
|
|
# ArrayInts = [1, 2, 3]
|
|
|
|
# [[C2.Structs]]
|
|
# Value = 100
|
|
# Values = []
|
|
|
|
# [[C2.Structs]]
|
|
# Value = 0
|
|
# Values = [4, 5, 6]
|
|
`
|
|
|
|
assert.Equal(t, expected, string(out))
|
|
}
|
|
|
|
func TestMarshalIndentedCustomTypeArray(t *testing.T) {
|
|
c := struct {
|
|
Nested struct {
|
|
NestedArray []struct {
|
|
Value int
|
|
}
|
|
}
|
|
}{
|
|
Nested: struct {
|
|
NestedArray []struct {
|
|
Value int
|
|
}
|
|
}{
|
|
NestedArray: []struct {
|
|
Value int
|
|
}{
|
|
{Value: 1},
|
|
{Value: 2},
|
|
},
|
|
},
|
|
}
|
|
|
|
expected := `[Nested]
|
|
[[Nested.NestedArray]]
|
|
Value = 1
|
|
|
|
[[Nested.NestedArray]]
|
|
Value = 2
|
|
`
|
|
|
|
var buf bytes.Buffer
|
|
enc := toml.NewEncoder(&buf)
|
|
enc.SetIndentTables(true)
|
|
assert.NoError(t, enc.Encode(c))
|
|
assert.Equal(t, expected, buf.String())
|
|
}
|
|
|
|
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']
|
|
}
|
|
|
|
// Example that uses the 'commented' field tag option to generate an example
|
|
// configuration file that has commented out sections (example from
|
|
// go-graphite/graphite-clickhouse).
|
|
func ExampleMarshal_commented() {
|
|
type Common struct {
|
|
Listen string `toml:"listen" comment:"general listener"`
|
|
PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`
|
|
MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"`
|
|
MemoryReturnInterval time.Duration `toml:"memory-return-interval" comment:"daemon will return the freed memory to the OS when it>0"`
|
|
}
|
|
|
|
type Costs struct {
|
|
Cost *int `toml:"cost" comment:"default cost (for wildcarded equivalence or matched with regex, or if no value cost set)"`
|
|
ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equivalence without wildcards) (additional tuning, usually not needed)"`
|
|
}
|
|
|
|
type ClickHouse struct {
|
|
URL string `toml:"url" comment:"default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params"`
|
|
|
|
RenderMaxQueries int `toml:"render-max-queries" comment:"Max queries to render queries"`
|
|
RenderConcurrentQueries int `toml:"render-concurrent-queries" comment:"Concurrent queries to render queries"`
|
|
TaggedCosts map[string]*Costs `toml:"tagged-costs,commented"`
|
|
TreeTable string `toml:"tree-table,commented"`
|
|
ReverseTreeTable string `toml:"reverse-tree-table,commented"`
|
|
DateTreeTable string `toml:"date-tree-table,commented"`
|
|
DateTreeTableVersion int `toml:"date-tree-table-version,commented"`
|
|
TreeTimeout time.Duration `toml:"tree-timeout,commented"`
|
|
TagTable string `toml:"tag-table,commented"`
|
|
ExtraPrefix string `toml:"extra-prefix" comment:"add extra prefix (directory in graphite) for all metrics, w/o trailing dot"`
|
|
ConnectTimeout time.Duration `toml:"connect-timeout" comment:"TCP connection timeout"`
|
|
DataTableLegacy string `toml:"data-table,commented"`
|
|
RollupConfLegacy string `toml:"rollup-conf,commented"`
|
|
MaxDataPoints int `toml:"max-data-points" comment:"max points per metric when internal-aggregation=true"`
|
|
InternalAggregation bool `toml:"internal-aggregation" comment:"ClickHouse-side aggregation, see doc/aggregation.md"`
|
|
}
|
|
|
|
type Tags struct {
|
|
Rules string `toml:"rules"`
|
|
Date string `toml:"date"`
|
|
ExtraWhere string `toml:"extra-where"`
|
|
InputFile string `toml:"input-file"`
|
|
OutputFile string `toml:"output-file"`
|
|
}
|
|
|
|
type Config struct {
|
|
Common Common `toml:"common"`
|
|
ClickHouse ClickHouse `toml:"clickhouse"`
|
|
Tags Tags `toml:"tags,commented"`
|
|
}
|
|
|
|
cfg := &Config{
|
|
Common: Common{
|
|
Listen: ":9090",
|
|
PprofListen: "",
|
|
MaxMetricsPerTarget: 15000, // This is arbitrary value to protect CH from overload
|
|
MemoryReturnInterval: 0,
|
|
},
|
|
ClickHouse: ClickHouse{
|
|
URL: "http://localhost:8123?cancel_http_readonly_queries_on_client_close=1",
|
|
ExtraPrefix: "",
|
|
ConnectTimeout: time.Second,
|
|
DataTableLegacy: "",
|
|
RollupConfLegacy: "auto",
|
|
MaxDataPoints: 1048576,
|
|
InternalAggregation: true,
|
|
},
|
|
Tags: Tags{},
|
|
}
|
|
|
|
out, err := toml.Marshal(cfg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = toml.Unmarshal(out, &cfg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Println(string(out))
|
|
|
|
// Output:
|
|
// [common]
|
|
// # general listener
|
|
// listen = ':9090'
|
|
// # listener to serve /debug/pprof requests. '-pprof' argument overrides it
|
|
// pprof-listen = ''
|
|
// # limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited
|
|
// max-metrics-per-target = 15000
|
|
// # daemon will return the freed memory to the OS when it>0
|
|
// memory-return-interval = 0
|
|
//
|
|
// [clickhouse]
|
|
// # default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params
|
|
// url = 'http://localhost:8123?cancel_http_readonly_queries_on_client_close=1'
|
|
// # Max queries to render queries
|
|
// render-max-queries = 0
|
|
// # Concurrent queries to render queries
|
|
// render-concurrent-queries = 0
|
|
// # tree-table = ''
|
|
// # reverse-tree-table = ''
|
|
// # date-tree-table = ''
|
|
// # date-tree-table-version = 0
|
|
// # tree-timeout = 0
|
|
// # tag-table = ''
|
|
// # add extra prefix (directory in graphite) for all metrics, w/o trailing dot
|
|
// extra-prefix = ''
|
|
// # TCP connection timeout
|
|
// connect-timeout = 1000000000
|
|
// # data-table = ''
|
|
// # rollup-conf = 'auto'
|
|
// # max points per metric when internal-aggregation=true
|
|
// max-data-points = 1048576
|
|
// # ClickHouse-side aggregation, see doc/aggregation.md
|
|
// internal-aggregation = true
|
|
//
|
|
// # [tags]
|
|
// # rules = ''
|
|
// # date = ''
|
|
// # extra-where = ''
|
|
// # input-file = ''
|
|
// # output-file = ''
|
|
}
|
|
|
|
func TestReadmeComments(t *testing.T) {
|
|
type TLS struct {
|
|
Cipher string `toml:"cipher"`
|
|
Version string `toml:"version"`
|
|
}
|
|
type Config struct {
|
|
Host string `toml:"host" comment:"Host IP to connect to."`
|
|
Port int `toml:"port" comment:"Port of the remote server."`
|
|
Tls TLS `toml:"TLS,commented" comment:"Encryption parameters (optional)"`
|
|
}
|
|
example := Config{
|
|
Host: "127.0.0.1",
|
|
Port: 4242,
|
|
Tls: TLS{
|
|
Cipher: "AEAD-AES128-GCM-SHA256",
|
|
Version: "TLS 1.3",
|
|
},
|
|
}
|
|
out, err := toml.Marshal(example)
|
|
assert.NoError(t, err)
|
|
|
|
expected := `# Host IP to connect to.
|
|
host = '127.0.0.1'
|
|
# Port of the remote server.
|
|
port = 4242
|
|
|
|
# Encryption parameters (optional)
|
|
# [TLS]
|
|
# cipher = 'AEAD-AES128-GCM-SHA256'
|
|
# version = 'TLS 1.3'
|
|
`
|
|
assert.Equal(t, expected, string(out))
|
|
}
|