Files
interpres/CHANGELOG.md
T
2026-09-17 23:11:50 +02:00

178 lines
9.5 KiB
Markdown

# Changelog
All notable changes to **interpres** are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [development]
### Added
- TOML 1.1 support, on by default: date-times and times without seconds
(`07:32`, `1979-05-27T07:32`, normalised to full seconds on output), the
`\e` and `\xHH` escape sequences, and multi-line inline tables with
comments and trailing commas. The compliance suite runs in TOML 1.1 mode:
214 valid and 467 invalid cases, zero failures. Every TOML 1.0 document
parses exactly as before.
- `interpres-decode -validate [file ...]`: a validate mode beside the
toml-test adapter. It parses each named file, or stdin when none are named,
prints one line per invalid document to stderr, and exits 0 when all are
valid, 1 when one is not, and 2 on a usage or read failure. Install it with
`go install .../cmd/interpres-decode@latest`; releases still ship no
binaries.
- `DecodeError` and `EncodeError`: decode and encode failures are wrapped in
typed errors carrying the key path, read with `errors.AsType` instead of
parsing the message text. The rendered messages keep their shape; the only
visible change is that an encode failure on a top-level field no longer
gains a meaningless leading dot in its path.
- `omitzero` and `omitempty` tag options on encode: `toml:"name,omitzero"`
skips a field whose value is the zero value of its type (a type with an
`IsZero() bool` method decides through the method), and
`toml:"name,omitempty"` skips a nil or empty slice, array, or map. The
decoder ignores both options.
### Changed
- The compliance suite is [toml-test](https://github.com/toml-lang/toml-test)
v2.2.0, run in TOML 1.0 mode. The new corpus holds 205 valid and 474 invalid
cases (v1.6.0 had 185 and 371), and it caught the two documents the parser
still accepted, fixed below.
- The flattened struct layout the decoder consults is cached per struct type
and shared with the encoder, which now resolves duplicate field keys with
it. Strict decoding of an array of tables of structs runs about a quarter
faster; marshalling structs gained the same layout without measurable cost.
- The parser scans the input bytes in place instead of building a `[]rune`
copy of the document: every character that drives the grammar is ASCII and
the input is validated UTF-8 up front, so the conversion pass and its four
bytes per rune were pure overhead. Parsing a large array-of-tables document
runs about a fifth faster and allocates about half the memory.
### 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
panicked.
- Decoding into a struct that embeds a pointer to itself terminates. The
schema walk recursed through the embedded type forever, so such a
`Unmarshal` call hung the process; the walk now tracks the struct types on
the current path and stops when one repeats.
- `Marshal` emits exactly one key when two struct fields resolve to the same
TOML name, picking the field the decoder would fill (the shallower one, the
later declaration at equal depth). Such a struct previously marshalled into
a duplicate key, and the output never re-parsed, breaking the round-trip
guarantee.
- An array-of-tables header whose path runs through an inline table
(`a = {b = {}}` followed by `[[a.b.c]]`) is rejected. The frozen-inline-table
check covered `[table]` headers and dotted keys but not the intermediate
steps of an array-of-tables header, so such a document silently extended the
inline table.
- A new element of an array of tables starts a fresh scope for dotted-key paths
and nested arrays of tables: `[[a]]`, `b.c = 1`, `[[a]]`, `[a.b]` parses, as
the TOML examples in the spec shape it. The records of the previous element
falsely rejected the same paths in the next one.
- `Marshal` returns an error for a table header key or an inline-table key that
is not valid UTF-8, the way scalar keys already did, instead of silently
emitting corrupt TOML (a header that lost its key, an inline table with a
missing key).
- `UseLiteralMultiline` falls back to the escaped basic string when the value
cannot be carried verbatim by the literal form: a run of three single quotes,
a control character, or a lone carriage return. Such values previously
produced output that did not re-parse.
- A `[]any` holding only tables marshals in the value-array form with inline
tables, keeping the type `Parse` produces for such an array. It previously
took the `[[header]]` form, so a round-trip changed the value's type from
`[]any` to `[]map[string]any`.
- Decoding into a `uint` destination checks the type's platform width instead
of only the fixed widths, so a 32-bit `uint` no longer truncates silently;
decoding a finite float beyond the `float32` range is an overflow error
instead of a silent infinity.
- Struct fields that resolve to one key at equal depth decode through the
field declared later, matching the documented rule; the first one won before.
- A float with an exponent marker but no digits (`1e`, `0.0E`) is rejected;
the exponent requires at least one digit.
- A date-time offset outside 00:00 through 23:59 is rejected; such offsets
were accepted and silently rolled over (`+00:60` decoded as `+01:00`).
- Untagged embedded fields now decode symmetrically with encode: an embedded
struct receives its keys inline (a nil embedded pointer struct is
allocated), an embedded map catches the keys no field claims, and a name
clash resolves in favour of the shallower field. A struct with an untagged
embedded field previously decoded with all inline keys dropped and did not
round-trip.
- `Marshal` re-emits arrays that mix tables with scalars: the table elements
render as inline tables inside the value array. A tree that `Parse` accepts
from such a document previously failed with
`cannot encode map[string]interface {}`.
## [1.0.0] - 2026-08-20
First stable release: a dependency-free TOML 1.0 parser and encoder for Go that
uses only the standard library and passes the entire
[toml-test](https://github.com/toml-lang/toml-test) suite
(**185 valid and 371 invalid cases, 0 failures**).
### Added
**Parsing**
- `Parse(data []byte) (map[string]any, error)`: decode a TOML document into an
untyped tree.
- Full TOML 1.0 syntax: comments; bare, quoted, and dotted keys; tables
(`[a.b]`) and arrays of tables (`[[a]]`); basic and literal strings, including
multiline (`"""` / `'''`) with escape sequences and line-ending backslash
trimming; integers in decimal, hex (`0x`), octal (`0o`), and binary (`0b`)
with `_` separators; floats with exponents and `inf` / `nan`; booleans;
offset/local date-times, dates, and times; arrays; and inline tables.
- Distinct date-time types: offset date-times decode to `time.Time`, while
`LocalDateTime`, `LocalDate`, and `LocalTime` represent the local variants,
each with a `String()` method returning the TOML-canonical rendering.
- Strict, spec-conformant validation that rejects invalid documents: leading
zeros, misplaced underscores, malformed floats and radix literals, control
characters in strings and comments, bare carriage returns, non-UTF-8 input,
out-of-range Unicode escapes, multiline strings used as keys, single-digit
date-time components, duplicate/overwriting inline-table keys, and the full
family of table redefinitions (header vs. header, header vs. dotted key,
array of tables vs. table, and dotted-key appends to defined tables).
- `SyntaxError` carrying the 1-based line of a malformed document.
**Decoding**
- `Unmarshal(data []byte, v any)`: parse and map onto a struct or
`map[string]any` via reflection, with overflow-checked numeric conversion,
nested structs, slices, and `map[string]T`.
- `toml:"name"` field tags, case-insensitive name fallback, and `toml:"-"` to
skip a field.
- `Decoder` with `DisallowUnknownFields` for strict decoding that rejects keys
without a destination field, at every struct depth.
- `Unmarshaler` interface (`UnmarshalTOML(data any) error`) for types that take
full control of their decode.
**Encoding**
- `Marshal(v any) ([]byte, error)`: encode a struct or `map[string]V` value to
a TOML 1.0 document that re-parses to an equivalent value tree.
- `Encoder` with chainable policy options: `GroupByKind` (group-by-kind layout
versus declaration order), `OmitEmptyArrays`, and `UseLiteralMultiline`.
- `Marshaler` interface (`MarshalTOML() (any, error)`) for types that need a
custom TOML shape; the returned value is encoded normally.
**Cancellation**
- `ParseContext`, `UnmarshalContext`, `MarshalContext`,
`(*Decoder).DecodeContext` and `(*Encoder).MarshalContext` honour a
`context.Context`, checked up front and every 64 statements or fields.
**Project**
- Standard library only: zero third-party modules.
- `cmd/interpres-decode`, a toml-test harness adapter (TOML on stdin, tagged
JSON on stdout).
- A runnable example, a table-driven Go test suite, and a canonical `just`
recipe set whose `gates` recipe is the definition of done.
- Hand-written CI pipelines for the test, race and release gates.
- `SECURITY.md` for private vulnerability reports.