// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT package interpres import ( "errors" "maps" "net" "reflect" "strings" "sync/atomic" "testing" "time" ) type targetNested struct { X int `toml:"x"` Y string `toml:"y"` } type targetCfg struct { Num int `toml:"num"` Small uint8 `toml:"small"` Tags []string `toml:"tags"` Lims map[string]any `toml:"lims"` Tab targetNested `toml:"tab"` Arr []targetNested `toml:"arr"` Other string `toml:"other"` } // TestTargetedStrictFindings pins the strict findings of the targeted parse // to the tree decode's own texts, paths included. Every case here was first // surfaced by FuzzTargetedDecode. func TestTargetedStrictFindings(t *testing.T) { tests := []struct { name string doc string want string }{ { name: "unknown key in a header table", doc: "[tab]\nother = \"o\"\n", want: `interpres: tab: unknown field "other" for interpres.targetNested`, }, { name: "unknown nested header without the parent header", doc: "[tab.nested]\nx = 1\n", want: `interpres: tab: unknown field "nested" for interpres.targetNested`, }, { name: "unknown key in an array-of-tables element", doc: "[[arr]]\nother = \"o\"\n", want: `interpres: arr[0]: unknown field "other" for interpres.targetNested`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte(tt.doc), &cfg, RejectUnknownFields(true)) if err == nil { t.Fatalf("no error, want %q", tt.want) } if err.Error() != tt.want { t.Errorf("message = %q, want %q", err.Error(), tt.want) } }) } } // TestTargetedParseErrors pins the parse-stage errors the targeted skeleton // raises, whose texts and lines are the tree parser's own. func TestTargetedParseErrors(t *testing.T) { tests := []struct { name string doc string want string }{ { name: "header on an assigned scalar", doc: "zz = 1\n[zz]\nx = 4\n", want: "interpres: line 2: key \"zz\" is not a table", }, { name: "dotted key on an assigned scalar", doc: "zz = 1\nzz.x = 2\n", want: "interpres: line 2: key \"zz\" is not a table", }, { name: "duplicate unknown keys", doc: "zz = 1\nzz = 2\n", want: "interpres: line 2: duplicate key \"zz\"", }, { name: "duplicate inside an unknown table", doc: "[zz]\nk = 1\nk = 2\n", want: "interpres: line 3: duplicate key \"k\"", }, { name: "duplicate across a sink's dotted keys", doc: "[zz]\na.b = 1\na.b = 2\n", want: "interpres: line 3: duplicate key \"b\"", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte(tt.doc), &cfg) if err == nil { t.Fatalf("no error, want %q", tt.want) } if err.Error() != tt.want { t.Errorf("message = %q, want %q", err.Error(), tt.want) } }) } } // TestTargetedSilentShapes covers the documents the targeted parse accepts // with the values the tree decode gives. func TestTargetedSilentShapes(t *testing.T) { t.Run("dotted key after an unknown nested header", func(t *testing.T) { // [tab.nested] is unknown and sinks; tab.x then lands in tab, and the // sink's own x is a different key, the tree's shape exactly. var cfg, ref targetCfg in := []byte("[tab]\nx = 1\n[tab.nested]\n") if err := Unmarshal(in, &cfg); err != nil { t.Fatalf("decode: %v", err) } if err := treeDecodeInto(in, &ref); err != nil { t.Fatalf("reference: %v", err) } if !reflect.DeepEqual(cfg, ref) { t.Errorf("values disagree: targeted %+v, tree %+v", cfg, ref) } if cfg.Tab.X != 1 { t.Errorf("tab.x = %d, want 1", cfg.Tab.X) } }) t.Run("unknown keys are ignored without strict", func(t *testing.T) { var cfg, ref targetCfg in := []byte("num = 5\nz1 = 1\n[zz]\nk = 1\n") if err := Unmarshal(in, &cfg); err != nil { t.Fatalf("decode: %v", err) } if err := treeDecodeInto(in, &ref); err != nil { t.Fatalf("reference: %v", err) } if !reflect.DeepEqual(cfg, ref) { t.Errorf("values disagree: targeted %+v, tree %+v", cfg, ref) } if cfg.Num != 5 { t.Errorf("num = %d, want 5", cfg.Num) } }) t.Run("an inline table into a map field", func(t *testing.T) { var cfg targetCfg in := []byte("lims = { cpu = 4, deep = { a = true } }\n") if err := Unmarshal(in, &cfg); err != nil { t.Fatalf("decode: %v", err) } if cfg.Lims["cpu"] != int64(4) { t.Errorf("lims = %v", cfg.Lims) } }) t.Run("an overflow falls back to the decode error", func(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte("small = 300\n"), &cfg) want := "interpres: small: integer 300 overflows uint8" if err == nil || err.Error() != want { t.Errorf("err = %v, want %q", err, want) } }) t.Run("too many array-of-tables elements falls back", func(t *testing.T) { type Item struct { N int `toml:"n"` } var cfg struct { Items [2]Item `toml:"items"` } err := Unmarshal([]byte("[[items]]\nn = 1\n[[items]]\nn = 2\n[[items]]\nn = 3\n"), &cfg) want := "interpres: items: cannot assign 3 elements to [2]interpres.Item" if err == nil || err.Error() != want { t.Errorf("err = %v, want %q", err, want) } }) t.Run("a UseNumber tree keeps literals in the targeted path", func(t *testing.T) { var cfg struct { Rate Number `toml:"rate"` } if err := Unmarshal([]byte("rate = 1_000\n"), &cfg, NumbersAsLiterals(true)); err != nil { t.Fatal(err) } if cfg.Rate != "1_000" { t.Errorf("rate = %q, want 1_000", cfg.Rate) } }) t.Run("dotted keys fill a map field", func(t *testing.T) { var cfg targetCfg in := []byte("lims.a.b = true\nlims.c = 3\n") if err := Unmarshal(in, &cfg); err != nil { t.Fatalf("decode: %v", err) } if cfg.Lims["c"] != int64(3) { t.Errorf("lims = %v", cfg.Lims) } }) t.Run("an inline table cannot be extended", func(t *testing.T) { var cfg targetCfg in := []byte("lims = { a = 1 }\n[lims.deep]\nb = 2\n") err := Unmarshal(in, &cfg) if err == nil || !strings.Contains(err.Error(), "cannot extend inline table") { t.Errorf("err = %v, want the inline-table extension error", err) } }) } // TestTargetedShapesMatrix walks a document per destination shape, both // through the targeted path and the tree reference, so the two agree on // every branch the skeleton carries. func TestTargetedShapesMatrix(t *testing.T) { docs := []string{ // Scalars of every kind, arrays, maps, tables, arrays of tables. "num = 7\nflt = 1.25\nstr = \"s\"\nflag = false\nsmall = 9\ntags = [\"a\"]\nlims = { a = 1 }\n\n[tab]\nx = 1\ny = \"t\"\n\n[[arr]]\nx = 2\ny = \"u\"\n\n[[arr]]\nx = 3\ny = \"v\"\n", // Dotted keys through nested tables and maps. "tab.x = 1\ntab.y = \"s\"\nlims.a.b = true\nlims.c = 3\nnum = 2\n", // Inline tables nested in arrays, mixed value arrays. "lims = { a = { b = 1 } }\ntags = []\nother = \"o\"\n", // A sub-table of an array of tables, then a second element. "[[arr]]\nx = 1\n[arr.nested]\ny = \"n\"\n[[arr]]\ny = \"m\"\n", // Negative and signed numbers, exponents, radix forms into floats. "flt = -3.5e2\nnum = -42\nflt = +1.0\n", // A quoted key and a defined-string-shaped value. "\"quoted key\" = 1\nstr = \"multi\"\n", } for i, doc := range docs { var ref, tgt targetCfg refErr := treeDecodeInto([]byte(doc), &ref) tgtErr := Unmarshal([]byte(doc), &tgt) if (refErr == nil) != (tgtErr == nil) { t.Errorf("doc %d: error presence disagrees: tree %v, targeted %v", i, refErr, tgtErr) continue } if refErr != nil { continue } if !reflect.DeepEqual(ref, tgt) { t.Errorf("doc %d: values disagree:\ntree: %#v\ntargeted: %#v", i, ref, tgt) } } } // TestTargetedFallbackContracts pins the documents that must fall back and // produce the tree decode's exact error. func TestTargetedFallbackContracts(t *testing.T) { type Item struct { N int `toml:"n"` } tests := []struct { name string doc string want string }{ { name: "uint8 overflow", doc: "small = 300\n", want: "interpres: small: integer 300 overflows uint8", }, { name: "negative into uint", doc: "small = -1\n", want: "interpres: small: cannot assign negative -1 to uint8", }, { name: "a table into a scalar", doc: "num = { a = 1 }\n", want: "interpres: num: cannot assign table to int", }, { name: "an integer into a string field", doc: "other = 5\n", want: "interpres: other: cannot assign integer to string", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte(tt.doc), &cfg) if err == nil || err.Error() != tt.want { t.Errorf("err = %v, want %q", err, tt.want) } }) } _ = Item{} } // TestTargetedHeaderOnAssignedScalar pins the parse error a header raises // when the key already holds a scalar, before any fallback can happen. func TestTargetedHeaderOnAssignedScalarArray(t *testing.T) { var cfg targetCfg in := []byte("arr = []\n[[arr]]\nx = 1\n") err := Unmarshal(in, &cfg) want := "interpres: line 2: key \"arr\" is not an array of tables" if err == nil || err.Error() != want { t.Errorf("err = %v, want %q", err, want) } } // TestTargetedBranchParity walks the fallback branches of the targeted // skeleton: every document here takes the tree path on a rerun, and must // carry the tree decode's exact error text. func TestTargetedBranchParity(t *testing.T) { tests := []struct { name string doc string want string }{ { name: "a header over a value array", doc: "tags = [\"x\"]\n[tags]\na = 1\n", want: "interpres: line 2: key \"tags\" is not a table", }, { name: "an array header over a value array", doc: "tags = [\"x\"]\n[[tags]]\na = 1\n", want: "interpres: line 2: key \"tags\" is not an array of tables", }, { name: "an array header over a datetime field", doc: "when = 1979-05-27T07:32:00Z\n[[when]]\nx = 1\n", want: "interpres: line 2: key \"when\" is not an array of tables", }, { name: "a boolean into a string field", doc: "other = true\n", want: "interpres: other: cannot assign bool to string", }, { name: "a leading-zero integer", doc: "num = 01\n", want: "interpres: line 1: leading zeros are not allowed in numbers", }, { name: "an int64-overflowing integer", doc: "num = 99999999999999999999\n", want: "interpres: line 1: integer \"99999999999999999999\" out of range", }, { name: "a malformed boolean", doc: "flag = tru\n", want: "interpres: line 1: invalid value", }, { name: "a negative number into an unsigned field", doc: "small = -5\n", want: "interpres: small: cannot assign negative -5 to uint8", }, { name: "an integer into a string field via the generic path", doc: "other = 5\n", want: "interpres: other: cannot assign integer to string", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte(tt.doc), &cfg, RejectUnknownFields(true)) if tt.want == "" { if err != nil { t.Fatalf("err = %v, want nil", err) } return } if err == nil || err.Error() != tt.want { t.Errorf("err = %v, want %q", err, tt.want) } }) } } // TestTargetedDecodeHookFields keeps the custom decode hooks of scalar-typed // fields working in the targeted path. func TestTargetedDecodeHookFields(t *testing.T) { type Cfg struct { IP net.IP `toml:"ip"` Dur time.Duration `toml:"dur"` Unm *scalarUnmarshaler `toml:"unm"` } var cfg Cfg in := []byte("ip = \"192.0.2.1\"\ndur = \"1h30m\"\nunm = \"hello\"\n") if err := Unmarshal(in, &cfg); err != nil { t.Fatal(err) } if cfg.IP.String() != "192.0.2.1" { t.Errorf("ip = %v", cfg.IP) } if cfg.Dur != 90*time.Minute { t.Errorf("dur = %v", cfg.Dur) } if cfg.Unm == nil || cfg.Unm.val != "hello" { t.Errorf("unm = %+v", cfg.Unm) } } // TestTargetedOddShapes pins the fallback and value shapes the matrix does // not reach: space-separated date-times, non-string map keys and repeated // dotted map keys. func TestTargetedOddShapes(t *testing.T) { t.Run("a space-separated date-time", func(t *testing.T) { type Cfg struct { When time.Time `toml:"when"` } var cfg, ref Cfg doc := []byte("when = 1979-05-27 07:32:00Z\n") if err := Unmarshal(doc, &cfg); err != nil { t.Fatal(err) } if err := treeDecodeInto(doc, &ref); err != nil { t.Fatal(err) } if !cfg.When.Equal(ref.When) { t.Errorf("when = %v, want %v", cfg.When, ref.When) } }) t.Run("a map with a non-string key falls back", func(t *testing.T) { type Cfg struct { M map[int]string `toml:"m"` } var cfg, ref Cfg doc := []byte("m = { a = 1 }\n") err := Unmarshal(doc, &cfg) refErr := treeDecodeInto(doc, &ref) if err == nil || refErr == nil { t.Fatalf("err = %v, refErr = %v, want both to fail", err, refErr) } if err.Error() != refErr.Error() { t.Errorf("errors disagree: targeted %q, tree %q", err, refErr) } }) t.Run("a repeated dotted map key is a duplicate", func(t *testing.T) { var cfg targetCfg doc := []byte("lims.a.b = 1\nlims.a.b = 2\n") err := Unmarshal(doc, &cfg) want := "interpres: line 2: duplicate key \"b\"" if err == nil || err.Error() != want { t.Errorf("err = %v, want %q", err, want) } }) t.Run("an underscored integer takes the token path", func(t *testing.T) { var cfg targetCfg doc := []byte("num = 1_000\n") if err := Unmarshal(doc, &cfg); err != nil { t.Fatal(err) } if cfg.Num != 1000 { t.Errorf("num = %d, want 1000", cfg.Num) } }) t.Run("an 18-digit integer takes the fast path", func(t *testing.T) { var cfg struct { Big int64 `toml:"big"` } doc := []byte("big = 999999999999999999\n") if err := Unmarshal(doc, &cfg); err != nil { t.Fatal(err) } if cfg.Big != 999999999999999999 { t.Errorf("big = %d", cfg.Big) } }) } // TestTargetedMapTableShapes covers the map-entry branches of the targeted // skeleton: entries that become tables, entries that refuse them, and the // duplicate checks across them. func TestTargetedMapTableShapes(t *testing.T) { tests := []struct { name string doc string want string }{ { name: "a header opens a map entry table", doc: "lims.c = 1\n[lims.d]\nk = 1\n", want: "", }, { name: "a header over an assigned map entry", doc: "lims.a = 1\n[lims.a]\nk = 1\n", want: "interpres: line 2: key \"a\" is not a table", }, { name: "a dotted key over an assigned map entry", doc: "lims.a = 1\nlims.a.b = 2\n", want: "interpres: line 2: key \"a\" is not a table", }, { name: "a duplicate plain map entry", doc: "lims.a = 1\nlims.a = 2\n", want: "interpres: line 2: duplicate key \"a\"", }, { name: "an array of tables inside a map entry", doc: "lims.c = 1\n[[lims.items]]\nk = 1\n", want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var cfg, ref targetCfg err := Unmarshal([]byte(tt.doc), &cfg) refErr := treeDecodeInto([]byte(tt.doc), &ref) if (err == nil) != (refErr == nil) { t.Fatalf("error presence disagrees: tree %v, targeted %v", refErr, err) } if err != nil { if err.Error() != refErr.Error() { t.Fatalf("errors disagree:\ntree: %v\ntargeted: %v", refErr, err) } return } if !reflect.DeepEqual(cfg, ref) { t.Errorf("values disagree: targeted %+v, tree %+v", cfg, ref) } }) } } // TestTargetedNestedMapDescents pins the descents into a map of maps that // meet entries the document built earlier: a dotted key twice through the // same sub-table, a header into a dotted-built sub-table, and a typed array // under a map key. Each shape once panicked on a reflect Elem of a map. func TestTargetedNestedMapDescents(t *testing.T) { t.Run("dotted key through one sub-table twice", func(t *testing.T) { var cfg struct { M map[string]map[string]any `toml:"m"` } err := Unmarshal([]byte("m.a.b = 1\nm.a.c = 2\n"), &cfg) if err != nil { t.Fatalf("unmarshal: %v", err) } if cfg.M["a"]["b"] != int64(1) || cfg.M["a"]["c"] != int64(2) { t.Errorf("m = %#v", cfg.M) } }) t.Run("header under a dotted-built sub-table", func(t *testing.T) { var cfg struct { M map[string]map[string]any `toml:"m"` } err := Unmarshal([]byte("m.a.b = 1\n[m.a.deep]\nx = 2\n"), &cfg) if err != nil { t.Fatalf("unmarshal: %v", err) } if cfg.M["a"]["b"] != int64(1) || cfg.M["a"]["deep"].(map[string]any)["x"] != int64(2) { t.Errorf("m = %#v", cfg.M) } }) t.Run("typed array under a map key", func(t *testing.T) { var cfg struct { M map[string][]map[string]any `toml:"m"` } err := Unmarshal([]byte("[[m.arr]]\nx = 1\n\n[[m.arr]]\ny = 2\n"), &cfg) if err != nil { t.Fatalf("unmarshal: %v", err) } if len(cfg.M["arr"]) != 2 || cfg.M["arr"][1]["y"] != int64(2) { t.Errorf("m = %#v", cfg.M) } }) } // TestTargetedPointerElementSlice pins that an array of tables over a slice // of pointer elements fills the pointed-to structs. func TestTargetedPointerElementSlice(t *testing.T) { type item struct { N int `toml:"n"` } var cfg struct { Items []*item `toml:"items"` } err := Unmarshal([]byte("[[items]]\nn = 1\n\n[[items]]\nn = 2\n"), &cfg) if err != nil { t.Fatalf("unmarshal: %v", err) } if len(cfg.Items) != 2 || cfg.Items[0] == nil || cfg.Items[1].N != 2 { t.Errorf("items = %#v", cfg.Items) } } // TestTargetedArrayScopeResets pins that a new element of an array of tables // starts a fresh definition scope, the contract the changelog documents. func TestTargetedArrayScopeResets(t *testing.T) { doc := "[[a]]\nb.c = 1\n\n[[a]]\n\n[a.b]\nx = 1\n" var ref, tgt targetCfg refErr := treeDecodeInto([]byte(doc), &ref) if refErr != nil { t.Fatalf("tree decode: %v", refErr) } if err := Unmarshal([]byte(doc), &tgt); err != nil { t.Fatalf("unmarshal: %v", err) } if !reflect.DeepEqual(ref, tgt) { t.Errorf("targeted = %#v, tree = %#v", tgt, ref) } } // TestTargetedUnknownArrayElements pins that every element of an unknown // array of tables is a fresh namespace, and a sub-table header reaches the // last element the way the tree parser's does. func TestTargetedUnknownArrayElements(t *testing.T) { doc := "[[zz]]\nk = 1\n\n[[zz]]\nk = 2\n\n[zz.sub]\nx = 3\n" var ref, tgt targetCfg refErr := treeDecodeInto([]byte(doc), &ref) tgtErr := Unmarshal([]byte(doc), &tgt) if (refErr == nil) != (tgtErr == nil) { t.Fatalf("error presence disagrees: tree %v, targeted %v", refErr, tgtErr) } if refErr != nil { return } if !reflect.DeepEqual(ref, tgt) { t.Errorf("targeted = %#v, tree = %#v", tgt, ref) } // A dotted key may not enter the array: the tree's own rule. var dotted targetCfg dErr := Unmarshal([]byte("[[zz]]\nk = 1\nzz.x = 2\n"), &dotted) refDotted := treeDecodeInto([]byte("[[zz]]\nk = 1\nzz.x = 2\n"), &dotted) if (dErr == nil) != (refDotted == nil) { t.Errorf("dotted into an array: targeted %v, tree %v", dErr, refDotted) } } // TestTargetedFixedArrayUnderFill pins that a fixed-size array the document // under-fills is the length mismatch the tree decode raises, with the // field's path. func TestTargetedFixedArrayUnderFill(t *testing.T) { type item struct { N int `toml:"n"` } var cfg struct { Items [2]item `toml:"items"` } err := Unmarshal([]byte("[[items]]\nn = 1\n"), &cfg) if err == nil { t.Fatal("unmarshal accepted an under-filled array") } want := `interpres: items: cannot assign 1 elements to [2]interpres.item` if err.Error() != want { t.Errorf("err = %v\nwant %q", err, want) } } // TestTargetedPrefilledSliceReplaced pins that a prefilled slice is replaced // by the document's elements on both paths, not appended to. func TestTargetedPrefilledSliceReplaced(t *testing.T) { type item struct { N int `toml:"n"` } doc := []byte("[[items]]\nn = 1\n") var ref struct { Items []item `toml:"items"` } ref.Items = []item{{N: 9}} if err := treeDecodeInto(doc, &ref); err != nil { t.Fatalf("tree decode: %v", err) } var tgt struct { Items []item `toml:"items"` } tgt.Items = []item{{N: 9}} if err := Unmarshal(doc, &tgt); err != nil { t.Fatalf("unmarshal: %v", err) } if !reflect.DeepEqual(ref, tgt) { t.Errorf("targeted = %#v, tree = %#v", tgt, ref) } if len(tgt.Items) != 1 || tgt.Items[0].N != 1 { t.Errorf("items = %#v, want the prefilled element replaced", tgt.Items) } } // TestTargetedHeaderOverValueArrayKeepsCase pins that a value array assigned // under a differently cased key than the field's name still blocks the // array-of-tables header over it, the tree parse error. func TestTargetedHeaderOverValueArrayKeepsCase(t *testing.T) { var cfg struct { Arr []targetNested `toml:"arr"` } err := Unmarshal([]byte("Arr = [{x = 1}]\n[[Arr]]\nx = 2\n"), &cfg) if err == nil || err.Error() != `interpres: line 2: key "Arr" is not an array of tables` { t.Errorf("err = %v, want the parse error over the assigned field", err) } } // TestTargetedDottedInlineFreezePath pins that an inline table assigned by a // dotted key freezes the whole path the statement wrote: a later header // under that path is the extension error, and a key outside it stays free. func TestTargetedDottedInlineFreezePath(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte("m.a.b = {x = 1}\nb.y = 2\n"), &cfg) if err != nil { t.Fatalf("unmarshal: %v", err) } err = Unmarshal([]byte("m.a.b = {x = 1}\n[m.a.b]\ny = 2\n"), &cfg) want := `interpres: line 2: cannot extend inline table "m.a.b"` if err == nil || err.Error() != want { t.Errorf("err = %v\nwant %q", err, want) } } // TestTargetedStrictThroughDottedKeys pins that strict and required findings // survive the transient tables a dotted descent builds. func TestTargetedStrictThroughDottedKeys(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte("tab.zz = 1\n"), &cfg, RejectUnknownFields(true)) if err == nil || !strings.Contains(err.Error(), `unknown field "zz"`) { t.Errorf("err = %v, want the strict failure through the dotted key", err) } if err == nil || !strings.HasPrefix(err.Error(), "interpres: tab:") { t.Errorf("err = %v, want the path through the dotted key", err) } } // TestTargetedRequiredThroughDottedKeys pins that a required tag is honoured // when the table is reached only through dotted keys. func TestTargetedRequiredThroughDottedKeys(t *testing.T) { type nested struct { X int `toml:"x,required"` Y int `toml:"y"` } var cfg struct { Tab nested `toml:"tab"` } err := Unmarshal([]byte("tab.y = 1\n"), &cfg) if err == nil || !strings.Contains(err.Error(), `missing required key "x"`) { t.Errorf("err = %v, want the missing required key through the dotted key", err) } } // TestTargetedOrderedMapSliceFallsBack pins that a slice of OrderedMap // elements takes the tree path, whose fill keeps the written order. func TestTargetedOrderedMapSliceFallsBack(t *testing.T) { var cfg struct { Items []OrderedMap `toml:"items"` } err := Unmarshal([]byte("[[items]]\nk = \"v\"\n"), &cfg) if err != nil { t.Fatalf("unmarshal: %v", err) } if len(cfg.Items) != 1 || cfg.Items[0].Keys()[0] != "k" { t.Errorf("items = %#v, want the element filled in written order", cfg.Items) } } // hookMap is a named map type whose decode hook counts its calls. type hookMap map[string]any var hookMapCalls atomic.Int32 func (h *hookMap) UnmarshalTOML(data any) error { hookMapCalls.Add(1) m, _ := data.(map[string]any) if *h == nil { *h = hookMap{} } maps.Copy((*h), m) return nil } // TestTargetedMapFieldHookGetsWholeTable pins that a named map field with a // decode hook receives the whole parsed table, even in its header form. func TestTargetedMapFieldHookGetsWholeTable(t *testing.T) { type cfg struct { M hookMap `toml:"m"` } var c cfg hookMapCalls.Store(0) err := Unmarshal([]byte("[m]\na = 1\nb = 2\n"), &c) if err != nil { t.Fatalf("unmarshal: %v", err) } if hookMapCalls.Load() != 1 { t.Errorf("hook calls = %d, want exactly one with the whole table", hookMapCalls.Load()) } if c.M["a"] != int64(1) || c.M["b"] != int64(2) { t.Errorf("m = %#v", c.M) } } // errHook fails every decode with a fixed error and counts its calls. type errHook struct{ calls *int } func (e *errHook) UnmarshalTOML(any) error { if e.calls != nil { *e.calls++ } return errors.New("boom") } // TestTargetedHookErrorRunsOnce pins that a failing hook's error is the // tree path's own, wrapped with the key, and that the hook is not run a // second time by a fallback. func TestTargetedHookErrorRunsOnce(t *testing.T) { calls := 0 cfg := struct { F errHook `toml:"f"` }{F: errHook{calls: &calls}} err := Unmarshal([]byte("f = 1\n"), &cfg) if err == nil || err.Error() != "interpres: f: unmarshal: boom" { t.Errorf("err = %v, want the wrapped hook failure", err) } if calls != 1 { t.Errorf("hook calls = %d, want one", calls) } } // TestTargetedUnknownBeforeRequired pins the report order the tree decode // produces: an unknown key wins over a missing required one. func TestTargetedUnknownBeforeRequired(t *testing.T) { type inner struct { X int `toml:"x,required"` } var cfg struct { Tab inner `toml:"tab"` } err := Unmarshal([]byte("[tab]\nzz = 1\n"), &cfg, RejectUnknownFields(true)) if err == nil || !strings.Contains(err.Error(), `unknown field "zz"`) { t.Errorf("err = %v, want the unknown key reported before the required one", err) } } // TestTargetedStrictPathStableAcrossHeaders pins that the path a strict // finding wraps does not alias the parser's key buffer: the table that owns // the unknown key keeps its name after a later header. func TestTargetedStrictPathStableAcrossHeaders(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte("[tab]\nzz = 1\n\n[lims]\nx = 1\n"), &cfg, RejectUnknownFields(true)) if err == nil || !strings.HasPrefix(err.Error(), "interpres: tab:") { t.Errorf("err = %v, want the finding on tab, not the later header", err) } } // TestTargetedPrefilledMapFieldMergesUnderHeader pins that a prefilled map // field merges the document's header-form table into it on both paths, the // rule the root map has always followed. func TestTargetedPrefilledMapFieldMergesUnderHeader(t *testing.T) { doc := []byte("[lims]\nnew = 3\n") var ref, tgt targetCfg ref.Lims = map[string]any{"keep": "yes"} if err := treeDecodeInto(doc, &ref); err != nil { t.Fatalf("tree decode: %v", err) } tgt.Lims = map[string]any{"keep": "yes"} if err := Unmarshal(doc, &tgt); err != nil { t.Fatalf("unmarshal: %v", err) } if !reflect.DeepEqual(ref, tgt) { t.Errorf("targeted = %#v, tree = %#v", tgt, ref) } if tgt.Lims["keep"] != "yes" || tgt.Lims["new"] != int64(3) { t.Errorf("lims = %#v, want the merge", tgt.Lims) } } // TestTargetedNumberTokenValidatesUTF8 pins that the token route the // targeted parse takes reports invalid UTF-8 with the scanner's own message // and position. func TestTargetedNumberTokenValidatesUTF8(t *testing.T) { var cfg targetCfg err := Unmarshal([]byte("num = 12\xff\n"), &cfg) if err == nil || !strings.Contains(err.Error(), "invalid UTF-8 in value at byte offset 8") { t.Errorf("err = %v, want the UTF-8 complaint on the invalid byte", err) } }