Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -46,6 +46,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
(10000 levels, which no hand-written document approaches): a document that
|
||||
nests arrays or inline tables deeper used to run the stack out and is now
|
||||
rejected with a `SyntaxError` naming the limit.
|
||||
- A TOML array decodes into a Go fixed-size array, `[N]T`, where only a slice
|
||||
was accepted before; the encoder could already encode one. A length mismatch
|
||||
is an error wrapped with the key path.
|
||||
- `MarshalAppend(buf, v)` appends the TOML encoding of v to buf and returns
|
||||
the extended buffer, the shape `json.MarshalAppend` has.
|
||||
- `ParseFile(path)` reads the file and parses it into a `Document`, with the
|
||||
file name at the front of every error it returns, read failure and parse
|
||||
failure alike. `Valid(data)` reports whether a document parses, nil on
|
||||
|
||||
@@ -309,31 +309,58 @@ func (d *decoder) assignMap(tbl map[string]any, dst reflect.Value) error {
|
||||
}
|
||||
|
||||
func (d *decoder) assignSlice(items []any, dst reflect.Value) error {
|
||||
if dst.Kind() != reflect.Slice {
|
||||
switch dst.Kind() {
|
||||
case reflect.Slice:
|
||||
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
||||
for i, item := range items {
|
||||
if err := d.assign(item, out.Index(i)); err != nil {
|
||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||
}
|
||||
}
|
||||
dst.Set(out)
|
||||
return nil
|
||||
case reflect.Array:
|
||||
// A fixed-size array takes the elements in place; a length mismatch is
|
||||
// the error, because a TOML array carries no way to name a default for
|
||||
// the elements it is short of, and the surplus has nowhere to go.
|
||||
if dst.Len() != len(items) {
|
||||
return fmt.Errorf("interpres: cannot assign %d elements to %s", len(items), dst.Type())
|
||||
}
|
||||
for i, item := range items {
|
||||
if err := d.assign(item, dst.Index(i)); err != nil {
|
||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("interpres: cannot assign array to %s", dst.Type())
|
||||
}
|
||||
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
||||
for i, item := range items {
|
||||
if err := d.assign(item, out.Index(i)); err != nil {
|
||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||
}
|
||||
}
|
||||
dst.Set(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) assignTableSlice(items []map[string]any, dst reflect.Value) error {
|
||||
if dst.Kind() != reflect.Slice {
|
||||
switch dst.Kind() {
|
||||
case reflect.Slice:
|
||||
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
||||
for i, item := range items {
|
||||
if err := d.assign(item, out.Index(i)); err != nil {
|
||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||
}
|
||||
}
|
||||
dst.Set(out)
|
||||
return nil
|
||||
case reflect.Array:
|
||||
if dst.Len() != len(items) {
|
||||
return fmt.Errorf("interpres: cannot assign %d elements to %s", len(items), dst.Type())
|
||||
}
|
||||
for i, item := range items {
|
||||
if err := d.assign(item, dst.Index(i)); err != nil {
|
||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("interpres: cannot assign array of tables to %s", dst.Type())
|
||||
}
|
||||
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
||||
for i, item := range items {
|
||||
if err := d.assign(item, out.Index(i)); err != nil {
|
||||
return newDecodeError(fmt.Sprintf("[%d]", i), err)
|
||||
}
|
||||
}
|
||||
dst.Set(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- low-level setters -----------------------------------------------------
|
||||
|
||||
@@ -1273,3 +1273,46 @@ func TestSourceLineEdgePositions(t *testing.T) {
|
||||
t.Errorf("SourceLine(offset past end) =\n%q\nwant:\n%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeFixedArray(t *testing.T) {
|
||||
t.Run("value array", func(t *testing.T) {
|
||||
var cfg struct {
|
||||
Ports [2]int `toml:"ports"`
|
||||
Label [2]string `toml:"label"`
|
||||
Grid [2][2]int64 `toml:"grid"`
|
||||
}
|
||||
in := []byte("ports = [8080, 9090]\nlabel = [\"a\", \"b\"]\ngrid = [[1, 2], [3, 4]]\n")
|
||||
if err := Unmarshal(in, &cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.Ports != [2]int{8080, 9090} || cfg.Label != [2]string{"a", "b"} || cfg.Grid != [2][2]int64{{1, 2}, {3, 4}} {
|
||||
t.Errorf("decoded %+v", cfg)
|
||||
}
|
||||
})
|
||||
t.Run("array of tables", func(t *testing.T) {
|
||||
type Item struct {
|
||||
Name string `toml:"name"`
|
||||
Qty int `toml:"qty"`
|
||||
}
|
||||
var cfg struct {
|
||||
Items [2]Item `toml:"items"`
|
||||
}
|
||||
in := []byte("[[items]]\nname = \"a\"\nqty = 1\n[[items]]\nname = \"b\"\nqty = 2\n")
|
||||
if err := Unmarshal(in, &cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.Items != [2]Item{{"a", 1}, {"b", 2}} {
|
||||
t.Errorf("decoded %+v", cfg)
|
||||
}
|
||||
})
|
||||
t.Run("a length mismatch is an error", func(t *testing.T) {
|
||||
var cfg struct {
|
||||
Ports [3]int `toml:"ports"`
|
||||
}
|
||||
err := Unmarshal([]byte("ports = [8080, 9090]\n"), &cfg)
|
||||
want := "ports: interpres: cannot assign 2 elements to [3]int"
|
||||
if err == nil || err.Error() != want {
|
||||
t.Errorf("err = %v, want %q", err, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+10
@@ -158,6 +158,11 @@ out, err := interpres.Marshal(cfg)
|
||||
The cancellable variant of `Marshal`. The context is checked before any work
|
||||
and every 64 fields during the reflection walk.
|
||||
|
||||
### `func MarshalAppend(buf []byte, v any) ([]byte, error)`
|
||||
|
||||
Appends the TOML encoding of `v` to `buf` and returns the extended buffer, the
|
||||
shape `json.MarshalAppend` has. A failed encoding leaves `buf` untouched.
|
||||
|
||||
## Decoding
|
||||
|
||||
### Value mapping
|
||||
@@ -282,6 +287,11 @@ destination is a slice, each element decodes into the slice's element type
|
||||
(`[]struct` or `[]map[string]V`); a mismatch on one element surfaces as an
|
||||
error wrapped with `[i]:` and the element index.
|
||||
|
||||
A value array also decodes into a fixed-size array, `[N]T`, the mirror of the
|
||||
encoder's ability to encode one. The element count has to match: an array
|
||||
whose length differs from `N` is an error, `interpres: cannot assign 2
|
||||
elements to [3]int`, wrapped with the key path.
|
||||
|
||||
### Custom decoding: `Unmarshaler`
|
||||
|
||||
A type that wants full control of its decode implements:
|
||||
|
||||
@@ -1998,3 +1998,29 @@ func TestMarshalNumber(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMarshalAppend(t *testing.T) {
|
||||
buf := []byte("preamble\n")
|
||||
out, err := MarshalAppend(buf, map[string]any{"a": int64(1)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "preamble\na = 1\n"
|
||||
if string(out) != want {
|
||||
t.Errorf("output %q, want %q", out, want)
|
||||
}
|
||||
if &out[0] != &buf[0] {
|
||||
t.Log("append reallocated; capacity differed")
|
||||
}
|
||||
out2, err := MarshalAppend(out, map[string]any{"b": true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(out2) != want+"b = true\n" {
|
||||
t.Errorf("second append %q", out2)
|
||||
}
|
||||
buf = []byte("keep\n")
|
||||
if out3, err := MarshalAppend(buf, Document{}); err == nil {
|
||||
t.Errorf("MarshalAppend with an unencodable value = %q, want an error", out3)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +399,17 @@ func Marshal(v any) ([]byte, error) {
|
||||
return MarshalContext(context.Background(), v)
|
||||
}
|
||||
|
||||
// MarshalAppend appends the TOML encoding of v to buf and returns the extended
|
||||
// buffer, the shape json.MarshalAppend has. A failed encoding leaves buf
|
||||
// untouched and comes back with a nil slice.
|
||||
func MarshalAppend(buf []byte, v any) ([]byte, error) {
|
||||
out, err := Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(buf, out...), nil
|
||||
}
|
||||
|
||||
// MarshalContext is the cancellable variant of Marshal.
|
||||
func MarshalContext(ctx context.Context, v any) ([]byte, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user