Files
go-toml/marshaler_test.go
T
Thomas Pelletier 003aa0993b Fix nil pointer map values not being marshaled (#1025)
When marshaling a map with nil pointer values, the keys were being
silently dropped, breaking round-trip fidelity. For example:

    map[string]*struct{}{"foo": nil}

Would produce an empty TOML document instead of "[foo]".

This change converts nil pointer values in maps to their zero values
(consistent with how nil pointers in slices are handled), allowing the
keys to be preserved as empty tables.

Nil interface values (map[string]any{"foo": nil}) are still skipped
since there's no type information to derive a zero value.

Fixes #975

Also, pin golangci-lint version to v2.8.0 in CI and document in AGENTS.md

- Explicitly set golangci-lint version in lint.yml to ensure consistent
  behavior across CI runs
- Update AGENTS.md with instructions to use the same linter version locally

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 11:08:31 -05:00

2242 lines
46 KiB
Go

package toml_test
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"net/netip"
"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, errors.New("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) {
t.Helper()
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 interface value in map is ignored",
v: map[string]interface{}{
"A": nil,
},
expected: ``,
},
{
desc: "nil pointer to struct in map produces empty table",
v: map[string]*struct{}{
"A": nil,
},
expected: `[A]
`,
},
{
desc: "nil pointer to int in map produces zero value",
v: map[string]*int{
"A": nil,
},
expected: `A = 0
`,
},
{
desc: "nil pointer to string in map produces empty string",
v: map[string]*string{
"A": nil,
},
expected: `A = ''
`,
},
{
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))
}
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, errors.New("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, errors.New("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"`
Inline struct {
String string `toml:",omitempty,multiline"`
} `toml:",inline"`
}
d := doc{}
b, err := toml.Marshal(d)
assert.NoError(t, err)
expected := `Inline = {}
`
assert.Equal(t, expected, string(b))
}
func TestEncoderOmitzero(t *testing.T) {
type doc struct {
String string `toml:",omitzero,multiline"`
Bool bool `toml:",omitzero,multiline"`
Int int `toml:",omitzero,multiline"`
Int8 int8 `toml:",omitzero,multiline"`
Int16 int16 `toml:",omitzero,multiline"`
Int32 int32 `toml:",omitzero,multiline"`
Int64 int64 `toml:",omitzero,multiline"`
Uint uint `toml:",omitzero,multiline"`
Uint8 uint8 `toml:",omitzero,multiline"`
Uint16 uint16 `toml:",omitzero,multiline"`
Uint32 uint32 `toml:",omitzero,multiline"`
Uint64 uint64 `toml:",omitzero,multiline"`
Float32 float32 `toml:",omitzero,multiline"`
Float64 float64 `toml:",omitzero,multiline"`
MapNil map[string]string `toml:",omitzero,multiline"`
Slice []string `toml:",omitzero,multiline"`
Ptr *string `toml:",omitzero,multiline"`
Iface interface{} `toml:",omitzero,multiline"`
Struct struct{} `toml:",omitzero,multiline"`
Time time.Time `toml:",omitzero,multiline"`
IP netip.Addr `toml:",omitzero,multiline"`
Inline struct {
String string `toml:",omitzero,multiline"`
} `toml:",inline"`
}
d := doc{}
b, err := toml.Marshal(d)
assert.NoError(t, err)
expected := `Inline = {}
`
assert.Equal(t, expected, string(b))
}
func TestEncoderOmitzeroOpaqueStruct(t *testing.T) {
type doc struct {
Time time.Time `toml:",omitzero"`
IP netip.Addr `toml:",omitzero"`
}
d := doc{
Time: time.Date(2001, 2, 3, 4, 5, 6, 7, time.UTC),
IP: netip.MustParseAddr("192.168.178.35"),
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
expected := `Time = 2001-02-03T04:05:06.000000007Z
IP = '192.168.178.35'
`
assert.Equal(t, expected, string(b))
}
// customZeroType has a custom IsZero method that returns true
// when Value is less than 10.
type customZeroType struct {
Value int
}
func (c customZeroType) IsZero() bool {
return c.Value < 10
}
// customZeroPointerType has a custom IsZero method on the pointer receiver.
type customZeroPointerType struct {
Value int
}
func (c *customZeroPointerType) IsZero() bool {
return c.Value < 10
}
func TestEncoderOmitzeroCustomIsZero(t *testing.T) {
type doc struct {
Custom customZeroType `toml:",omitzero"`
Normal int `toml:",omitzero"`
}
// Custom.Value = 5, which is < 10, so custom IsZero returns true
d := doc{
Custom: customZeroType{Value: 5},
Normal: 0,
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Both fields should be omitted: Custom because custom IsZero returns true,
// Normal because its reflect zero value is true.
expected := ``
assert.Equal(t, expected, string(b))
}
func TestEncoderOmitzeroCustomIsZeroNotZero(t *testing.T) {
type doc struct {
Custom customZeroType `toml:",omitzero"`
Normal int `toml:",omitzero"`
}
// Custom.Value = 15, which is >= 10, so custom IsZero returns false
d := doc{
Custom: customZeroType{Value: 15},
Normal: 42,
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Both fields should be present
expected := `Normal = 42
[Custom]
Value = 15
`
assert.Equal(t, expected, string(b))
}
func TestEncoderOmitzeroCustomIsZeroPointerReceiver(t *testing.T) {
type doc struct {
Custom customZeroPointerType `toml:",omitzero"`
}
// Custom.Value = 5, which is < 10, so custom IsZero returns true
d := doc{
Custom: customZeroPointerType{Value: 5},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Field should be omitted because custom IsZero returns true
expected := ``
assert.Equal(t, expected, string(b))
}
func TestEncoderOmitzeroCustomIsZeroPointerReceiverNotZero(t *testing.T) {
type doc struct {
Custom customZeroPointerType `toml:",omitzero"`
}
// Custom.Value = 15, which is >= 10, so custom IsZero returns false
d := doc{
Custom: customZeroPointerType{Value: 15},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Field should be present
expected := `[Custom]
Value = 15
`
assert.Equal(t, expected, string(b))
}
// TestEncoderOmitzeroCustomIsZeroPointerReceiverAddressable tests the v.CanAddr() path
// by marshaling a pointer to a struct, which makes fields addressable.
func TestEncoderOmitzeroCustomIsZeroPointerReceiverAddressable(t *testing.T) {
type doc struct {
Custom customZeroPointerType `toml:",omitzero"`
}
// Custom.Value = 5, which is < 10, so custom IsZero returns true
d := &doc{
Custom: customZeroPointerType{Value: 5},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Field should be omitted because custom IsZero returns true
expected := ``
assert.Equal(t, expected, string(b))
}
// TestEncoderOmitzeroCustomIsZeroPointerReceiverAddressableNotZero tests the v.CanAddr() path
// when custom IsZero returns false.
func TestEncoderOmitzeroCustomIsZeroPointerReceiverAddressableNotZero(t *testing.T) {
type doc struct {
Custom customZeroPointerType `toml:",omitzero"`
}
// Custom.Value = 15, which is >= 10, so custom IsZero returns false
d := &doc{
Custom: customZeroPointerType{Value: 15},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Field should be present
expected := `[Custom]
Value = 15
`
assert.Equal(t, expected, string(b))
}
// TestEncoderOmitzeroCustomIsZeroInlineTable tests omitzero with inline tables.
func TestEncoderOmitzeroCustomIsZeroInlineTable(t *testing.T) {
type doc struct {
Custom customZeroType `toml:",omitzero,inline"`
}
// Custom.Value = 5, which is < 10, so custom IsZero returns true
d := doc{
Custom: customZeroType{Value: 5},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Field should be omitted
expected := ``
assert.Equal(t, expected, string(b))
}
// TestEncoderOmitzeroCustomIsZeroInlineTableNotZero tests omitzero with inline tables when not zero.
func TestEncoderOmitzeroCustomIsZeroInlineTableNotZero(t *testing.T) {
type doc struct {
Custom customZeroType `toml:",omitzero,inline"`
}
// Custom.Value = 15, which is >= 10, so custom IsZero returns false
d := doc{
Custom: customZeroType{Value: 15},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Field should be present as inline table
expected := `Custom = {Value = 15}
`
assert.Equal(t, expected, string(b))
}
// TestEncoderOmitzeroCustomIsZeroMixedTypes tests omitzero with a mix of custom and regular types.
func TestEncoderOmitzeroCustomIsZeroMixedTypes(t *testing.T) {
type doc struct {
Custom customZeroType `toml:",omitzero"`
Regular int `toml:",omitzero"`
NoOmit customZeroType `toml:""`
Pointer *int `toml:",omitzero"`
}
d := doc{
Custom: customZeroType{Value: 5}, // IsZero returns true
Regular: 0, // zero value
NoOmit: customZeroType{Value: 5}, // not omitted (no omitzero tag)
Pointer: nil, // nil pointer
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Custom is omitted (custom IsZero true), Regular is omitted (zero value),
// NoOmit is present (no omitzero tag), Pointer is omitted (nil)
expected := `[NoOmit]
Value = 5
`
assert.Equal(t, expected, string(b))
}
// TestEncoderOmitzeroCustomIsZeroSlice tests omitzero with slices containing custom types.
func TestEncoderOmitzeroCustomIsZeroSlice(t *testing.T) {
type doc struct {
Items []customZeroType `toml:",omitzero"`
}
// Nil slice should be omitted (IsZero returns true for nil slices)
d := doc{
Items: nil,
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
expected := ``
assert.Equal(t, expected, string(b))
// Empty but non-nil slice is NOT zero, so it's included
d2 := doc{
Items: []customZeroType{},
}
b2, err := toml.Marshal(d2)
assert.NoError(t, err)
expected2 := `Items = []
`
assert.Equal(t, expected2, string(b2))
}
// TestEncoderOmitzeroCustomIsZeroNestedStruct tests omitzero with nested structs.
func TestEncoderOmitzeroCustomIsZeroNestedStruct(t *testing.T) {
type inner struct {
Custom customZeroType `toml:",omitzero"`
Value int `toml:",omitzero"`
}
type doc struct {
Inner inner `toml:",omitzero"`
}
// Inner struct has all zero fields, but the struct itself is not zero
// (reflect.Value.IsZero checks if all fields are zero)
d := doc{
Inner: inner{
Custom: customZeroType{Value: 5}, // custom IsZero returns true
Value: 0, // zero value
},
}
b, err := toml.Marshal(d)
assert.NoError(t, err)
// Inner is present but its fields are omitted
expected := `[Inner]
`
assert.Equal(t, expected, string(b))
}
func TestEncoderTagFieldName(t *testing.T) {
type doc struct {
String string `toml:"hello"`
OkSym string `toml:"#"`
Bad string `toml:"\"` //nolint:govet
}
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)
err = encoder.Encode(config)
assert.NoError(t, err)
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)
err := encoder.Encode(config)
assert.NoError(t, err)
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"` //nolint:lll
MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"` //nolint:lll
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)"` //nolint:lll
}
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"` //nolint:lll
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))
}
// TestMarshalIssue975 tests that nil pointer values in maps are marshaled as
// empty tables, allowing round-trip marshaling to work correctly.
// See https://github.com/pelletier/go-toml/issues/975
func TestMarshalIssue975(t *testing.T) {
// Test case from the issue: map[string]*struct{}
oldMap := map[string]*struct{}{
"foo": nil,
}
doc, err := toml.Marshal(&oldMap)
assert.NoError(t, err)
assert.Equal(t, "[foo]\n", string(doc))
var newMap map[string]*struct{}
err = toml.Unmarshal(doc, &newMap)
assert.NoError(t, err)
// Verify the key is preserved after round-trip
_, exists := newMap["foo"]
assert.True(t, exists, "key 'foo' should exist after round-trip")
}