test: pin the embedded map and map merge rules
Test / test (push) Successful in 1m34s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-22 00:23:41 +02:00
parent 10391a090f
commit 13ac6dd521
2 changed files with 59 additions and 2 deletions
+54
View File
@@ -1423,3 +1423,57 @@ func (b *ctxBoth) UnmarshalTOML(data any) error {
b.plainCalled = true b.plainCalled = true
return nil return nil
} }
func TestEmbeddedMapRule(t *testing.T) {
t.Run("only the first embedded map takes the leftover keys", func(t *testing.T) {
type ExtraMap map[string]any
type MoreMap map[string]any
type Config struct {
Port int
ExtraMap
MoreMap
}
var cfg Config
in := []byte("port = 8080\nlang = \"cs\"\nregion = \"EU\"\n")
if err := Unmarshal(in, &cfg); err != nil {
t.Fatal(err)
}
if cfg.Port != 8080 {
t.Errorf("port = %d", cfg.Port)
}
if cfg.ExtraMap["lang"] != "cs" || cfg.ExtraMap["region"] != "EU" {
t.Errorf("extra = %v, want the leftover keys", cfg.ExtraMap)
}
if len(cfg.MoreMap) != 0 {
t.Errorf("more = %v, want empty: only the first embedded map is the filler", cfg.MoreMap)
}
})
t.Run("a tagged embedded map is an ordinary field", func(t *testing.T) {
type Config struct {
Extra map[string]any `toml:"extra"`
}
var cfg Config
if err := Unmarshal([]byte("extra = {a = 1}\n"), &cfg); err != nil {
t.Fatal(err)
}
if cfg.Extra["a"] != int64(1) {
t.Errorf("extra = %v", cfg.Extra)
}
})
}
func TestDecodeMergesIntoNonEmptyMap(t *testing.T) {
dst := map[string]any{"keep": "me", "port": 1}
if err := Unmarshal([]byte("port = 8080\nlang = \"cs\"\n"), &dst); err != nil {
t.Fatal(err)
}
if dst["keep"] != "me" {
t.Errorf("keep = %v, want the pre-existing key kept", dst["keep"])
}
if dst["port"] != int64(8080) {
t.Errorf("port = %v, want the document's value to win", dst["port"])
}
if dst["lang"] != "cs" {
t.Errorf("lang = %v, want the key added", dst["lang"])
}
}
+5 -2
View File
@@ -202,7 +202,8 @@ table with `Number`, which keeps the literal; see
- `*struct`, matched per the field rules below - `*struct`, matched per the field rules below
- `*map[string]any` or `*map[string]T`, keys become map keys and values decode - `*map[string]any` or `*map[string]T`, keys become map keys and values decode
into `T` recursively into `T` recursively; a map that already holds entries is merged into, the
document's values replacing same-named keys and the rest left standing
- `*OrderedMap`, the keys fill in the order the document wrote them; see - `*OrderedMap`, the keys fill in the order the document wrote them; see
[Ordered tables](#ordered-tables) [Ordered tables](#ordered-tables)
- `*any`, receives the whole parsed tree unchanged - `*any`, receives the whole parsed tree unchanged
@@ -220,7 +221,9 @@ For a struct destination, a TOML key matches a field as follows:
into the embedded struct and matches its own fields against the same keys, into the embedded struct and matches its own fields against the same keys,
mirroring how the encoder flattens it. A nil embedded pointer struct is mirroring how the encoder flattens it. A nil embedded pointer struct is
allocated on demand. An untagged embedded map receives the keys no field allocated on demand. An untagged embedded map receives the keys no field
claims. claims; when a struct embeds several untagged maps, the first one
declared takes all of them and the rest stay untouched, so the rule stays
predictable.
4. The key itself is lower-cased before lookup, so the match is 4. The key itself is lower-cased before lookup, so the match is
case-insensitive on both sides: `DATABASEURL` matches a field named case-insensitive on both sides: `DATABASEURL` matches a field named
`DatabaseUrl`. `DatabaseUrl`.