fix(decode): report the smallest unknown field deterministically
Assisted-by: GLM 5.3
This commit is contained in:
@@ -45,6 +45,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Strict decoding reports the smallest unknown key. Several unknown keys in
|
||||
one table made the message depend on Go's random map iteration order, so
|
||||
the same document reported different keys across runs.
|
||||
- A `MarshalTOML` result of `nil` with a nil error fails the marshal with
|
||||
`MarshalTOML returned a nil value`. The field silently vanished before, and
|
||||
inside a value array the nil result reached reflection as a zero value and
|
||||
|
||||
@@ -107,12 +107,25 @@ func (d *decoder) assignTable(tbl map[string]any, dst reflect.Value) error {
|
||||
|
||||
func (d *decoder) assignStruct(tbl map[string]any, dst reflect.Value) error {
|
||||
schema := cachedStructSchema(dst.Type())
|
||||
if d.disallowUnknown {
|
||||
// Map iteration order is random, so pick the unknown key to report
|
||||
// deterministically: the smallest one.
|
||||
unknown := ""
|
||||
for key := range tbl {
|
||||
if _, ok := schema.byName[strings.ToLower(key)]; ok {
|
||||
continue
|
||||
}
|
||||
if unknown == "" || key < unknown {
|
||||
unknown = key
|
||||
}
|
||||
}
|
||||
if unknown != "" {
|
||||
return fmt.Errorf("interpres: unknown field %q for %s", unknown, dst.Type())
|
||||
}
|
||||
}
|
||||
for key, val := range tbl {
|
||||
field, ok := schema.byName[strings.ToLower(key)]
|
||||
if !ok {
|
||||
if d.disallowUnknown {
|
||||
return fmt.Errorf("interpres: unknown field %q for %s", key, dst.Type())
|
||||
}
|
||||
if schema.embedMaps != nil {
|
||||
// Leftover keys land in an untagged embedded map, the inverse
|
||||
// of the encoder inlining that map's entries.
|
||||
|
||||
+2
-1
@@ -186,7 +186,8 @@ A typo such as `database_urls` then fails with
|
||||
`interpres: unknown field "database_urls" for main.Config` instead of a silent
|
||||
default-zero run. Strictness applies to every struct the decode reaches, at any
|
||||
depth, including struct elements inside slices; map destinations accept every
|
||||
key by nature.
|
||||
key by nature. When several keys are unknown, the message names the smallest
|
||||
one, so it does not depend on map iteration order.
|
||||
|
||||
### Cancellation
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package interpres
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -316,6 +317,26 @@ func TestDisallowUnknownFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisallowUnknownFieldsReportsSmallestKey(t *testing.T) {
|
||||
// Map iteration order is random, so the reported key must be chosen
|
||||
// deterministically: the smallest unknown key, whichever order the map
|
||||
// iterates in.
|
||||
type C struct {
|
||||
Known string `toml:"known"`
|
||||
}
|
||||
data := []byte("known = \"x\"\nzeta = 1\nalpha = 2\nmu = 3\n")
|
||||
for range 20 {
|
||||
var c C
|
||||
err := NewDecoder().DisallowUnknownFields().Decode(data, &c)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown fields")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `unknown field "alpha"`) {
|
||||
t.Fatalf("err = %v, want the smallest unknown key alpha", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkippedFieldTag(t *testing.T) {
|
||||
type C struct {
|
||||
Keep string `toml:"keep"`
|
||||
|
||||
Reference in New Issue
Block a user