package interpres import ( "math" "reflect" "strings" "testing" "time" ) func TestMarshalScalars(t *testing.T) { type Cfg struct { Title string `toml:"title"` Count int `toml:"count"` Unsigned uint64 `toml:"unsigned"` Ratio float64 `toml:"ratio"` Enabled bool `toml:"enabled"` Disabled bool `toml:"disabled"` } out, err := Marshal(Cfg{ Title: "demo", Count: 42, Unsigned: 99, Ratio: 3.14, Enabled: true, Disabled: false, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "title = \"demo\"\ncount = 42\nunsigned = 99\nratio = 3.14\nenabled = true\ndisabled = false\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalFloatSpecials(t *testing.T) { type Cfg struct { PosInf float64 `toml:"pos_inf"` NegInf float64 `toml:"neg_inf"` NaN float64 `toml:"nan"` Zero float64 `toml:"zero"` IntVal float64 `toml:"int_val"` NegZ float64 `toml:"neg_zero"` } out, err := Marshal(Cfg{ PosInf: math.Inf(1), NegInf: math.Inf(-1), NaN: math.NaN(), Zero: 0, IntVal: 7, NegZ: math.Copysign(0, -1), }) if err != nil { t.Fatalf("marshal: %v", err) } want := "pos_inf = inf\nneg_inf = -inf\nnan = nan\nzero = 0.0\nint_val = 7.0\nneg_zero = 0.0\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalStringEscapes(t *testing.T) { cases := []struct { name string in string want string // the TOML scalar value (without "s = " prefix) }{ {"plain", "hello", `"hello"`}, {"quote", `say "hi"`, `"say \"hi\""`}, {"backslash", `a\b`, `"a\\b"`}, {"newline", "line1\nline2", `"line1\nline2"`}, {"tab", "col1\tcol2", `"col1\tcol2"`}, {"cr", "line\rmore", `"line\rmore"`}, {"control", "a\x01b", `"a\u0001b"`}, {"unicode", "\u201csmart\u201d", `"“smart”"`}, // printable unicode; not escaped {"empty", "", `""`}, {"slash_only", "a/b", `"a/b"`}, } for _, c := range cases { out, err := Marshal(struct { S string `toml:"s"` }{S: c.in}) if err != nil { t.Fatalf("%s: marshal: %v", c.name, err) } got := strings.TrimSuffix(string(out), "\n") want := "s = " + c.want if got != want { t.Errorf("%s:\ngot: %s\nwant: %s", c.name, got, want) } } } func TestMarshalDateTime(t *testing.T) { type Cfg struct { Offset time.Time `toml:"offset"` Local LocalDateTime `toml:"local"` Day LocalDate `toml:"day"` Clock LocalTime `toml:"clock"` } out, err := Marshal(Cfg{ Offset: time.Date(2026, 6, 26, 10, 0, 0, 0, time.UTC), Local: LocalDateTime{Time: time.Date(2026, 6, 26, 7, 32, 0, 0, time.UTC)}, Day: LocalDate{Time: time.Date(2026, 6, 26, 0, 0, 0, 0, time.UTC)}, Clock: LocalTime{Time: time.Date(0, 1, 1, 7, 32, 0, 0, time.UTC)}, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "offset = 2026-06-26T10:00:00Z\nlocal = 2026-06-26T07:32:00\nday = 2026-06-26\nclock = 07:32:00\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalDateTimeFractional(t *testing.T) { out, err := Marshal(struct { LDT LocalDateTime `toml:"ldt"` LT LocalTime `toml:"lt"` }{ LDT: LocalDateTime{Time: time.Date(2026, 6, 26, 7, 32, 0, 123456789, time.UTC)}, LT: LocalTime{Time: time.Date(0, 1, 1, 7, 32, 0, 123, time.UTC)}, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "ldt = 2026-06-26T07:32:00.123456789\nlt = 07:32:00.000000123\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalArraysOfScalars(t *testing.T) { type Cfg struct { Tags []string `toml:"tags"` Ports []int `toml:"ports"` Mixed []any `toml:"mixed"` Empty []int `toml:"empty"` EmptyS []string `toml:"empty_s"` } out, err := Marshal(Cfg{ Tags: []string{"a", "b"}, Ports: []int{80, 443}, Mixed: []any{int64(1), "x", true}, Empty: nil, EmptyS: []string{}, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "tags = [\"a\", \"b\"]\nports = [80, 443]\nmixed = [1, \"x\", true]\nempty_s = []\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalNestedArrays(t *testing.T) { type Cfg struct { Matrix [][]int `toml:"matrix"` Words [][]string `toml:"words"` } out, err := Marshal(Cfg{ Matrix: [][]int{{1, 2}, {3, 4}}, Words: [][]string{{"a", "b"}, {"c"}}, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "matrix = [[1, 2], [3, 4]]\nwords = [[\"a\", \"b\"], [\"c\"]]\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalFloatExponentNoLeadingZero(t *testing.T) { // strconv.FormatFloat with 'g' would produce "1e+06" (leading zero in // exponent). The encoder must strip it so the output is "1e+6". type Cfg struct { Large float64 `toml:"large"` Small float64 `toml:"small"` } out, err := Marshal(Cfg{Large: 1e6, Small: 1e-5}) if err != nil { t.Fatalf("marshal: %v", err) } // Parse to check the output is valid TOML (for a strict parser that // rejects leading zeros in exponents). if _, err := Parse(out); err != nil { t.Fatalf("marshalled output is not valid TOML:\n%s\nerror: %v", out, err) } if string(out) != "large = 1e+6\nsmall = 1e-5\n" { t.Errorf("output mismatch:\ngot: %q", out) } } func TestMarshalStructAsTable(t *testing.T) { type Server struct { Host string `toml:"host"` Port int `toml:"port"` } type Cfg struct { Title string `toml:"title"` Server Server `toml:"server"` } out, err := Marshal(Cfg{ Title: "demo", Server: Server{Host: "127.0.0.1", Port: 9090}, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "title = \"demo\"\n\n[server]\nhost = \"127.0.0.1\"\nport = 9090\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalArrayOfTables(t *testing.T) { type Item struct { Name string `toml:"name"` Qty int `toml:"qty"` } type Cfg struct { Items []Item `toml:"items"` } out, err := Marshal(Cfg{ Items: []Item{ {Name: "a", Qty: 1}, {Name: "b", Qty: 2}, }, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "[[items]]\nname = \"a\"\nqty = 1\n\n[[items]]\nname = \"b\"\nqty = 2\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalEmptyArrayOfTablesIsSkipped(t *testing.T) { type Item struct { Name string `toml:"name"` } type Cfg struct { Title string `toml:"title"` Items []Item `toml:"items"` } out, err := Marshal(Cfg{ Title: "demo", Items: nil, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "title = \"demo\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalNestedTablesAndArrays(t *testing.T) { type SMTP struct { Host string `toml:"host"` Port int `toml:"port"` } type Form struct { Name string `toml:"name"` SMTP SMTP `toml:"smtp"` } type Cfg struct { Port int `toml:"port"` Forms []Form `toml:"forms"` } out, err := Marshal(Cfg{ Port: 8080, Forms: []Form{ {Name: "contact", SMTP: SMTP{Host: "h1", Port: 587}}, {Name: "feedback", SMTP: SMTP{Host: "h2", Port: 25}}, }, }) if err != nil { t.Fatalf("marshal: %v", err) } want := "port = 8080\n\n[[forms]]\nname = \"contact\"\n\n[forms.smtp]\nhost = \"h1\"\nport = 587\n\n[[forms]]\nname = \"feedback\"\n\n[forms.smtp]\nhost = \"h2\"\nport = 25\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalStructTags(t *testing.T) { type Cfg struct { Keep string `toml:"keep"` Rename string `toml:"renamed"` Skip string `toml:"-"` Untagged string } out, err := Marshal(Cfg{ Keep: "k", Rename: "r", Skip: "s", Untagged: "u", }) if err != nil { t.Fatalf("marshal: %v", err) } want := "keep = \"k\"\nrenamed = \"r\"\nuntagged = \"u\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalEmbeddedStructPromoted(t *testing.T) { type Base struct { ID int `toml:"id"` } type Derived struct { Base Name string `toml:"name"` } out, err := Marshal(Derived{Base: Base{ID: 1}, Name: "x"}) if err != nil { t.Fatalf("marshal: %v", err) } want := "id = 1\nname = \"x\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalEmbeddedStructAsTable(t *testing.T) { type Inner struct { Host string `toml:"host"` } type Cfg struct { Inner Inner `toml:"inner"` Name string `toml:"name"` } out, err := Marshal(Cfg{Inner: Inner{Host: "h"}, Name: "n"}) if err != nil { t.Fatalf("marshal: %v", err) } want := "name = \"n\"\n\n[inner]\nhost = \"h\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalMapKeysSorted(t *testing.T) { m := map[string]any{ "zeta": 1, "alpha": 2, "mu": 3, } out, err := Marshal(m) if err != nil { t.Fatalf("marshal: %v", err) } want := "alpha = 2\nmu = 3\nzeta = 1\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalMapWithSubMap(t *testing.T) { m := map[string]any{ "meta": map[string]any{"x": 1, "y": 2}, "a": "z", } out, err := Marshal(m) if err != nil { t.Fatalf("marshal: %v", err) } want := "a = \"z\"\n\n[meta]\nx = 1\ny = 2\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalMarshaler(t *testing.T) { type Port int type Cfg struct { P Port `toml:"p"` } out, err := Marshal(Cfg{P: 8080}) if err != nil { t.Fatalf("marshal: %v", err) } want := "p = 8080\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalMarshalerReturningScalar(t *testing.T) { type Wrapped struct { Value string `toml:"value"` } type Alias struct{} out, err := Marshal(struct { W Wrapped `toml:"w"` }{W: Wrapped{Value: "hello"}}) if err != nil { t.Fatalf("marshal: %v", err) } want := "[w]\nvalue = \"hello\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } _ = Alias{} } func TestMarshalMarshalerReturningDifferentShape(t *testing.T) { out, err := Marshal(struct { C Custom `toml:"c"` }{C: Custom{tag: "x"}}) if err != nil { t.Fatalf("marshal: %v", err) } // Custom returns a string from MarshalTOML. want := "c = \"x\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalNilPointerFieldSkipped(t *testing.T) { type Cfg struct { Name string `toml:"name"` Hidden *string `toml:"hidden"` } out, err := Marshal(Cfg{Name: "x"}) if err != nil { t.Fatalf("marshal: %v", err) } want := "name = \"x\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalNonNilPointerFollowed(t *testing.T) { v := "v" type Cfg struct { Name string `toml:"name"` Hidden *string `toml:"hidden"` } out, err := Marshal(Cfg{Name: "n", Hidden: &v}) if err != nil { t.Fatalf("marshal: %v", err) } want := "name = \"n\"\nhidden = \"v\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalTopLevelMustBeStructOrMap(t *testing.T) { if _, err := Marshal(42); err == nil { t.Errorf("expected error marshalling int at top level") } if _, err := Marshal("hello"); err == nil { t.Errorf("expected error marshalling string at top level") } if _, err := Marshal(nil); err == nil { t.Errorf("expected error marshalling nil") } } func TestMarshalMapKeyMustBeString(t *testing.T) { m := map[int]any{1: "x"} if _, err := Marshal(m); err == nil { t.Errorf("expected error for non-string map key") } } func TestMarshalUnexportedFieldSkipped(t *testing.T) { type Cfg struct { Pub string `toml:"pub"` priv string } out, err := Marshal(Cfg{Pub: "p", priv: "s"}) if err != nil { t.Fatalf("marshal: %v", err) } want := "pub = \"p\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalBareAndQuotedKeys(t *testing.T) { type Cfg struct { Bare string `toml:"bare_key"` Dash string `toml:"with-dash"` Num string `toml:"num123"` Q string `toml:"needs space"` Dot string `toml:"needs.dot"` } out, err := Marshal(Cfg{ Bare: "a", Dash: "b", Num: "c", Q: "d", Dot: "e", }) if err != nil { t.Fatalf("marshal: %v", err) } want := "bare_key = \"a\"\nwith-dash = \"b\"\nnum123 = \"c\"\n\"needs space\" = \"d\"\n\"needs.dot\" = \"e\"\n" if string(out) != want { t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want) } } func TestMarshalThenParseRoundTrip(t *testing.T) { type Server struct { Host string `toml:"host"` Port int `toml:"port"` Enabled bool `toml:"enabled"` Tags []string `toml:"tags"` } type Form struct { Name string `toml:"name"` Allowed []string `toml:"allowed"` } type Cfg struct { Title string `toml:"title"` Count int `toml:"count"` Ratio float64 `toml:"ratio"` Server Server `toml:"server"` Forms []Form `toml:"forms"` Due time.Time `toml:"due"` Day LocalDate `toml:"day"` } in := Cfg{ Title: "demo", Count: 42, Ratio: 3.14, Server: Server{ Host: "127.0.0.1", Port: 9090, Enabled: true, Tags: []string{"a", "b"}, }, Forms: []Form{ {Name: "contact", Allowed: []string{"x"}}, {Name: "feedback", Allowed: nil}, }, Due: time.Date(2026, 6, 26, 10, 0, 0, 0, time.UTC), Day: LocalDate{Time: time.Date(2026, 6, 26, 0, 0, 0, 0, time.UTC)}, } out, err := Marshal(in) if err != nil { t.Fatalf("marshal: %v", err) } tree1, err := Parse(out) if err != nil { t.Fatalf("parse of marshalled: %v\noutput:\n%s", err, out) } // Decode back into the struct. var out2 Cfg if err := Unmarshal(out, &out2); err != nil { t.Fatalf("unmarshal of marshalled: %v", err) } if !reflect.DeepEqual(in, out2) { t.Errorf("round-trip mismatch:\nin: %#v\nout: %#v", in, out2) } _ = tree1 } func TestMarshalRoundTripFromUntypedTree(t *testing.T) { src := []byte(`title = "demo" count = 42 ratio = 3.14 enabled = true [server] host = "127.0.0.1" port = 9090 [[items]] name = "a" qty = 1 [[items]] name = "b" qty = 2 [meta] created = 2026-06-26T10:00:00Z `) tree1, err := Parse(src) if err != nil { t.Fatalf("parse src: %v", err) } out, err := Marshal(tree1) if err != nil { t.Fatalf("marshal: %v", err) } tree2, err := Parse(out) if err != nil { t.Fatalf("re-parse marshalled: %v\noutput:\n%s", err, out) } if !reflect.DeepEqual(tree1, tree2) { t.Errorf("round-trip mismatch:\nbefore: %#v\nafter: %#v", tree1, tree2) } } func TestMarshalUintOverflow(t *testing.T) { type Cfg struct { Big uint64 `toml:"big"` } if _, err := Marshal(Cfg{Big: 1<<63 + 1}); err == nil { t.Errorf("expected overflow error") } } func TestMarshalKeyRequiresUTF8(t *testing.T) { m := map[string]any{"\xff": "x"} if _, err := Marshal(m); err == nil { t.Errorf("expected error for invalid UTF-8 key") } } func TestMarshalStringRequiresUTF8(t *testing.T) { type Cfg struct { S string `toml:"s"` } if _, err := Marshal(Cfg{S: "abc\xff"}); err == nil { t.Errorf("expected error for invalid UTF-8 string") } } func TestLocalDateString(t *testing.T) { ld := LocalDate{Time: time.Date(1979, 5, 27, 0, 0, 0, 0, time.UTC)} if got := ld.String(); got != "1979-05-27" { t.Errorf("LocalDate.String() = %q, want 1979-05-27", got) } } func TestLocalDateTimeString(t *testing.T) { ldt := LocalDateTime{Time: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC)} if got := ldt.String(); got != "1979-05-27T07:32:00" { t.Errorf("LocalDateTime.String() = %q, want 1979-05-27T07:32:00", got) } ldt2 := LocalDateTime{Time: time.Date(1979, 5, 27, 7, 32, 0, 5, time.UTC)} if got := ldt2.String(); got != "1979-05-27T07:32:00.000000005" { t.Errorf("LocalDateTime.String() = %q, want 1979-05-27T07:32:00.000000005", got) } ldt3 := LocalDateTime{Time: time.Date(1979, 5, 27, 7, 32, 0, 500, time.UTC)} if got := ldt3.String(); got != "1979-05-27T07:32:00.000000500" { t.Errorf("LocalDateTime.String() = %q, want 1979-05-27T07:32:00.000000500", got) } } func TestLocalTimeString(t *testing.T) { lt := LocalTime{Time: time.Date(0, 1, 1, 7, 32, 0, 0, time.UTC)} if got := lt.String(); got != "07:32:00" { t.Errorf("LocalTime.String() = %q, want 07:32:00", got) } } func TestEncoderEquivalenceToMarshal(t *testing.T) { type Cfg struct { Title string `toml:"title"` Count int `toml:"count"` } in := Cfg{Title: "x", Count: 7} a, err := Marshal(in) if err != nil { t.Fatalf("marshal: %v", err) } b, err := NewEncoder().Marshal(in) if err != nil { t.Fatalf("encoder marshal: %v", err) } if !reflect.DeepEqual(a, b) { t.Errorf("Marshal and Encoder disagree:\n%s\n%s", a, b) } } type Custom struct { tag string } func (c Custom) MarshalTOML() (any, error) { return c.tag, nil }