feat: add OffsetDateTime, nesting limits and uniform Marshaler dispatch
Test / test (push) Successful in 2m18s

Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
2026-09-19 19:36:24 +02:00
parent b695b69768
commit a6e3e3fe31
12 changed files with 631 additions and 117 deletions
+127
View File
@@ -951,3 +951,130 @@ func TestQuotedStringNeverBecomesDateTime(t *testing.T) {
t.Fatal("expected a quoted string to be rejected for LocalDate")
}
}
func TestDecoderMaxDepth(t *testing.T) {
deep := func(n int) []byte {
return []byte("v = " + strings.Repeat("[", n) + strings.Repeat("]", n) + "\n")
}
var cfg struct {
V any `toml:"v"`
}
if err := NewDecoder().MaxDepth(4).Decode(deep(4), &cfg); err != nil {
t.Fatalf("at the limit: %v", err)
}
err := NewDecoder().MaxDepth(4).Decode(deep(5), &cfg)
if err == nil {
t.Fatal("expected a nesting error")
}
if !strings.Contains(err.Error(), "limit of 4") {
t.Errorf("err = %v, want it to name the limit", err)
}
}
func TestDecoderMaxInputSize(t *testing.T) {
doc := []byte("v = \"ab\"\n")
var cfg struct {
V string `toml:"v"`
}
if err := NewDecoder().MaxInputSize(len(doc)).Decode(doc, &cfg); err != nil {
t.Fatalf("at the limit: %v", err)
}
err := NewDecoder().MaxInputSize(len(doc)-1).Decode(doc, &cfg)
if err == nil {
t.Fatal("expected a size error")
}
if !strings.Contains(err.Error(), "over the limit of 8") {
t.Errorf("err = %v, want it to name the limit", err)
}
// Parse carries the nesting default and no size limit.
if _, err := Parse(doc); err != nil {
t.Fatalf("parse: %v", err)
}
}
// --- OffsetDateTime --------------------------------------------------------
func TestOffsetDateTimeIsTheParsedType(t *testing.T) {
// A document's offset date-time arrives as the wrapper, and a plain
// time.Time destination still takes it, so a timestamp field needs no
// change to keep working.
in := []byte("stamp = 2026-06-26T10:00:00-07:00\n")
want := time.Date(2026, 6, 26, 10, 0, 0, 0, time.FixedZone("", -7*3600))
var plain struct {
Stamp time.Time `toml:"stamp"`
}
if err := Unmarshal(in, &plain); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if !plain.Stamp.Equal(want) {
t.Errorf("time.Time destination = %v, want %v", plain.Stamp, want)
}
var wrapped struct {
Stamp OffsetDateTime `toml:"stamp"`
}
if err := Unmarshal(in, &wrapped); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if !wrapped.Stamp.Time.Equal(want) {
t.Errorf("OffsetDateTime destination = %v, want %v", wrapped.Stamp.Time, want)
}
if got := wrapped.Stamp.String(); got != "2026-06-26T10:00-07:00" {
t.Errorf("String() = %q, want 2026-06-26T10:00-07:00", got)
}
}
func TestOffsetDateTimeFromHandBuiltTree(t *testing.T) {
// A tree built by hand may carry a plain time.Time, which is the other
// source of the offset kind; both date-time destinations take it.
tree := map[string]any{"stamp": time.Date(2026, 6, 26, 10, 0, 0, 0, time.UTC)}
want := time.Date(2026, 6, 26, 10, 0, 0, 0, time.UTC)
var plain struct {
Stamp time.Time `toml:"stamp"`
}
if err := newDecoder().decode(tree, &plain); err != nil {
t.Fatalf("decode: %v", err)
}
if !plain.Stamp.Equal(want) {
t.Errorf("time.Time destination = %v, want %v", plain.Stamp, want)
}
var wrapped struct {
Stamp OffsetDateTime `toml:"stamp"`
}
if err := newDecoder().decode(tree, &wrapped); err != nil {
t.Fatalf("decode: %v", err)
}
if !wrapped.Stamp.Time.Equal(want) {
t.Errorf("OffsetDateTime destination = %v, want %v", wrapped.Stamp.Time, want)
}
}
// dateKindReceiver records the Go type UnmarshalTOML was handed.
type dateKindReceiver struct{ Kind string }
func (r *dateKindReceiver) UnmarshalTOML(data any) error {
r.Kind = fmt.Sprintf("%T", data)
return nil
}
func TestUnmarshalerReceivesOffsetDateTime(t *testing.T) {
// The interface sees the wrapper, which names the date-time kind on its
// own; the local kinds keep their own wrappers.
var cfg struct {
O dateKindReceiver `toml:"o"`
L dateKindReceiver `toml:"l"`
}
in := []byte("o = 2026-06-26T10:00:00Z\nl = 2026-06-26T10:00:00\n")
if err := Unmarshal(in, &cfg); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if cfg.O.Kind != "interpres.OffsetDateTime" {
t.Errorf("offset kind = %q, want interpres.OffsetDateTime", cfg.O.Kind)
}
if cfg.L.Kind != "interpres.LocalDateTime" {
t.Errorf("local kind = %q, want interpres.LocalDateTime", cfg.L.Kind)
}
}