feat: bound the encoder walk and add UnmarshalWithOptions
Test / test (push) Successful in 1m34s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-22 00:21:46 +02:00
parent eaa69dc6f6
commit 10391a090f
5 changed files with 219 additions and 18 deletions
+102
View File
@@ -2024,3 +2024,105 @@ func TestMarshalAppend(t *testing.T) {
t.Errorf("MarshalAppend with an unencodable value = %q, want an error", out3)
}
}
func TestMarshalCyclicData(t *testing.T) {
t.Run("a cyclic struct is an error, not a crash", func(t *testing.T) {
type Node struct {
Name string `toml:"name"`
Next *Node `toml:"next"`
}
a := &Node{Name: "a"}
b := &Node{Name: "b"}
a.Next = b
b.Next = a
_, err := Marshal(a)
if err == nil {
t.Fatal("Marshal(cyclic) succeeded, want an error")
}
if !strings.Contains(err.Error(), "may be cyclic") {
t.Errorf("err = %v, want it to name the cycle", err)
}
})
t.Run("a cyclic map is an error", func(t *testing.T) {
m := map[string]any{}
m["self"] = m
if _, err := Marshal(m); err == nil {
t.Fatal("Marshal(cyclic map) succeeded, want an error")
}
})
t.Run("a cyclic value array is an error", func(t *testing.T) {
m := map[string]any{}
m["items"] = []any{int64(1), m}
if _, err := Marshal(map[string]any{"outer": m}); err == nil {
t.Fatal("Marshal(cyclic array) succeeded, want an error")
}
})
t.Run("a deeply nested but finite value encodes", func(t *testing.T) {
type Node struct {
Next *Node `toml:"next"`
}
root := &Node{}
cur := root
for range 5000 {
cur.Next = &Node{}
cur = cur.Next
}
if _, err := Marshal(root); err != nil {
t.Errorf("Marshal(deep) = %v, want nil", err)
}
})
}
func TestUnmarshalWithOptions(t *testing.T) {
data := []byte("host = \"db\"\nextra = 1\n")
type Config struct {
Host string `toml:"host,required"`
}
t.Run("the zero value takes the defaults", func(t *testing.T) {
var cfg struct {
Host string `toml:"host"`
Extra int `toml:"extra"`
}
if err := UnmarshalWithOptions(data, &cfg, DecodeOptions{}); err != nil {
t.Fatal(err)
}
if cfg.Host != "db" || cfg.Extra != 1 {
t.Errorf("decoded %+v", cfg)
}
})
t.Run("strict and required work in one call", func(t *testing.T) {
err := UnmarshalWithOptions(data, &Config{}, DecodeOptions{DisallowUnknownFields: true})
want := `interpres: unknown field "extra" for interpres.Config`
if err == nil || err.Error() != want {
t.Errorf("err = %v, want %q", err, want)
}
})
t.Run("UseNumber keeps the literal", func(t *testing.T) {
var tree map[string]any
in := []byte("n = 1_000\n")
if err := UnmarshalWithOptions(in, &tree, DecodeOptions{UseNumber: true}); err != nil {
t.Fatal(err)
}
if got, ok := tree["n"].(Number); !ok || got != "1_000" {
t.Errorf("n = %#v, want Number(\"1_000\")", tree["n"])
}
})
t.Run("the limits apply", func(t *testing.T) {
var nested strings.Builder
nested.WriteString("x = ")
for range 20 {
nested.WriteString("[")
}
nested.WriteString("1")
for range 20 {
nested.WriteString("]")
}
var tree map[string]any
if err := UnmarshalWithOptions([]byte(nested.String()), &tree, DecodeOptions{MaxDepth: 10}); err == nil {
t.Error("a document over MaxDepth decoded, want an error")
}
if err := UnmarshalWithOptions([]byte("a = 1\n"), &tree, DecodeOptions{MaxInputSize: 2}); err == nil {
t.Error("a document over MaxInputSize decoded, want an error")
}
})
}