feat: add the required tag option and UnmarshalerContext
Test / test (push) Canceled after 39s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-22 00:04:16 +02:00
parent 2e5dfc54c9
commit 0ba145ba0c
5 changed files with 250 additions and 16 deletions
+107
View File
@@ -1316,3 +1316,110 @@ func TestDecodeFixedArray(t *testing.T) {
}
})
}
func TestRequiredTag(t *testing.T) {
type Config struct {
Host string `toml:"host,required"`
Radius int `toml:"radius"`
}
t.Run("a present key satisfies the tag", func(t *testing.T) {
var cfg Config
if err := Unmarshal([]byte("radius = 2\nhost = \"example.org\"\n"), &cfg); err != nil {
t.Fatal(err)
}
if cfg.Host != "example.org" || cfg.Radius != 2 {
t.Errorf("decoded %+v", cfg)
}
})
t.Run("a missing key is an error", func(t *testing.T) {
var cfg Config
err := Unmarshal([]byte("radius = 2\n"), &cfg)
want := `interpres: missing required key "host"`
if err == nil || err.Error() != want {
t.Errorf("err = %v, want %q", err, want)
}
})
t.Run("the error carries the key path", func(t *testing.T) {
var outer struct {
Server Config `toml:"server"`
}
err := Unmarshal([]byte("[server]\nradius = 1\n"), &outer)
want := `server: interpres: missing required key "host"`
if err == nil || err.Error() != want {
t.Errorf("err = %v, want %q", err, want)
}
})
t.Run("case-insensitive match satisfies the tag", func(t *testing.T) {
var cfg Config
if err := Unmarshal([]byte("HOST = \"x\"\n"), &cfg); err != nil {
t.Errorf("err = %v, want nil", err)
}
})
}
type ctxRecorder struct {
got context.Context
value any
}
func (r *ctxRecorder) UnmarshalTOMLContext(ctx context.Context, data any) error {
r.got = ctx
r.value = data
return nil
}
func TestUnmarshalerContext(t *testing.T) {
t.Run("the context reaches the method", func(t *testing.T) {
type keyT struct{}
ctx := context.WithValue(context.Background(), keyT{}, "sentinel")
var r ctxRecorder
if err := UnmarshalContext(ctx, []byte("a = 1\n"), &r); err != nil {
t.Fatal(err)
}
if v, _ := r.got.Value(keyT{}).(string); v != "sentinel" {
t.Errorf("ctx = %v, want the caller's context", r.got)
}
tree, isMap := r.value.(map[string]any)
if !isMap || tree["a"] != int64(1) {
t.Errorf("value = %#v, want the tree with a = 1", r.value)
}
})
t.Run("the context wins over Unmarshaler", func(t *testing.T) {
var v struct {
R ctxBoth `toml:"r"`
}
if err := Unmarshal([]byte("r = 1\n"), &v); err != nil {
t.Fatal(err)
}
if !v.R.ctxCalled {
t.Error("UnmarshalTOMLContext was not called")
}
if v.R.plainCalled {
t.Error("UnmarshalTOML was called although the context method exists")
}
})
t.Run("a non-cancellable entry point hands in Background", func(t *testing.T) {
var r ctxRecorder
if err := Unmarshal([]byte("a = 1\n"), &r); err != nil {
t.Fatal(err)
}
if r.got != context.Background() {
t.Errorf("ctx = %v, want context.Background", r.got)
}
})
}
type ctxBoth struct {
ctxCalled bool
plainCalled bool
}
func (b *ctxBoth) UnmarshalTOMLContext(ctx context.Context, data any) error {
b.ctxCalled = true
return nil
}
func (b *ctxBoth) UnmarshalTOML(data any) error {
b.plainCalled = true
return nil
}