feat(httputil): add Bind/BindEmpty request binding from path and query; v1.6.0

Bind and BindEmpty fill Req from path/query/json struct tags and validate
once, extending the typed decode->validate->call->encode pipeline to routes
with identifiers and filters. Conversion via builtins + encoding.TextUnmarshaler
(uuid.UUID, time.Time); malformed value -> 400 naming the parameter; default:
applies only when absent; repeated query -> slice; mis-tagged struct panics at
wiring. Purely additive; existing adapters unchanged. Coordinated lockstep v1.6.0.
This commit is contained in:
2026-08-13 23:11:23 -06:00
parent bcccfef443
commit a778227fc2
10 changed files with 920 additions and 14 deletions
+31
View File
@@ -6,6 +6,37 @@ This module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html
---
## [1.6.0] — 2026-08-13
Minor — request binding from path and query, not only the JSON body.
### Added
- **`httputil.Bind[Req, Res]`** and **`httputil.BindEmpty[Req]`** — a fourth adapter family
that fills `Req` from the path, the query string **and** the body, each field declaring its
source with a struct tag (`path:` / `query:` / `json:`), then validates the assembled struct
once with the same `valid.Validator`. The handler signature is identical to `Handle` /
`HandleEmpty`; `WithStatus` and the full error-mapping pipeline are reused unchanged.
- Conversion covers `string`, the sized integer/unsigned/float types, `bool`, and any type
whose pointer implements `encoding.TextUnmarshaler` — so `uuid.UUID` and `time.Time` bind
with no special-casing and no new dependency in `web`.
- A conversion failure is `ErrInvalidInput` naming the parameter (**400, never 500**).
- `default:` applies only when a parameter is **absent** (a present-but-empty `?q=` is left
as the zero value). Repeated query parameters bind to a slice; a comma inside a single value
is not split. A bodiless `GET`/`DELETE` is not an error — `BindEmpty` retires the
`HandleEmpty` empty-body (`io.EOF`) trap for routes keyed only by a path parameter.
- The struct is reflected over **once per type and cached**. A field with more than one source
tag, an unsupported field type, or a `default:` that is not a valid value for its field all
**panic at wiring** — a mis-tagged struct fails the service at boot, not on a request.
### Changed
- `HandlerFunc`'s doc comment no longer advertises itself for path/query parameters — those go
through `Bind` now; it remains the escape hatch for genuinely custom responses (streaming,
file downloads, non-JSON). `Handle`, `HandleNoBody`, `HandleEmpty` and `HandlerFunc` are
behaviourally unchanged.
- Bumped `contracts`, `core` to v1.6.0.
## [1.5.0] — 2026-08-09
Minor — configurable success status on the httputil handler adapters.