feat: add OffsetDateTime, nesting limits and uniform Marshaler dispatch
Test / test (push) Successful in 2m18s
Test / test (push) Successful in 2m18s
Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
+54
-26
@@ -74,7 +74,7 @@ and every 64 fields during the reflection walk.
|
||||
| integer | `int64` |
|
||||
| float | `float64` |
|
||||
| boolean | `bool` |
|
||||
| offset date-time | `time.Time` |
|
||||
| offset date-time | `OffsetDateTime` |
|
||||
| local date-time | `LocalDateTime` |
|
||||
| local date | `LocalDate` |
|
||||
| local time | `LocalTime` |
|
||||
@@ -133,22 +133,24 @@ The decoder converts to the destination type with explicit overflow checks:
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | the value must be non-negative and must not overflow the destination's own width, `uint` on a 32-bit platform included; `uint64` accepts any non-negative `int64` |
|
||||
| `float32`, `float64` | copied verbatim, except that a finite value beyond the `float32` range is an overflow error rather than a silent infinity; an integer also coerces, so TOML `5` decodes into `5.0` |
|
||||
| `bool`, `string` | exact kind match only, no coercion across kinds |
|
||||
| `time.Time` | offset date-times only; no implicit conversion to or from the local variants |
|
||||
| `time.Time`, `OffsetDateTime` | offset date-times only; no implicit conversion to or from the local variants |
|
||||
|
||||
A conversion that the rules do not allow produces an error wrapped with the
|
||||
offending key or index, for example `p: interpres: integer 300 overflows uint8`.
|
||||
|
||||
### Date-time values
|
||||
|
||||
Offset date-times decode into `time.Time` and keep their offset. The local
|
||||
variants decode into `LocalDateTime`, `LocalDate` and `LocalTime`, whose
|
||||
embedded `time.Time` is normalised to UTC (midnight UTC for a local date, the
|
||||
zero date for a local time). Every kind may omit the seconds as of TOML 1.1
|
||||
(`07:32`, `1979-05-27T07:32`); such a value carries a zero second, and the
|
||||
encoder writes the seconds only when the value carries them, so a document
|
||||
written without seconds comes back without them. There is no implicit
|
||||
Offset date-times decode into `OffsetDateTime`, whose embedded `time.Time` is the
|
||||
instant with the offset the document wrote; a destination of the plain
|
||||
`time.Time` takes the same value, so a timestamp field does not have to name the
|
||||
wrapper. The local variants decode into `LocalDateTime`, `LocalDate` and
|
||||
`LocalTime`, whose embedded `time.Time` is normalised to UTC (midnight UTC for a
|
||||
local date, the zero date for a local time). Every kind may omit the seconds as
|
||||
of TOML 1.1 (`07:32`, `1979-05-27T07:32`); such a value carries a zero second,
|
||||
and the encoder writes the seconds only when the value carries them, so a
|
||||
document written without seconds comes back without them. There is no implicit
|
||||
conversion between the offset and local kinds; assigning one to the other is an
|
||||
error. The four types take a bare timestamp and never a quoted string, so a
|
||||
error. The date-time types take a bare timestamp and never a quoted string, so a
|
||||
document that writes a date-time with quotes does not decode into them, and
|
||||
neither `encoding.TextUnmarshaler` nor the embedded `time.Time` changes that.
|
||||
|
||||
@@ -170,7 +172,7 @@ type Unmarshaler interface {
|
||||
```
|
||||
|
||||
`data` is whatever the parser produced for that key: `string`, `bool`, `int64`,
|
||||
`float64`, `time.Time`, `LocalDateTime`, `LocalDate`, `LocalTime`, `[]any`, or
|
||||
`float64`, `OffsetDateTime`, `LocalDateTime`, `LocalDate`, `LocalTime`, `[]any`, or
|
||||
`map[string]any`. The method inspects the value and mutates its own receiver;
|
||||
the decoder keeps whatever state the receiver stored.
|
||||
|
||||
@@ -201,7 +203,7 @@ prefix `unmarshal text:`, for example `addr: unmarshal text: not an address`.
|
||||
[`UnmarshalTOML`](#custom-decoding-unmarshaler) wins over `UnmarshalText` when
|
||||
a type implements both, and the four [date-time
|
||||
types](#date-time-values) are excluded: a quoted string stays a string and
|
||||
never becomes a `time.Time` or one of the local wrappers.
|
||||
never becomes an `OffsetDateTime` or one of the local wrappers.
|
||||
|
||||
### Durations
|
||||
|
||||
@@ -316,7 +318,7 @@ them.
|
||||
By default every table is emitted with its entries grouped by kind:
|
||||
|
||||
1. scalars (`string`, `int64`, `float64`, `bool`, `time.Time`,
|
||||
`LocalDateTime`, `LocalDate`, `LocalTime`)
|
||||
`OffsetDateTime`, `LocalDateTime`, `LocalDate`, `LocalTime`)
|
||||
2. sub-tables (structs and `map[string]V` values)
|
||||
3. arrays of tables (`[]struct` and `[]map[string]V`)
|
||||
|
||||
@@ -351,11 +353,20 @@ type Marshaler interface {
|
||||
The returned value is encoded as if it had been passed in place of the
|
||||
receiver, so it may be a scalar, a slice, an array of tables, or another
|
||||
struct or map, including the `Marshaler` result of another type; the encoder
|
||||
recurses. An error returned from `MarshalTOML` fails the marshal wrapped with
|
||||
the key path, for example `interpres: server.port: bad timestamp`. A result
|
||||
of `nil` with a nil error fails the same way with
|
||||
`MarshalTOML returned a nil value`: nil has no TOML representation, so
|
||||
dropping the field silently is not an option.
|
||||
recurses, and the result is normalised like any other value, so a method may
|
||||
return a plain `int` or a `time.Duration`.
|
||||
|
||||
An error returned from `MarshalTOML` fails the marshal wrapped with the key
|
||||
path, for example `interpres: server.port: bad timestamp`. A result of `nil` with
|
||||
a nil error fails the same way with `MarshalTOML returned a nil value`: nil has
|
||||
no TOML representation, so dropping the field silently is not an option.
|
||||
|
||||
The method is reached for every value the walk meets, array elements included:
|
||||
an element that renders itself as a table keeps the `[[header]]` form, one that
|
||||
renders itself as a scalar turns the array into a value array, and the method
|
||||
runs once per element. It is looked up on the value and on its address, so a
|
||||
pointer-receiver method is called for a field or an element, exactly as
|
||||
`MarshalText` is.
|
||||
|
||||
```go
|
||||
type Port int
|
||||
@@ -512,9 +523,11 @@ sequenceDiagram
|
||||
|
||||
### `type SyntaxError struct{ Line int; Msg string }`
|
||||
|
||||
Describes a malformed TOML document; `Line` is 1-based and `Error()` renders as
|
||||
`interpres: line N: msg`. Read the structured fields with a type assertion or
|
||||
`errors.AsType`:
|
||||
Describes a document the parser rejected, with the 1-based `Line` at which it
|
||||
gave up and `Error()` rendering as `interpres: line N: msg`. A malformed
|
||||
document is the usual cause; the nesting limit and an input that is not valid
|
||||
UTF-8 report through the same type. Read the structured fields with a type
|
||||
assertion or `errors.AsType`:
|
||||
|
||||
```go
|
||||
if se, ok := errors.AsType[*interpres.SyntaxError](err); ok {
|
||||
@@ -550,6 +563,19 @@ with `DisallowUnknownFields`, then call `Decode` or `DecodeContext` any number
|
||||
of times. A configured `Decoder` holds no per-call state and is safe for
|
||||
concurrent use.
|
||||
|
||||
| Method | Default | Effect |
|
||||
|---|---|---|
|
||||
| `DisallowUnknownFields()` | off | a key with no matching struct field is an error |
|
||||
| `MaxDepth(depth int)` | `10000` | bound how deeply arrays and inline tables may nest |
|
||||
| `MaxInputSize(size int)` | no limit | bound the size of the document, in bytes |
|
||||
|
||||
The nesting limit protects the stack, because the parser is a recursive
|
||||
descent: a deeper document is rejected with a `SyntaxError` naming the limit
|
||||
rather than running the stack out. `Parse` and `ParseContext` carry that same
|
||||
default but take no options. The size limit is off by default, because the
|
||||
caller already holds the bytes and the size is therefore a policy, not a
|
||||
protection the library can impose on its own.
|
||||
|
||||
### `type Encoder`
|
||||
|
||||
Configurable emission policy, constructed with `NewEncoder`. The option state
|
||||
@@ -587,9 +613,10 @@ See [Custom decoding](#custom-decoding-unmarshaler).
|
||||
### Date-time wrappers
|
||||
|
||||
```go
|
||||
type LocalDateTime struct{ time.Time } // 1979-05-27T07:32:00
|
||||
type LocalDate struct{ time.Time } // 1979-05-27
|
||||
type LocalTime struct{ time.Time } // 07:32:00.999999
|
||||
type OffsetDateTime struct{ time.Time } // 1979-05-27T07:32:00Z
|
||||
type LocalDateTime struct{ time.Time } // 1979-05-27T07:32:00
|
||||
type LocalDate struct{ time.Time } // 1979-05-27
|
||||
type LocalTime struct{ time.Time } // 07:32:00.999999
|
||||
```
|
||||
|
||||
Each carries a `String()` method returning the TOML-canonical rendering: the
|
||||
@@ -602,11 +629,12 @@ writes them through `String()`.
|
||||
|
||||
The entry points return:
|
||||
|
||||
- `*SyntaxError` for a malformed document, with the 1-based line
|
||||
- `*SyntaxError` for a malformed document, with the 1-based line; the nesting
|
||||
limit reports through it as well
|
||||
- `*DecodeError` for a decoding failure, with the key path in `Path`
|
||||
- `*EncodeError` for an encoding failure, with the key path in `Path`
|
||||
- a plain error for the rest: a non-pointer decode target, a cancelled
|
||||
context, a key that is not valid UTF-8
|
||||
context, a key that is not valid UTF-8, an input over the size limit
|
||||
|
||||
Decode and encode failures carry the key path or element index in the typed
|
||||
wrappers above, so `errors.Is` and `errors.AsType` see through them and the
|
||||
|
||||
Reference in New Issue
Block a user