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 TestEncoderOmitempty(t *testing.T) {
952 type doc struct {
953 String string `toml:",omitempty,multiline"`
954 Bool bool `toml:",omitempty,multiline"`
955 Int int `toml:",omitempty,multiline"`
956 Int8 int8 `toml:",omitempty,multiline"`
957 Int16 int16 `toml:",omitempty,multiline"`
958 Int32 int32 `toml:",omitempty,multiline"`
959 Int64 int64 `toml:",omitempty,multiline"`
960 Uint uint `toml:",omitempty,multiline"`
961 Uint8 uint8 `toml:",omitempty,multiline"`
962 Uint16 uint16 `toml:",omitempty,multiline"`
963 Uint32 uint32 `toml:",omitempty,multiline"`
964 Uint64 uint64 `toml:",omitempty,multiline"`
965 Float32 float32 `toml:",omitempty,multiline"`
966 Float64 float64 `toml:",omitempty,multiline"`
967 MapNil map[string]string `toml:",omitempty,multiline"`
968 Slice []string `toml:",omitempty,multiline"`
969 Ptr *string `toml:",omitempty,multiline"`
970 Iface interface{} `toml:",omitempty,multiline"`
971 Struct struct{} `toml:",omitempty,multiline"`
972 }
973
974 d := doc{}
975
976 b, err := toml.Marshal(d)
977 require.NoError(t, err)
978
979 expected := ``
980
981 assert.Equal(t, expected, string(b))
982 }
983
984 func TestEncoderTagFieldName(t *testing.T) {
985 type doc struct {
986 String string `toml:"hello"`
987 OkSym string `toml:"#"`
988 Bad string `toml:"\"`
989 }
990
991 d := doc{String: "world"}
992
993 b, err := toml.Marshal(d)
994 require.NoError(t, err)
995
996 expected := `hello = 'world'
997 '#' = ''
998 Bad = ''
999 `
1000
1001 assert.Equal(t, expected, string(b))
1002 }
1003
1004 func TestIssue436(t *testing.T) {
1005 data := []byte(`{"a": [ { "b": { "c": "d" } } ]}`)
1006
1007 var v interface{}
1008 err := json.Unmarshal(data, &v)
1009 require.NoError(t, err)
1010
1011 var buf bytes.Buffer
1012 err = toml.NewEncoder(&buf).Encode(v)
1013 require.NoError(t, err)
1014
1015 expected := `[[a]]
1016 [a.b]
1017 c = 'd'
1018 `
1019 assert.Equal(t, expected, buf.String())
1020 }
1021
1022 func TestIssue424(t *testing.T) {
1023 type Message1 struct {
1024 Text string
1025 }
1026
1027 type Message2 struct {
1028 Text string `multiline:"true"`
1029 }
1030
1031 msg1 := Message1{"Hello\\World"}
1032 msg2 := Message2{"Hello\\World"}
1033
1034 toml1, err := toml.Marshal(msg1)
1035 require.NoError(t, err)
1036
1037 toml2, err := toml.Marshal(msg2)
1038 require.NoError(t, err)
1039
1040 msg1parsed := Message1{}
1041 err = toml.Unmarshal(toml1, &msg1parsed)
1042 require.NoError(t, err)
1043 require.Equal(t, msg1, msg1parsed)
1044
1045 msg2parsed := Message2{}
1046 err = toml.Unmarshal(toml2, &msg2parsed)
1047 require.NoError(t, err)
1048 require.Equal(t, msg2, msg2parsed)
1049 }
1050
1051 func TestIssue567(t *testing.T) {
1052 var m map[string]interface{}
1053 err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
1054 require.NoError(t, err)
1055 require.IsType(t, m["A"], toml.LocalTime{})
1056 }
1057
1058 func TestIssue590(t *testing.T) {
1059 type CustomType int
1060 var cfg struct {
1061 Option CustomType `toml:"option"`
1062 }
1063 err := toml.Unmarshal([]byte("option = 42"), &cfg)
1064 require.NoError(t, err)
1065 }
1066
1067 func TestIssue571(t *testing.T) {
1068 type Foo struct {
1069 Float32 float32
1070 Float64 float64
1071 }
1072
1073 const closeEnough = 1e-9
1074
1075 foo := Foo{
1076 Float32: 42,
1077 Float64: 43,
1078 }
1079 b, err := toml.Marshal(foo)
1080 require.NoError(t, err)
1081
1082 var foo2 Foo
1083 err = toml.Unmarshal(b, &foo2)
1084 require.NoError(t, err)
1085
1086 assert.InDelta(t, 42, foo2.Float32, closeEnough)
1087 assert.InDelta(t, 43, foo2.Float64, closeEnough)
1088 }
1089
1090 func TestIssue678(t *testing.T) {
1091 type Config struct {
1092 BigInt big.Int
1093 }
1094
1095 cfg := &Config{
1096 BigInt: *big.NewInt(123),
1097 }
1098
1099 out, err := toml.Marshal(cfg)
1100 require.NoError(t, err)
1101 assert.Equal(t, "BigInt = '123'\n", string(out))
1102
1103 cfg2 := &Config{}
1104 err = toml.Unmarshal(out, cfg2)
1105 require.NoError(t, err)
1106 require.Equal(t, cfg, cfg2)
1107 }
1108
1109 func TestIssue752(t *testing.T) {
1110 type Fooer interface {
1111 Foo() string
1112 }
1113
1114 type Container struct {
1115 Fooer
1116 }
1117
1118 c := Container{}
1119
1120 out, err := toml.Marshal(c)
1121 require.NoError(t, err)
1122 require.Equal(t, "", string(out))
1123 }
1124
1125 func TestIssue768(t *testing.T) {
1126 type cfg struct {
1127 Name string `comment:"This is a multiline comment.\nThis is line 2."`
1128 }
1129
1130 out, err := toml.Marshal(&cfg{})
1131 require.NoError(t, err)
1132
1133 expected := `# This is a multiline comment.
1134 # This is line 2.
1135 Name = ''
1136 `
1137
1138 require.Equal(t, expected, string(out))
1139 }
1140
1141 func TestIssue786(t *testing.T) {
1142 type Dependencies struct {
1143 Dependencies []string `toml:"dependencies,multiline,omitempty"`
1144 BuildDependencies []string `toml:"buildDependencies,multiline,omitempty"`
1145 OptionalDependencies []string `toml:"optionalDependencies,multiline,omitempty"`
1146 }
1147
1148 type Test struct {
1149 Dependencies Dependencies `toml:"dependencies,omitempty"`
1150 }
1151
1152 x := Test{}
1153 b, err := toml.Marshal(x)
1154 require.NoError(t, err)
1155
1156 require.Equal(t, "", string(b))
1157
1158 type General struct {
1159 From string `toml:"from,omitempty" json:"from,omitempty" comment:"from in graphite-web format, the local TZ is used"`
1160 Randomize bool `toml:"randomize" json:"randomize" comment:"randomize starting time with [0,step)"`
1161 }
1162
1163 type Custom struct {
1164 Name string `toml:"name" json:"name,omitempty" comment:"names for generator, braces are expanded like in shell"`
1165 Type string `toml:"type,omitempty" json:"type,omitempty" comment:"type of generator"`
1166 General
1167 }
1168 type Config struct {
1169 General
1170 Custom []Custom `toml:"custom,omitempty" json:"custom,omitempty" comment:"generators with custom parameters can be specified separately"`
1171 }
1172
1173 buf := new(bytes.Buffer)
1174 config := &Config{General: General{From: "-2d", Randomize: true}}
1175 config.Custom = []Custom{{Name: "omit", General: General{Randomize: false}}}
1176 config.Custom = append(config.Custom, Custom{Name: "present", General: General{From: "-2d", Randomize: true}})
1177 encoder := toml.NewEncoder(buf)
1178 encoder.Encode(config)
1179
1180 expected := `# from in graphite-web format, the local TZ is used
1181 from = '-2d'
1182 # randomize starting time with [0,step)
1183 randomize = true
1184
1185 # generators with custom parameters can be specified separately
1186 [[custom]]
1187 # names for generator, braces are expanded like in shell
1188 name = 'omit'
1189 # randomize starting time with [0,step)
1190 randomize = false
1191
1192 [[custom]]
1193 # names for generator, braces are expanded like in shell
1194 name = 'present'
1195 # from in graphite-web format, the local TZ is used
1196 from = '-2d'
1197 # randomize starting time with [0,step)
1198 randomize = true
1199 `
1200
1201 require.Equal(t, expected, buf.String())
1202 }
1203
1204 func TestMarhsalIssue888(t *testing.T) {
1205 type Thing struct {
1206 FieldA string `comment:"my field A"`
1207 FieldB string `comment:"my field B"`
1208 }
1209
1210 type Cfg struct {
1211 Custom []Thing `comment:"custom config"`
1212 }
1213
1214 buf := new(bytes.Buffer)
1215
1216 config := Cfg{
1217 Custom: []Thing{
1218 {FieldA: "field a 1", FieldB: "field b 1"},
1219 {FieldA: "field a 2", FieldB: "field b 2"},
1220 },
1221 }
1222
1223 encoder := toml.NewEncoder(buf).SetIndentTables(true)
1224 encoder.Encode(config)
1225
1226 expected := `# custom config
1227 [[Custom]]
1228 # my field A
1229 FieldA = 'field a 1'
1230 # my field B
1231 FieldB = 'field b 1'
1232
1233 [[Custom]]
1234 # my field A
1235 FieldA = 'field a 2'
1236 # my field B
1237 FieldB = 'field b 2'
1238 `
1239
1240 require.Equal(t, expected, buf.String())
1241 }
1242
1243 func TestMarshalNestedAnonymousStructs(t *testing.T) {
1244 type Embedded struct {
1245 Value string `toml:"value" json:"value"`
1246 Top struct {
1247 Value string `toml:"value" json:"value"`
1248 } `toml:"top" json:"top"`
1249 }
1250
1251 type Named struct {
1252 Value string `toml:"value" json:"value"`
1253 }
1254
1255 var doc struct {
1256 Embedded
1257 Named `toml:"named" json:"named"`
1258 Anonymous struct {
1259 Value string `toml:"value" json:"value"`
1260 } `toml:"anonymous" json:"anonymous"`
1261 }
1262
1263 expected := `value = ''
1264
1265 [top]
1266 value = ''
1267
1268 [named]
1269 value = ''
1270
1271 [anonymous]
1272 value = ''
1273 `
1274
1275 result, err := toml.Marshal(doc)
1276 require.NoError(t, err)
1277 require.Equal(t, expected, string(result))
1278 }
1279
1280 func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
1281 type Embedded struct {
1282 Value string `toml:"value" json:"value"`
1283 Top struct {
1284 Value string `toml:"value" json:"value"`
1285 } `toml:"top" json:"top"`
1286 }
1287
1288 var doc struct {
1289 Value string `toml:"value" json:"value"`
1290 Embedded
1291 }
1292 doc.Embedded.Value = "shadowed"
1293 doc.Value = "shadows"
1294
1295 expected := `value = 'shadows'
1296
1297 [top]
1298 value = ''
1299 `
1300
1301 result, err := toml.Marshal(doc)
1302 require.NoError(t, err)
1303 require.NoError(t, err)
1304 require.Equal(t, expected, string(result))
1305 }
1306
1307 func TestLocalTime(t *testing.T) {
1308 v := map[string]toml.LocalTime{
1309 "a": {
1310 Hour: 1,
1311 Minute: 2,
1312 Second: 3,
1313 Nanosecond: 4,
1314 },
1315 }
1316
1317 expected := `a = 01:02:03.000000004
1318 `
1319
1320 out, err := toml.Marshal(v)
1321 require.NoError(t, err)
1322 require.Equal(t, expected, string(out))
1323 }
1324
1325 func TestMarshalUint64Overflow(t *testing.T) {
1326
1327
1328
1329
1330 x := map[string]interface{}{
1331 "foo": uint64(math.MaxInt64) + 1,
1332 }
1333
1334 _, err := toml.Marshal(x)
1335 require.Error(t, err)
1336 }
1337
1338 func TestIndentWithInlineTable(t *testing.T) {
1339 x := map[string][]map[string]string{
1340 "one": {
1341 {"0": "0"},
1342 {"1": "1"},
1343 },
1344 }
1345 expected := `one = [
1346 {0 = '0'},
1347 {1 = '1'}
1348 ]
1349 `
1350 var buf bytes.Buffer
1351 enc := toml.NewEncoder(&buf)
1352 enc.SetIndentTables(true)
1353 enc.SetTablesInline(true)
1354 enc.SetArraysMultiline(true)
1355 require.NoError(t, enc.Encode(x))
1356 assert.Equal(t, expected, buf.String())
1357 }
1358
1359 type C3 struct {
1360 Value int `toml:",commented"`
1361 Values []int `toml:",commented"`
1362 }
1363
1364 type C2 struct {
1365 Int int64
1366 String string
1367 ArrayInts []int
1368 Structs []C3
1369 }
1370
1371 type C1 struct {
1372 Int int64 `toml:",commented"`
1373 String string `toml:",commented"`
1374 ArrayInts []int `toml:",commented"`
1375 Structs []C3 `toml:",commented"`
1376 }
1377
1378 type Commented struct {
1379 Int int64 `toml:",commented"`
1380 String string `toml:",commented"`
1381
1382 C1 C1
1383 C2 C2 `toml:",commented"`
1384 }
1385
1386 func TestMarshalCommented(t *testing.T) {
1387 c := Commented{
1388 Int: 42,
1389 String: "root",
1390
1391 C1: C1{
1392 Int: 11,
1393 String: "C1",
1394 ArrayInts: []int{1, 2, 3},
1395 Structs: []C3{
1396 {Value: 100},
1397 {Values: []int{4, 5, 6}},
1398 },
1399 },
1400 C2: C2{
1401 Int: 22,
1402 String: "C2",
1403 ArrayInts: []int{1, 2, 3},
1404 Structs: []C3{
1405 {Value: 100},
1406 {Values: []int{4, 5, 6}},
1407 },
1408 },
1409 }
1410
1411 out, err := toml.Marshal(c)
1412 require.NoError(t, err)
1413
1414 expected := `# Int = 42
1415 # String = 'root'
1416
1417 [C1]
1418 # Int = 11
1419 # String = 'C1'
1420 # ArrayInts = [1, 2, 3]
1421
1422 # [[C1.Structs]]
1423 # Value = 100
1424 # Values = []
1425
1426 # [[C1.Structs]]
1427 # Value = 0
1428 # Values = [4, 5, 6]
1429
1430 # [C2]
1431 # Int = 22
1432 # String = 'C2'
1433 # ArrayInts = [1, 2, 3]
1434
1435 # [[C2.Structs]]
1436 # Value = 100
1437 # Values = []
1438
1439 # [[C2.Structs]]
1440 # Value = 0
1441 # Values = [4, 5, 6]
1442 `
1443
1444 require.Equal(t, expected, string(out))
1445 }
1446
1447 func ExampleMarshal() {
1448 type MyConfig struct {
1449 Version int
1450 Name string
1451 Tags []string
1452 }
1453
1454 cfg := MyConfig{
1455 Version: 2,
1456 Name: "go-toml",
1457 Tags: []string{"go", "toml"},
1458 }
1459
1460 b, err := toml.Marshal(cfg)
1461 if err != nil {
1462 panic(err)
1463 }
1464 fmt.Println(string(b))
1465
1466
1467
1468
1469
1470 }
1471
1472
1473
1474
1475 func ExampleMarshal_commented() {
1476
1477 type Common struct {
1478 Listen string `toml:"listen" comment:"general listener"`
1479 PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`
1480 MaxMetricsPerTarget int `toml:"max-metrics-per-target" comment:"limit numbers of queried metrics per target in /render requests, 0 or negative = unlimited"`
1481 MemoryReturnInterval time.Duration `toml:"memory-return-interval" comment:"daemon will return the freed memory to the OS when it>0"`
1482 }
1483
1484 type Costs struct {
1485 Cost *int `toml:"cost" comment:"default cost (for wildcarded equalence or matched with regex, or if no value cost set)"`
1486 ValuesCost map[string]int `toml:"values-cost" comment:"cost with some value (for equalence without wildcards) (additional tuning, usually not needed)"`
1487 }
1488
1489 type ClickHouse struct {
1490 URL string `toml:"url" comment:"default url, see https://clickhouse.tech/docs/en/interfaces/http. Can be overwritten with query-params"`
1491
1492 RenderMaxQueries int `toml:"render-max-queries" comment:"Max queries to render queiries"`
1493 RenderConcurrentQueries int `toml:"render-concurrent-queries" comment:"Concurrent queries to render queiries"`
1494 TaggedCosts map[string]*Costs `toml:"tagged-costs,commented"`
1495 TreeTable string `toml:"tree-table,commented"`
1496 ReverseTreeTable string `toml:"reverse-tree-table,commented"`
1497 DateTreeTable string `toml:"date-tree-table,commented"`
1498 DateTreeTableVersion int `toml:"date-tree-table-version,commented"`
1499 TreeTimeout time.Duration `toml:"tree-timeout,commented"`
1500 TagTable string `toml:"tag-table,commented"`
1501 ExtraPrefix string `toml:"extra-prefix" comment:"add extra prefix (directory in graphite) for all metrics, w/o trailing dot"`
1502 ConnectTimeout time.Duration `toml:"connect-timeout" comment:"TCP connection timeout"`
1503 DataTableLegacy string `toml:"data-table,commented"`
1504 RollupConfLegacy string `toml:"rollup-conf,commented"`
1505 MaxDataPoints int `toml:"max-data-points" comment:"max points per metric when internal-aggregation=true"`
1506 InternalAggregation bool `toml:"internal-aggregation" comment:"ClickHouse-side aggregation, see doc/aggregation.md"`
1507 }
1508
1509 type Tags struct {
1510 Rules string `toml:"rules"`
1511 Date string `toml:"date"`
1512 ExtraWhere string `toml:"extra-where"`
1513 InputFile string `toml:"input-file"`
1514 OutputFile string `toml:"output-file"`
1515 }
1516
1517 type Config struct {
1518 Common Common `toml:"common"`
1519 ClickHouse ClickHouse `toml:"clickhouse"`
1520 Tags Tags `toml:"tags,commented"`
1521 }
1522
1523 cfg := &Config{
1524 Common: Common{
1525 Listen: ":9090",
1526 PprofListen: "",
1527 MaxMetricsPerTarget: 15000,
1528 MemoryReturnInterval: 0,
1529 },
1530 ClickHouse: ClickHouse{
1531 URL: "http://localhost:8123?cancel_http_readonly_queries_on_client_close=1",
1532 ExtraPrefix: "",
1533 ConnectTimeout: time.Second,
1534 DataTableLegacy: "",
1535 RollupConfLegacy: "auto",
1536 MaxDataPoints: 1048576,
1537 InternalAggregation: true,
1538 },
1539 Tags: Tags{},
1540 }
1541
1542 out, err := toml.Marshal(cfg)
1543 if err != nil {
1544 panic(err)
1545 }
1546 err = toml.Unmarshal(out, &cfg)
1547 if err != nil {
1548 panic(err)
1549 }
1550
1551 fmt.Println(string(out))
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594 }
1595
1596 func TestReadmeComments(t *testing.T) {
1597 type TLS struct {
1598 Cipher string `toml:"cipher"`
1599 Version string `toml:"version"`
1600 }
1601 type Config struct {
1602 Host string `toml:"host" comment:"Host IP to connect to."`
1603 Port int `toml:"port" comment:"Port of the remote server."`
1604 Tls TLS `toml:"TLS,commented" comment:"Encryption parameters (optional)"`
1605 }
1606 example := Config{
1607 Host: "127.0.0.1",
1608 Port: 4242,
1609 Tls: TLS{
1610 Cipher: "AEAD-AES128-GCM-SHA256",
1611 Version: "TLS 1.3",
1612 },
1613 }
1614 out, err := toml.Marshal(example)
1615 require.NoError(t, err)
1616
1617 expected := `# Host IP to connect to.
1618 host = '127.0.0.1'
1619 # Port of the remote server.
1620 port = 4242
1621
1622 # Encryption parameters (optional)
1623 # [TLS]
1624 # cipher = 'AEAD-AES128-GCM-SHA256'
1625 # version = 'TLS 1.3'
1626 `
1627 require.Equal(t, expected, string(out))
1628 }
1629
View as plain text