1 package toml_test
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "math"
8 "math/big"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/pelletier/go-toml/v2"
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/require"
16 )
17
18 type marshalTextKey struct {
19 A string
20 B string
21 }
22
23 func (k marshalTextKey) MarshalText() ([]byte, error) {
24 return []byte(k.A + "-" + k.B), nil
25 }
26
27 type marshalBadTextKey struct{}
28
29 func (k marshalBadTextKey) MarshalText() ([]byte, error) {
30 return nil, fmt.Errorf("error")
31 }
32
33 func TestMarshal(t *testing.T) {
34 someInt := 42
35
36 type structInline struct {
37 A interface{} `toml:",inline"`
38 }
39
40 type comments struct {
41 One int
42 Two int `comment:"Before kv"`
43 Three []int `comment:"Before array"`
44 }
45
46 examples := []struct {
47 desc string
48 v interface{}
49 expected string
50 err bool
51 }{
52 {
53 desc: "simple map and string",
54 v: map[string]string{
55 "hello": "world",
56 },
57 expected: "hello = 'world'\n",
58 },
59 {
60 desc: "map with new line in key",
61 v: map[string]string{
62 "hel\nlo": "world",
63 },
64 expected: "\"hel\\nlo\" = 'world'\n",
65 },
66 {
67 desc: `map with " in key`,
68 v: map[string]string{
69 `hel"lo`: "world",
70 },
71 expected: "'hel\"lo' = 'world'\n",
72 },
73 {
74 desc: "map in map and string",
75 v: map[string]map[string]string{
76 "table": {
77 "hello": "world",
78 },
79 },
80 expected: `[table]
81 hello = 'world'
82 `,
83 },
84 {
85 desc: "map in map in map and string",
86 v: map[string]map[string]map[string]string{
87 "this": {
88 "is": {
89 "a": "test",
90 },
91 },
92 },
93 expected: `[this]
94 [this.is]
95 a = 'test'
96 `,
97 },
98 {
99 desc: "map in map in map and string with values",
100 v: map[string]interface{}{
101 "this": map[string]interface{}{
102 "is": map[string]string{
103 "a": "test",
104 },
105 "also": "that",
106 },
107 },
108 expected: `[this]
109 also = 'that'
110
111 [this.is]
112 a = 'test'
113 `,
114 },
115 {
116 desc: `map with text key`,
117 v: map[marshalTextKey]string{
118 {A: "a", B: "1"}: "value 1",
119 {A: "a", B: "2"}: "value 2",
120 {A: "b", B: "1"}: "value 3",
121 },
122 expected: `a-1 = 'value 1'
123 a-2 = 'value 2'
124 b-1 = 'value 3'
125 `,
126 },
127 {
128 desc: `table with text key`,
129 v: map[marshalTextKey]map[string]string{
130 {A: "a", B: "1"}: {"value": "foo"},
131 },
132 expected: `[a-1]
133 value = 'foo'
134 `,
135 },
136 {
137 desc: `map with ptr text key`,
138 v: map[*marshalTextKey]string{
139 {A: "a", B: "1"}: "value 1",
140 {A: "a", B: "2"}: "value 2",
141 {A: "b", B: "1"}: "value 3",
142 },
143 expected: `a-1 = 'value 1'
144 a-2 = 'value 2'
145 b-1 = 'value 3'
146 `,
147 },
148 {
149 desc: `map with bad text key`,
150 v: map[marshalBadTextKey]string{
151 {}: "value 1",
152 },
153 err: true,
154 },
155 {
156 desc: `map with bad ptr text key`,
157 v: map[*marshalBadTextKey]string{
158 {}: "value 1",
159 },
160 err: true,
161 },
162 {
163 desc: "simple string array",
164 v: map[string][]string{
165 "array": {"one", "two", "three"},
166 },
167 expected: `array = ['one', 'two', 'three']
168 `,
169 },
170 {
171 desc: "empty string array",
172 v: map[string][]string{},
173 expected: ``,
174 },
175 {
176 desc: "map",
177 v: map[string][]string{},
178 expected: ``,
179 },
180 {
181 desc: "nested string arrays",
182 v: map[string][][]string{
183 "array": {{"one", "two"}, {"three"}},
184 },
185 expected: `array = [['one', 'two'], ['three']]
186 `,
187 },
188 {
189 desc: "mixed strings and nested string arrays",
190 v: map[string][]interface{}{
191 "array": {"a string", []string{"one", "two"}, "last"},
192 },
193 expected: `array = ['a string', ['one', 'two'], 'last']
194 `,
195 },
196 {
197 desc: "array of maps",
198 v: map[string][]map[string]string{
199 "top": {
200 {"map1.1": "v1.1"},
201 {"map2.1": "v2.1"},
202 },
203 },
204 expected: `[[top]]
205 'map1.1' = 'v1.1'
206
207 [[top]]
208 'map2.1' = 'v2.1'
209 `,
210 },
211 {
212 desc: "fixed size string array",
213 v: map[string][3]string{
214 "array": {"one", "two", "three"},
215 },
216 expected: `array = ['one', 'two', 'three']
217 `,
218 },
219 {
220 desc: "fixed size nested string arrays",
221 v: map[string][2][2]string{
222 "array": {{"one", "two"}, {"three"}},
223 },
224 expected: `array = [['one', 'two'], ['three', '']]
225 `,
226 },
227 {
228 desc: "mixed strings and fixed size nested string arrays",
229 v: map[string][]interface{}{
230 "array": {"a string", [2]string{"one", "two"}, "last"},
231 },
232 expected: `array = ['a string', ['one', 'two'], 'last']
233 `,
234 },
235 {
236 desc: "fixed size array of maps",
237 v: map[string][2]map[string]string{
238 "ftop": {
239 {"map1.1": "v1.1"},
240 {"map2.1": "v2.1"},
241 },
242 },
243 expected: `[[ftop]]
244 'map1.1' = 'v1.1'
245
246 [[ftop]]
247 'map2.1' = 'v2.1'
248 `,
249 },
250 {
251 desc: "map with two keys",
252 v: map[string]string{
253 "key1": "value1",
254 "key2": "value2",
255 },
256 expected: `key1 = 'value1'
257 key2 = 'value2'
258 `,
259 },
260 {
261 desc: "simple struct",
262 v: struct {
263 A string
264 }{
265 A: "foo",
266 },
267 expected: `A = 'foo'
268 `,
269 },
270 {
271 desc: "one level of structs within structs",
272 v: struct {
273 A interface{}
274 }{
275 A: struct {
276 K1 string
277 K2 string
278 }{
279 K1: "v1",
280 K2: "v2",
281 },
282 },
283 expected: `[A]
284 K1 = 'v1'
285 K2 = 'v2'
286 `,
287 },
288 {
289 desc: "structs in array with interfaces",
290 v: map[string]interface{}{
291 "root": map[string]interface{}{
292 "nested": []interface{}{
293 map[string]interface{}{"name": "Bob"},
294 map[string]interface{}{"name": "Alice"},
295 },
296 },
297 },
298 expected: `[root]
299 [[root.nested]]
300 name = 'Bob'
301
302 [[root.nested]]
303 name = 'Alice'
304 `,
305 },
306 {
307 desc: "string escapes",
308 v: map[string]interface{}{
309 "a": "'\b\f\r\t\"\\",
310 },
311 expected: `a = "'\b\f\r\t\"\\"
312 `,
313 },
314 {
315 desc: "string utf8 low",
316 v: map[string]interface{}{
317 "a": "'Ę",
318 },
319 expected: `a = "'Ę"
320 `,
321 },
322 {
323 desc: "string utf8 low 2",
324 v: map[string]interface{}{
325 "a": "'\u10A85",
326 },
327 expected: "a = \"'\u10A85\"\n",
328 },
329 {
330 desc: "string utf8 low 2",
331 v: map[string]interface{}{
332 "a": "'\u10A85",
333 },
334 expected: "a = \"'\u10A85\"\n",
335 },
336 {
337 desc: "emoji",
338 v: map[string]interface{}{
339 "a": "'😀",
340 },
341 expected: "a = \"'😀\"\n",
342 },
343 {
344 desc: "control char",
345 v: map[string]interface{}{
346 "a": "'\u001A",
347 },
348 expected: `a = "'\u001A"
349 `,
350 },
351 {
352 desc: "multi-line string",
353 v: map[string]interface{}{
354 "a": "hello\nworld",
355 },
356 expected: `a = "hello\nworld"
357 `,
358 },
359 {
360 desc: "multi-line forced",
361 v: struct {
362 A string `toml:",multiline"`
363 }{
364 A: "hello\nworld",
365 },
366 expected: `A = """
367 hello
368 world"""
369 `,
370 },
371 {
372 desc: "inline field",
373 v: struct {
374 A map[string]string `toml:",inline"`
375 B map[string]string
376 }{
377 A: map[string]string{
378 "isinline": "yes",
379 },
380 B: map[string]string{
381 "isinline": "no",
382 },
383 },
384 expected: `A = {isinline = 'yes'}
385
386 [B]
387 isinline = 'no'
388 `,
389 },
390 {
391 desc: "mutiline array int",
392 v: struct {
393 A []int `toml:",multiline"`
394 B []int
395 }{
396 A: []int{1, 2, 3, 4},
397 B: []int{1, 2, 3, 4},
398 },
399 expected: `A = [
400 1,
401 2,
402 3,
403 4
404 ]
405 B = [1, 2, 3, 4]
406 `,
407 },
408 {
409 desc: "mutiline array in array",
410 v: struct {
411 A [][]int `toml:",multiline"`
412 }{
413 A: [][]int{{1, 2}, {3, 4}},
414 },
415 expected: `A = [
416 [1, 2],
417 [3, 4]
418 ]
419 `,
420 },
421 {
422 desc: "nil interface not supported at root",
423 v: nil,
424 err: true,
425 },
426 {
427 desc: "nil interface not supported in slice",
428 v: map[string]interface{}{
429 "a": []interface{}{"a", nil, 2},
430 },
431 err: true,
432 },
433 {
434 desc: "nil pointer in slice uses zero value",
435 v: struct {
436 A []*int
437 }{
438 A: []*int{nil},
439 },
440 expected: `A = [0]
441 `,
442 },
443 {
444 desc: "nil pointer in slice uses zero value",
445 v: struct {
446 A []*int
447 }{
448 A: []*int{nil},
449 },
450 expected: `A = [0]
451 `,
452 },
453 {
454 desc: "pointer in slice",
455 v: struct {
456 A []*int
457 }{
458 A: []*int{&someInt},
459 },
460 expected: `A = [42]
461 `,
462 },
463 {
464 desc: "inline table in inline table",
465 v: structInline{
466 A: structInline{
467 A: structInline{
468 A: "hello",
469 },
470 },
471 },
472 expected: `A = {A = {A = 'hello'}}
473 `,
474 },
475 {
476 desc: "empty slice in map",
477 v: map[string][]string{
478 "a": {},
479 },
480 expected: `a = []
481 `,
482 },
483 {
484 desc: "map in slice",
485 v: map[string][]map[string]string{
486 "a": {{"hello": "world"}},
487 },
488 expected: `[[a]]
489 hello = 'world'
490 `,
491 },
492 {
493 desc: "newline in map in slice",
494 v: map[string][]map[string]string{
495 "a\n": {{"hello": "world"}},
496 },
497 expected: `[["a\n"]]
498 hello = 'world'
499 `,
500 },
501 {
502 desc: "newline in map in slice",
503 v: map[string][]map[string]*customTextMarshaler{
504 "a": {{"hello": &customTextMarshaler{1}}},
505 },
506 err: true,
507 },
508 {
509 desc: "empty slice of empty struct",
510 v: struct {
511 A []struct{}
512 }{
513 A: []struct{}{},
514 },
515 expected: `A = []
516 `,
517 },
518 {
519 desc: "nil field is ignored",
520 v: struct {
521 A interface{}
522 }{
523 A: nil,
524 },
525 expected: ``,
526 },
527 {
528 desc: "private fields are ignored",
529 v: struct {
530 Public string
531 private string
532 }{
533 Public: "shown",
534 private: "hidden",
535 },
536 expected: `Public = 'shown'
537 `,
538 },
539 {
540 desc: "fields tagged - are ignored",
541 v: struct {
542 Public string `toml:"-"`
543 private string
544 }{
545 Public: "hidden",
546 },
547 expected: ``,
548 },
549 {
550 desc: "nil value in map is ignored",
551 v: map[string]interface{}{
552 "A": nil,
553 },
554 expected: ``,
555 },
556 {
557 desc: "new line in table key",
558 v: map[string]interface{}{
559 "hello\nworld": 42,
560 },
561 expected: `"hello\nworld" = 42
562 `,
563 },
564 {
565 desc: "new line in parent of nested table key",
566 v: map[string]interface{}{
567 "hello\nworld": map[string]interface{}{
568 "inner": 42,
569 },
570 },
571 expected: `["hello\nworld"]
572 inner = 42
573 `,
574 },
575 {
576 desc: "new line in nested table key",
577 v: map[string]interface{}{
578 "parent": map[string]interface{}{
579 "in\ner": map[string]interface{}{
580 "foo": 42,
581 },
582 },
583 },
584 expected: `[parent]
585 [parent."in\ner"]
586 foo = 42
587 `,
588 },
589 {
590 desc: "invalid map key",
591 v: map[int]interface{}{1: "a"},
592 err: true,
593 },
594 {
595 desc: "invalid map key but empty",
596 v: map[int]interface{}{},
597 expected: "",
598 },
599 {
600 desc: "unhandled type",
601 v: struct {
602 A chan int
603 }{
604 A: make(chan int),
605 },
606 err: true,
607 },
608 {
609 desc: "time",
610 v: struct {
611 T time.Time
612 }{
613 T: time.Time{},
614 },
615 expected: `T = 0001-01-01T00:00:00Z
616 `,
617 },
618 {
619 desc: "time nano",
620 v: struct {
621 T time.Time
622 }{
623 T: time.Date(1979, time.May, 27, 0, 32, 0, 999999000, time.UTC),
624 },
625 expected: `T = 1979-05-27T00:32:00.999999Z
626 `,
627 },
628 {
629 desc: "bool",
630 v: struct {
631 A bool
632 B bool
633 }{
634 A: false,
635 B: true,
636 },
637 expected: `A = false
638 B = true
639 `,
640 },
641 {
642 desc: "numbers",
643 v: struct {
644 A float32
645 B uint64
646 C uint32
647 D uint16
648 E uint8
649 F uint
650 G int64
651 H int32
652 I int16
653 J int8
654 K int
655 L float64
656 }{
657 A: 1.1,
658 B: 42,
659 C: 42,
660 D: 42,
661 E: 42,
662 F: 42,
663 G: 42,
664 H: 42,
665 I: 42,
666 J: 42,
667 K: 42,
668 L: 2.2,
669 },
670 expected: `A = 1.1
671 B = 42
672 C = 42
673 D = 42
674 E = 42
675 F = 42
676 G = 42
677 H = 42
678 I = 42
679 J = 42
680 K = 42
681 L = 2.2
682 `,
683 },
684 {
685 desc: "comments",
686 v: struct {
687 Table comments `comment:"Before table"`
688 }{
689 Table: comments{
690 One: 1,
691 Two: 2,
692 Three: []int{1, 2, 3},
693 },
694 },
695 expected: `# Before table
696 [Table]
697 One = 1
698 # Before kv
699 Two = 2
700 # Before array
701 Three = [1, 2, 3]
702 `,
703 },
704 }
705
706 for _, e := range examples {
707 e := e
708 t.Run(e.desc, func(t *testing.T) {
709 b, err := toml.Marshal(e.v)
710 if e.err {
711 require.Error(t, err)
712
713 return
714 }
715
716 require.NoError(t, err)
717 assert.Equal(t, e.expected, string(b))
718
719
720 defaultMap := map[string]interface{}{}
721 err = toml.Unmarshal(b, &defaultMap)
722 require.NoError(t, err)
723
724 testWithAllFlags(t, func(t *testing.T, flags int) {
725 t.Helper()
726
727 var buf bytes.Buffer
728 enc := toml.NewEncoder(&buf)
729 setFlags(enc, flags)
730
731 err := enc.Encode(e.v)
732 require.NoError(t, err)
733
734 inlineMap := map[string]interface{}{}
735 err = toml.Unmarshal(buf.Bytes(), &inlineMap)
736 require.NoError(t, err)
737
738 require.Equal(t, defaultMap, inlineMap)
739 })
740 })
741 }
742 }
743
744 type flagsSetters []struct {
745 name string
746 f func(enc *toml.Encoder, flag bool) *toml.Encoder
747 }
748
749 var allFlags = flagsSetters{
750 {"arrays-multiline", (*toml.Encoder).SetArraysMultiline},
751 {"tables-inline", (*toml.Encoder).SetTablesInline},
752 {"indent-tables", (*toml.Encoder).SetIndentTables},
753 }
754
755 func setFlags(enc *toml.Encoder, flags int) {
756 for i := 0; i < len(allFlags); i++ {
757 enabled := flags&1 > 0
758 allFlags[i].f(enc, enabled)
759 }
760 }
761
762 func testWithAllFlags(t *testing.T, testfn func(t *testing.T, flags int)) {
763 t.Helper()
764 testWithFlags(t, 0, allFlags, testfn)
765 }
766
767 func testWithFlags(t *testing.T, flags int, setters flagsSetters, testfn func(t *testing.T, flags int)) {
768 t.Helper()
769
770 if len(setters) == 0 {
771 testfn(t, flags)
772
773 return
774 }
775
776 s := setters[0]
777
778 for _, enabled := range []bool{false, true} {
779 name := fmt.Sprintf("%s=%t", s.name, enabled)
780 newFlags := flags << 1
781
782 if enabled {
783 newFlags++
784 }
785
786 t.Run(name, func(t *testing.T) {
787 testWithFlags(t, newFlags, setters[1:], testfn)
788 })
789 }
790 }
791
792 func TestMarshalFloats(t *testing.T) {
793 v := map[string]float32{
794 "nan": float32(math.NaN()),
795 "+inf": float32(math.Inf(1)),
796 "-inf": float32(math.Inf(-1)),
797 }
798
799 expected := `'+inf' = inf
800 -inf = -inf
801 nan = nan
802 `
803
804 actual, err := toml.Marshal(v)
805 require.NoError(t, err)
806 require.Equal(t, expected, string(actual))
807
808 v64 := map[string]float64{
809 "nan": math.NaN(),
810 "+inf": math.Inf(1),
811 "-inf": math.Inf(-1),
812 }
813
814 actual, err = toml.Marshal(v64)
815 require.NoError(t, err)
816 require.Equal(t, expected, string(actual))
817 }
818
819
820 func TestMarshalIndentTables(t *testing.T) {
821 examples := []struct {
822 desc string
823 v interface{}
824 expected string
825 }{
826 {
827 desc: "one kv",
828 v: map[string]interface{}{
829 "foo": "bar",
830 },
831 expected: `foo = 'bar'
832 `,
833 },
834 {
835 desc: "one level table",
836 v: map[string]map[string]string{
837 "foo": {
838 "one": "value1",
839 "two": "value2",
840 },
841 },
842 expected: `[foo]
843 one = 'value1'
844 two = 'value2'
845 `,
846 },
847 {
848 desc: "two levels table",
849 v: map[string]interface{}{
850 "root": "value0",
851 "level1": map[string]interface{}{
852 "one": "value1",
853 "level2": map[string]interface{}{
854 "two": "value2",
855 },
856 },
857 },
858 expected: `root = 'value0'
859
860 [level1]
861 one = 'value1'
862
863 [level1.level2]
864 two = 'value2'
865 `,
866 },
867 }
868
869 for _, e := range examples {
870 e := e
871 t.Run(e.desc, func(t *testing.T) {
872 var buf strings.Builder
873 enc := toml.NewEncoder(&buf)
874 enc.SetIndentTables(true)
875 err := enc.Encode(e.v)
876 require.NoError(t, err)
877 assert.Equal(t, e.expected, buf.String())
878 })
879 }
880 }
881
882 type customTextMarshaler struct {
883 value int64
884 }
885
886 func (c *customTextMarshaler) MarshalText() ([]byte, error) {
887 if c.value == 1 {
888 return nil, fmt.Errorf("cannot represent 1 because this is a silly test")
889 }
890 return []byte(fmt.Sprintf("::%d", c.value)), nil
891 }
892
893 func TestMarshalTextMarshaler_NoRoot(t *testing.T) {
894 c := customTextMarshaler{}
895 _, err := toml.Marshal(&c)
896 require.Error(t, err)
897 }
898
899 func TestMarshalTextMarshaler_Error(t *testing.T) {
900 m := map[string]interface{}{"a": &customTextMarshaler{value: 1}}
901 _, err := toml.Marshal(m)
902 require.Error(t, err)
903 }
904
905 func TestMarshalTextMarshaler_ErrorInline(t *testing.T) {
906 type s struct {
907 A map[string]interface{} `inline:"true"`
908 }
909
910 d := s{
911 A: map[string]interface{}{"a": &customTextMarshaler{value: 1}},
912 }
913
914 _, err := toml.Marshal(d)
915 require.Error(t, err)
916 }
917
918 func TestMarshalTextMarshaler(t *testing.T) {
919 m := map[string]interface{}{"a": &customTextMarshaler{value: 2}}
920 r, err := toml.Marshal(m)
921 require.NoError(t, err)
922 assert.Equal(t, "a = '::2'\n", string(r))
923 }
924
925 type brokenWriter struct{}
926
927 func (b *brokenWriter) Write([]byte) (int, error) {
928 return 0, fmt.Errorf("dead")
929 }
930
931 func TestEncodeToBrokenWriter(t *testing.T) {
932 w := brokenWriter{}
933 enc := toml.NewEncoder(&w)
934 err := enc.Encode(map[string]string{"hello": "world"})
935 require.Error(t, err)
936 }
937
938 func TestEncoderSetIndentSymbol(t *testing.T) {
939 var w strings.Builder
940 enc := toml.NewEncoder(&w)
941 enc.SetIndentTables(true)
942 enc.SetIndentSymbol(">>>")
943 err := enc.Encode(map[string]map[string]string{"parent": {"hello": "world"}})
944 require.NoError(t, err)
945 expected := `[parent]
946 >>>hello = 'world'
947 `
948 assert.Equal(t, expected, w.String())
949 }
950
951 func TestEncoderSetMarshalJsonNumbers(t *testing.T) {
952 var w strings.Builder
953 enc := toml.NewEncoder(&w)
954 enc.SetMarshalJsonNumbers(true)
955 err := enc.Encode(map[string]interface{}{
956 "A": json.Number("1.1"),
957 "B": json.Number("42e-3"),
958 "C": json.Number("42"),
959 "D": json.Number("0"),
960 "E": json.Number("0.0"),
961 "F": json.Number(""),
962 })
963 require.NoError(t, err)
964 expected := `A = 1.1
965 B = 0.042
966 C = 42
967 D = 0
968 E = 0.0
969 F = 0
970 `
971 assert.Equal(t, expected, w.String())
972 }
973
974 func TestEncoderOmitempty(t *testing.T) {
975 type doc struct {
976 String string `toml:",omitempty,multiline"`
977 Bool bool `toml:",omitempty,multiline"`
978 Int int `toml:",omitempty,multiline"`
979 Int8 int8 `toml:",omitempty,multiline"`
980 Int16 int16 `toml:",omitempty,multiline"`
981 Int32 int32 `toml:",omitempty,multiline"`
982 Int64 int64 `toml:",omitempty,multiline"`
983 Uint uint `toml:",omitempty,multiline"`
984 Uint8 uint8 `toml:",omitempty,multiline"`
985 Uint16 uint16 `toml:",omitempty,multiline"`
986 Uint32 uint32 `toml:",omitempty,multiline"`
987 Uint64 uint64 `toml:",omitempty,multiline"`
988 Float32 float32 `toml:",omitempty,multiline"`
989 Float64 float64 `toml:",omitempty,multiline"`
990 MapNil map[string]string `toml:",omitempty,multiline"`
991 Slice []string `toml:",omitempty,multiline"`
992 Ptr *string `toml:",omitempty,multiline"`
993 Iface interface{} `toml:",omitempty,multiline"`
994 Struct struct{} `toml:",omitempty,multiline"`
995 }
996
997 d := doc{}
998
999 b, err := toml.Marshal(d)
1000 require.NoError(t, err)
1001
1002 expected := ``
1003
1004 assert.Equal(t, expected, string(b))
1005 }
1006
1007 func TestEncoderTagFieldName(t *testing.T) {
1008 type doc struct {
1009 String string `toml:"hello"`
1010 OkSym string `toml:"#"`
1011 Bad string `toml:"\"`
1012 }
1013
1014 d := doc{String: "world"}
1015
1016 b, err := toml.Marshal(d)
1017 require.NoError(t, err)
1018
1019 expected := `hello = 'world'
1020 '#' = ''
1021 Bad = ''
1022 `
1023
1024 assert.Equal(t, expected, string(b))
1025 }
1026
1027 func TestIssue436(t *testing.T) {
1028 data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`)
1029
1030 var v interface{}
1031 err := json.Unmarshal(data, &v)
1032 require.NoError(t, err)
1033
1034 var buf bytes.Buffer
1035 err = toml.NewEncoder(&buf).Encode(v)
1036 require.NoError(t, err)
1037
1038 expected := `[[a]]
1039 [a.b]
1040 c = 'd'
1041 `
1042 assert.Equal(t, expected, buf.String())
1043 }
1044
1045 func TestIssue424(t *testing.T) {
1046 type Message1 struct {
1047 Text string
1048 }
1049
1050 type Message2 struct {
1051 Text string `multiline:"true"`
1052 }
1053
1054 msg1 := Message1{"Hello\\World"}
1055 msg2 := Message2{"Hello\\World"}
1056
1057 toml1, err := toml.Marshal(msg1)
1058 require.NoError(t, err)
1059
1060 toml2, err := toml.Marshal(msg2)
1061 require.NoError(t, err)
1062
1063 msg1parsed := Message1{}
1064 err = toml.Unmarshal(toml1, &msg1parsed)
1065 require.NoError(t, err)
1066 require.Equal(t, msg1, msg1parsed)
1067
1068 msg2parsed := Message2{}
1069 err = toml.Unmarshal(toml2, &msg2parsed)
1070 require.NoError(t, err)
1071 require.Equal(t, msg2, msg2parsed)
1072 }
1073
1074 func TestIssue567(t *testing.T) {
1075 var m map[string]interface{}
1076 err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
1077 require.NoError(t, err)
1078 require.IsType(t, m["A"], toml.LocalTime{})
1079 }
1080
1081 func TestIssue590(t *testing.T) {
1082 type CustomType int
1083 var cfg struct {
1084 Option CustomType `toml:"option"`
1085 }
1086 err := toml.Unmarshal([]byte("option = 42"), &cfg)
1087 require.NoError(t, err)
1088 }
1089
1090 func TestIssue571(t *testing.T) {
1091 type Foo struct {
1092 Float32 float32
1093 Float64 float64
1094 }
1095
1096 const closeEnough = 1e-9
1097
1098 foo := Foo{
1099 Float32: 42,
1100 Float64: 43,
1101 }
1102 b, err := toml.Marshal(foo)
1103 require.NoError(t, err)
1104
1105 var foo2 Foo
1106 err = toml.Unmarshal(b, &foo2)
1107 require.NoError(t, err)
1108
1109 assert.InDelta(t, 42, foo2.Float32, closeEnough)
1110 assert.InDelta(t, 43, foo2.Float64, closeEnough)
1111 }
1112
1113 func TestIssue678(t *testing.T) {
1114 type Config struct {
1115 BigInt big.Int
1116 }
1117
1118 cfg := &Config{
1119 BigInt: *big.NewInt(123),
1120 }
1121
1122 out, err := toml.Marshal(cfg)
1123 require.NoError(t, err)
1124 assert.Equal(t, "BigInt = '123'\n", string(out))
1125
1126 cfg2 := &Config{}
1127 err = toml.Unmarshal(out, cfg2)
1128 require.NoError(t, err)
1129 require.Equal(t, cfg, cfg2)
1130 }
1131
1132 func TestIssue752(t *testing.T) {
1133 type Fooer interface {
1134 Foo() string
1135 }
1136
1137 type Container struct {
1138 Fooer
1139 }
1140
1141 c := Container{}
1142
1143 out, err := toml.Marshal(c)
1144 require.NoError(t, err)
1145 require.Equal(t, "", string(out))
1146 }
1147
1148 func TestIssue768(t *testing.T) {
1149 type cfg struct {
1150 Name string `comment:"This is a multiline comment.\nThis is line 2."`
1151 }
1152
1153 out, err := toml.Marshal(&cfg{})
1154 require.NoError(t, err)
1155
1156 expected := `# This is a multiline comment.
1157 # This is line 2.
1158 Name = ''
1159 `
1160
1161 require.Equal(t, expected, string(out))
1162 }
1163
1164 func TestIssue786(t *testing.T) {
1165 type Dependencies struct {
1166 Dependencies []string `toml:"dependencies,multiline,omitempty"`
1167 BuildDependencies []string `toml:"buildDependencies,multiline,omitempty"`
1168 OptionalDependencies []string `toml:"optionalDependencies,multiline,omitempty"`
1169 }
1170
1171 type Test struct {
1172 Dependencies Dependencies `toml:"dependencies,omitempty"`
1173 }
1174
1175 x := Test{}
1176 b, err := toml.Marshal(x)
1177 require.NoError(t, err)
1178
1179 require.Equal(t, "", string(b))
1180
1181 type General struct {
1182 From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"`
1183 Randomize bool `toml:"randomize" json:"randomize" comment:"randomize starting time with [0,step)"`
1184 }
1185
1186 type Custom struct {
1187 Name string `toml:"name" json:"name,omitempty" comment:"names for generator, braces are expanded like in shell"`
1188 Type string `toml:"type,omitempty" json:"type,omitempty" comment:"type of generator"`
1189 General
1190 }
1191 type Config struct {
1192 General
1193 Custom []Custom `toml:"custom,omitempty" json:"custom,omitempty" comment:"generators with custom parameters can be specified separately"`
1194 }
1195
1196 buf := new(bytes.Buffer)
1197 config := &Config{General: General{From: "-2d", Randomize: true}}
1198 config.Custom = []Custom{{Name: "omit", General: General{Randomize: false}}}
1199 config.Custom = append(config.Custom, Custom{Name: "present", General: General{From: "-2d", Randomize: true}})
1200 encoder := toml.NewEncoder(buf)
1201 encoder.Encode(config)
1202
1203 expected := `# from in graphite-web format, the local TZ is used
1204 from = '-2d'
1205 # randomize starting time with [0,step)
1206 randomize = true
1207
1208 # generators with custom parameters can be specified separately
1209 [[custom]]
1210 # names for generator, braces are expanded like in shell
1211 name = 'omit'
1212 # randomize starting time with [0,step)
1213 randomize = false
1214
1215 [[custom]]
1216 # names for generator, braces are expanded like in shell
1217 name = 'present'
1218 # from in graphite-web format, the local TZ is used
1219 from = '-2d'
1220 # randomize starting time with [0,step)
1221 randomize = true
1222 `
1223
1224 require.Equal(t, expected, buf.String())
1225 }
1226
1227 func TestMarhsalIssue888(t *testing.T) {
1228 type Thing struct {
1229 FieldA string `comment:"my field A"`
1230 FieldB string `comment:"my field B"`
1231 }
1232
1233 type Cfg struct {
1234 Custom []Thing `comment:"custom config"`
1235 }
1236
1237 buf := new(bytes.Buffer)
1238
1239 config := Cfg{
1240 Custom: []Thing{
1241 {FieldA: "field a 1", FieldB: "field b 1"},
1242 {FieldA: "field a 2", FieldB: "field b 2"},
1243 },
1244 }
1245
1246 encoder := toml.NewEncoder(buf).SetIndentTables(true)
1247 encoder.Encode(config)
1248
1249 expected := `# custom config
1250 [[Custom]]
1251 # my field A
1252 FieldA = 'field a 1'
1253 # my field B
1254 FieldB = 'field b 1'
1255
1256 [[Custom]]
1257 # my field A
1258 FieldA = 'field a 2'
1259 # my field B
1260 FieldB = 'field b 2'
1261 `
1262
1263 require.Equal(t, expected, buf.String())
1264 }
1265
1266 func TestMarshalNestedAnonymousStructs(t *testing.T) {
1267 type Embedded struct {
1268 Value string `toml:"value" json:"value"`
1269 Top struct {
1270 Value string `toml:"value" json:"value"`
1271 } `toml:"top" json:"top"`
1272 }
1273
1274 type Named struct {
1275 Value string `toml:"value" json:"value"`
1276 }
1277
1278 var doc struct {
1279 Embedded
1280 Named `toml:"named" json:"named"`
1281 Anonymous struct {
1282 Value string `toml:"value" json:"value"`
1283 } `toml:"anonymous" json:"anonymous"`
1284 }
1285
1286 expected := `value = ''
1287
1288 [top]
1289 value = ''
1290
1291 [named]
1292 value = ''
1293
1294 [anonymous]
1295 value = ''
1296 `
1297
1298 result, err := toml.Marshal(doc)
1299 require.NoError(t, err)
1300 require.Equal(t, expected, string(result))
1301 }
1302
1303 func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
1304 type Embedded struct {
1305 Value string `toml:"value" json:"value"`
1306 Top struct {
1307 Value string `toml:"value" json:"value"`
1308 } `toml:"top" json:"top"`
1309 }
1310
1311 var doc struct {
1312 Value string `toml:"value" json:"value"`
1313 Embedded
1314 }
1315 doc.Embedded.Value = "shadowed"
1316 doc.Value = "shadows"
1317
1318 expected := `value = 'shadows'
1319
1320 [top]
1321 value = ''
1322 `
1323
1324 result, err := toml.Marshal(doc)
1325 require.NoError(t, err)
1326 require.NoError(t, err)
1327 require.Equal(t, expected, string(result))
1328 }
1329
1330 func TestMarshalNestedAnonymousStructs_PointerEmbedded(t *testing.T) {
1331 type Embedded struct {
1332 Value string `toml:"value" json:"value"`
1333 Omitted string `toml:"omitted,omitempty"`
1334 Ptr *string `toml:"ptr"`
1335 }
1336
1337 type Named struct {
1338 Value string `toml:"value" json:"value"`
1339 }
1340
1341 type Doc struct {
1342 *Embedded
1343 *Named `toml:"named" json:"named"`
1344 Anonymous struct {
1345 *Embedded
1346 Value *string `toml:"value" json:"value"`
1347 } `toml:"anonymous,omitempty" json:"anonymous,omitempty"`
1348 }
1349
1350 doc := &Doc{
1351 Embedded: &Embedded{Value: "foo"},
1352 }
1353
1354 expected := `value = 'foo'
1355 `
1356
1357 result, err := toml.Marshal(doc)
1358 require.NoError(t, err)
1359 require.Equal(t, expected, string(result))
1360 }
1361
1362 func TestLocalTime(t *testing.T) {
1363 v := map[string]toml.LocalTime{
1364 "a": {
1365 Hour: 1,
1366 Minute: 2,
1367 Second: 3,
1368 Nanosecond: 4,
1369 },
1370 }
1371
1372 expected := `a = 01:02:03.000000004
1373 `
1374
1375 out, err := toml.Marshal(v)
1376 require.NoError(t, err)
1377 require.Equal(t, expected, string(out))
1378 }
1379
1380 func TestMarshalUint64Overflow(t *testing.T) {
1381
1382
1383
1384
1385 x := map[string]interface{}{
1386 "foo": uint64(math.MaxInt64) + 1,
1387 }
1388
1389 _, err := toml.Marshal(x)
1390 require.Error(t, err)
1391 }
1392
1393 func TestIndentWithInlineTable(t *testing.T) {
1394 x := map[string][]map[string]string{
1395 "one": {
1396 {"0": "0"},
1397 {"1": "1"},
1398 },
1399 }
1400 expected := `one = [
1401 {0 = '0'},
1402 {1 = '1'}
1403 ]
1404 `
1405 var buf bytes.Buffer
1406 enc := toml.NewEncoder(&buf)
1407 enc.SetIndentTables(true)
1408 enc.SetTablesInline(true)
1409 enc.SetArraysMultiline(true)
1410 require.NoError(t, enc.Encode(x))
1411 assert.Equal(t, expected, buf.String())
1412 }
1413
1414 type C3 struct {
1415 Value int `toml:",commented"`
1416 Values []int `toml:",commented"`
1417 }
1418
1419 type C2 struct {
1420 Int int64
1421 String string
1422 ArrayInts []int
1423 Structs []C3
1424 }
1425
1426 type C1 struct {
1427 Int int64 `toml:",commented"`
1428 String string `toml:",commented"`
1429 ArrayInts []int `toml:",commented"`
1430 Structs []C3 `toml:",commented"`
1431 }
1432
1433 type Commented struct {
1434 Int int64 `toml:",commented"`
1435 String string `toml:",commented"`
1436
1437 C1 C1
1438 C2 C2 `toml:",commented"`
1439 }
1440
1441 func TestMarshalCommented(t *testing.T) {
1442 c := Commented{
1443 Int: 42,
1444 String: "root",
1445
1446 C1: C1{
1447 Int: 11,
1448 String: "C1",
1449 ArrayInts: []int{1, 2, 3},
1450 Structs: []C3{
1451 {Value: 100},
1452 {Values: []int{4, 5, 6}},
1453 },
1454 },
1455 C2: C2{
1456 Int: 22,
1457 String: "C2",
1458 ArrayInts: []int{1, 2, 3},
1459 Structs: []C3{
1460 {Value: 100},
1461 {Values: []int{4, 5, 6}},
1462 },
1463 },
1464 }
1465
1466 out, err := toml.Marshal(c)
1467 require.NoError(t, err)
1468
1469 expected := `# Int = 42
1470 # String = 'root'
1471
1472 [C1]
1473 # Int = 11
1474 # String = 'C1'
1475 # ArrayInts = [1, 2, 3]
1476
1477 # [[C1.Structs]]
1478 # Value = 100
1479 # Values = []
1480
1481 # [[C1.Structs]]
1482 # Value = 0
1483 # Values = [4, 5, 6]
1484
1485 # [C2]
1486 # Int = 22
1487 # String = 'C2'
1488 # ArrayInts = [1, 2, 3]
1489
1490 # [[C2.Structs]]
1491 # Value = 100
1492 # Values = []
1493
1494 # [[C2.Structs]]
1495 # Value = 0
1496 # Values = [4, 5, 6]
1497 `
1498
1499 require.Equal(t, expected, string(out))
1500 }
1501
1502 func TestMarshalIndentedCustomTypeArray(t *testing.T) {
1503 c := struct {
1504 Nested struct {
1505 NestedArray []struct {
1506 Value int
1507 }
1508 }
1509 }{
1510 Nested: struct {
1511 NestedArray []struct {
1512 Value int
1513 }
1514 }{
1515 NestedArray: []struct {
1516 Value int
1517 }{
1518 {Value: 1},
1519 {Value: 2},
1520 },
1521 },
1522 }
1523
1524 expected := `[Nested]
1525 [[Nested.NestedArray]]
1526 Value = 1
1527
1528 [[Nested.NestedArray]]
1529 Value = 2
1530 `
1531
1532 var buf bytes.Buffer
1533 enc := toml.NewEncoder(&buf)
1534 enc.SetIndentTables(true)
1535 require.NoError(t, enc.Encode(c))
1536 require.Equal(t, expected, buf.String())
1537 }
1538
1539 func ExampleMarshal() {
1540 type MyConfig struct {
1541 Version int
1542 Name string
1543 Tags []string
1544 }
1545
1546 cfg := MyConfig{
1547 Version: 2,
1548 Name: "go-toml",
1549 Tags: []string{"go", "toml"},
1550 }
1551
1552 b, err := toml.Marshal(cfg)
1553 if err != nil {
1554 panic(err)
1555 }
1556 fmt.Println(string(b))
1557
1558
1559
1560
1561
1562 }
1563
1564
1565
1566
1567 func ExampleMarshal_commented() {
1568
1569 type Common struct {
1570 Listen string `toml:"listen" comment:"general listener"`
1571 PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`
1572 MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"`
1573 MemoryReturnInterval time.Duration `toml:"memory-return-interval" comment:"daemon will return the freed memory to the OS when it>0"`
1574 }
1575
1576 type Costs struct {
1577 Cost *int `toml:"cost" comment:"default cost (for wildcarded equalence or matched with regex, or if no value cost set)"`
1578 ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equalence without wildcards) (additional tuning, usually not needed)"`
1579 }
1580
1581 type ClickHouse struct {
1582 URL string `toml:"url" comment:"default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params"`
1583
1584 RenderMaxQueries int `toml:"render-max-queries" comment:"Max queries to render queiries"`
1585 RenderConcurrentQueries int `toml:"render-concurrent-queries" comment:"Concurrent queries to render queiries"`
1586 TaggedCosts map[string]*Costs `toml:"tagged-costs,commented"`
1587 TreeTable string `toml:"tree-table,commented"`
1588 ReverseTreeTable string `toml:"reverse-tree-table,commented"`
1589 DateTreeTable string `toml:"date-tree-table,commented"`
1590 DateTreeTableVersion int `toml:"date-tree-table-version,commented"`
1591 TreeTimeout time.Duration `toml:"tree-timeout,commented"`
1592 TagTable string `toml:"tag-table,commented"`
1593 ExtraPrefix string `toml:"extra-prefix" comment:"add extra prefix (directory in graphite) for all metrics, w/o trailing dot"`
1594 ConnectTimeout time.Duration `toml:"connect-timeout" comment:"TCP connection timeout"`
1595 DataTableLegacy string `toml:"data-table,commented"`
1596 RollupConfLegacy string `toml:"rollup-conf,commented"`
1597 MaxDataPoints int `toml:"max-data-points" comment:"max points per metric when internal-aggregation=true"`
1598 InternalAggregation bool `toml:"internal-aggregation" comment:"ClickHouse-side aggregation, see doc/aggregation.md"`
1599 }
1600
1601 type Tags struct {
1602 Rules string `toml:"rules"`
1603 Date string `toml:"date"`
1604 ExtraWhere string `toml:"extra-where"`
1605 InputFile string `toml:"input-file"`
1606 OutputFile string `toml:"output-file"`
1607 }
1608
1609 type Config struct {
1610 Common Common `toml:"common"`
1611 ClickHouse ClickHouse `toml:"clickhouse"`
1612 Tags Tags `toml:"tags,commented"`
1613 }
1614
1615 cfg := &Config{
1616 Common: Common{
1617 Listen: ":9090",
1618 PprofListen: "",
1619 MaxMetricsPerTarget: 15000,
1620 MemoryReturnInterval: 0,
1621 },
1622 ClickHouse: ClickHouse{
1623 URL: "http://localhost:8123?cancel_http_readonly_queries_on_client_close=1",
1624 ExtraPrefix: "",
1625 ConnectTimeout: time.Second,
1626 DataTableLegacy: "",
1627 RollupConfLegacy: "auto",
1628 MaxDataPoints: 1048576,
1629 InternalAggregation: true,
1630 },
1631 Tags: Tags{},
1632 }
1633
1634 out, err := toml.Marshal(cfg)
1635 if err != nil {
1636 panic(err)
1637 }
1638 err = toml.Unmarshal(out, &cfg)
1639 if err != nil {
1640 panic(err)
1641 }
1642
1643 fmt.Println(string(out))
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686 }
1687
1688 func TestReadmeComments(t *testing.T) {
1689 type TLS struct {
1690 Cipher string `toml:"cipher"`
1691 Version string `toml:"version"`
1692 }
1693 type Config struct {
1694 Host string `toml:"host" comment:"Host IP to connect to."`
1695 Port int `toml:"port" comment:"Port of the remote server."`
1696 Tls TLS `toml:"TLS,commented" comment:"Encryption parameters (optional)"`
1697 }
1698 example := Config{
1699 Host: "127.0.0.1",
1700 Port: 4242,
1701 Tls: TLS{
1702 Cipher: "AEAD-AES128-GCM-SHA256",
1703 Version: "TLS 1.3",
1704 },
1705 }
1706 out, err := toml.Marshal(example)
1707 require.NoError(t, err)
1708
1709 expected := `# Host IP to connect to.
1710 host = '127.0.0.1'
1711 # Port of the remote server.
1712 port = 4242
1713
1714 # Encryption parameters (optional)
1715 # [TLS]
1716 # cipher = 'AEAD-AES128-GCM-SHA256'
1717 # version = 'TLS 1.3'
1718 `
1719 require.Equal(t, expected, string(out))
1720 }
1721
View as plain text