feat: add Decoder.UseNumber and the Number type
Test / test (push) Successful in 1m52s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-21 23:49:39 +02:00
parent 582222738b
commit ce0c1ebd9d
9 changed files with 320 additions and 4 deletions
+123
View File
@@ -1097,3 +1097,126 @@ func TestUnmarshalerReceivesOffsetDateTime(t *testing.T) {
t.Errorf("local kind = %q, want interpres.LocalDateTime", cfg.L.Kind)
}
}
func TestDecoderUseNumber(t *testing.T) {
data := []byte(`hex = 0x1f
sep = 1_000
signed = +1.0
exp = 1e6
posinf = inf
negzero = -0.0
plain = 42
frac = 2.5
`)
t.Run("the tree keeps the literal", func(t *testing.T) {
var tree map[string]any
if err := NewDecoder().UseNumber().Decode(data, &tree); err != nil {
t.Fatal(err)
}
for lit, key := range map[string]string{
"0x1f": "hex", "1_000": "sep", "+1.0": "signed", "1e6": "exp",
"inf": "posinf", "-0.0": "negzero", "42": "plain", "2.5": "frac",
} {
got, ok := tree[key].(Number)
if !ok {
t.Errorf("%s = %T, want Number", key, tree[key])
continue
}
if string(got) != lit {
t.Errorf("%s = %q, want %q", key, got, lit)
}
}
})
t.Run("typed fields take the evaluated value", func(t *testing.T) {
var cfg struct {
Hex Number `toml:"hex"`
Plain int64 `toml:"plain"`
Frac float64 `toml:"frac"`
Rate time.Duration
}
dec := NewDecoder().UseNumber()
if err := dec.Decode([]byte("hex = 0x1f\nplain = 42\nfrac = 2.5\nRate = 1_000\n"), &cfg); err != nil {
t.Fatal(err)
}
if cfg.Hex != "0x1f" {
t.Errorf("hex = %q, want 0x1f", cfg.Hex)
}
if cfg.Plain != 42 {
t.Errorf("plain = %d, want 42", cfg.Plain)
}
if cfg.Frac != 2.5 {
t.Errorf("frac = %g, want 2.5", cfg.Frac)
}
if cfg.Rate != 1000 {
t.Errorf("rate = %s, want 1µs", cfg.Rate)
}
})
t.Run("invalid numbers are still parse errors", func(t *testing.T) {
for _, in := range []string{"a = 01\n", "a = 1__0\n", "a = 1x\n"} {
var tree map[string]any
if err := NewDecoder().UseNumber().Decode([]byte(in), &tree); err == nil {
t.Errorf("%q decoded without an error", in)
}
}
})
t.Run("without UseNumber the tree holds the evaluated kinds", func(t *testing.T) {
var tree map[string]any
if err := NewDecoder().Decode([]byte("hex = 0x1f\nfrac = 2.5\n"), &tree); err != nil {
t.Fatal(err)
}
if v, ok := tree["hex"].(int64); !ok || v != 31 {
t.Errorf("hex = %#v, want int64 31", tree["hex"])
}
if v, ok := tree["frac"].(float64); !ok || v != 2.5 {
t.Errorf("frac = %#v, want float64 2.5", tree["frac"])
}
})
}
func TestNumberMethods(t *testing.T) {
tests := []struct {
lit Number
wantI int64
wantF float64
intErr bool
}{
{lit: "42", wantI: 42, wantF: 42},
{lit: "0x1f", wantI: 31, wantF: 31},
{lit: "1_000", wantI: 1000, wantF: 1000},
{lit: "+1.0", wantF: 1, intErr: true},
{lit: "1e6", wantF: 1e6, intErr: true},
{lit: "inf", wantF: math.Inf(1), intErr: true},
{lit: "-2.5", wantF: -2.5, intErr: true},
}
for _, tt := range tests {
i, err := tt.lit.Int64()
if tt.intErr && err == nil {
t.Errorf("%q.Int64() succeeded with %d, want an error", tt.lit, i)
}
if !tt.intErr {
if err != nil {
t.Errorf("%q.Int64() = %v", tt.lit, err)
continue
}
if i != tt.wantI {
t.Errorf("%q.Int64() = %d, want %d", tt.lit, i, tt.wantI)
}
}
f, err := tt.lit.Float64()
if err != nil {
t.Errorf("%q.Float64() = %v", tt.lit, err)
continue
}
if f != tt.wantF {
t.Errorf("%q.Float64() = %g, want %g", tt.lit, f, tt.wantF)
}
}
for _, lit := range []Number{"01", "1__0", "abc", ""} {
if _, err := lit.Float64(); err == nil {
t.Errorf("%q.Float64() succeeded, want an error", lit)
}
if _, err := lit.Int64(); err == nil {
t.Errorf("%q.Int64() succeeded, want an error", lit)
}
}
}