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.
3.1 KiB
3.1 KiB
ADR Index — web
Module-level architecture decisions for code.nochebuena.dev/einherjar/web.
For framework-wide decisions see the Einherjar docs repository.
No module-level ADRs for v1.0.0 — all design decisions were consistent with
existing framework principles (ADR-001 through ADR-003 from core, framework
ADRs 001–004 from docs). No contested choices required a record.
Module ADRs:
| ADR | Title | Shipped |
|---|---|---|
| ADR-001 | httputil request binding — Bind / BindEmpty |
v1.6.0 |
Decisions worth noting (not ADR-worthy individually):
| Decision | Outcome | Rationale |
|---|---|---|
RateLimiterStore as interface (not concrete) |
Interface only; InMemoryRateLimiterStore as default |
Allows cache-valkey to satisfy it via duck typing without cross-module import |
| Fail-open rate limiting | Request allowed on store error | Availability preferred over hard enforcement during infrastructure degradation |
last_seen excluded |
Not in v1.0.0 | Application-domain concern; not transport-level middleware |
| UUID v7 for request IDs | v7 with v4 fallback | Time-ordered IDs sort chronologically in logs; fallback ensures generation never fails |
observability.Checkable not redefined |
Imported from contracts | Starters implement contracts directly; no web import needed by db/cache starters |
| Background goroutine for in-memory eviction | time.Ticker goroutine |
Avoids worker module dependency; in-memory store is self-contained |
mw.RequestIDFrom resolver sees the request (v1.4.0) |
New entry point takes func(*http.Request) string; RequestID becomes a request-ignoring wrapper over it |
An inbound correlation id must be able to survive this boundary, but acceptability is per-service — a typed audit column rejects what an opaque log accepts. The framework provides plumbing only (context + header, once per request); the app owns policy (which header, validation, generation fallback). An empty resolver result attaches nothing rather than a silently-empty value |
httputil success status is configurable (v1.5.0) |
Handle/HandleNoBody/HandleEmpty take opts ...Option; WithStatus(code) overrides the default (200 / 200 / 204) |
201 Created / 202 Accepted are common and were only reachable by hand-rolling the handler (losing decode+validate+error-mapping). Variadic options are non-breaking and future-extensible (headers, etc.). WithStatus is success-only (2xx) and panics at wiring on a non-2xx code — a wrong status is a routing mistake that should fail to boot, not surface at runtime; error status stays separate, resolved from the xerror by Error |
httputil request binding (v1.6.0) |
Bind/BindEmpty fill Req from path:/query:/json: tags and validate once; see ADR-001 |
Path/query were only reachable via HandlerFunc, the one route to production with no validation (unvalidated bounds; client mistakes as 500s). encoding.TextUnmarshaler is the whole extensibility story (uuid/time, no new dep). Mis-tagged struct panics at wiring. Purely additive |