# ADR-001: Generic Typed Handler Adapters **Status:** Accepted **Date:** 2026-03-18 ## Context Standard `net/http` handlers have the signature `func(http.ResponseWriter, *http.Request)`. Business logic that lives inside these handlers must manually decode JSON, validate input, encode responses, and convert errors to status codes — the same boilerplate repeated for every endpoint. This tightly couples business functions to HTTP and makes them hard to test in isolation. A common mitigation is a generic "controller" framework, but Go generics allow a lighter approach: thin adapter functions that perform the boilerplate at the HTTP boundary while leaving business logic free of HTTP types. ## Decision Three generic adapter functions are provided, covering the three common handler shapes: | Function | Request body | Response body | Success status | |---|---|---|---| | `Handle[Req, Res]` | JSON-decoded `Req` | JSON `Res` | 200 | | `HandleNoBody[Res]` | none | JSON `Res` | 200 | | `HandleEmpty[Req]` | JSON-decoded `Req` | none | 204 | Each adapter accepts a plain Go function — e.g. `func(ctx context.Context, req Req) (Res, error)` — and returns an `http.HandlerFunc`. The business function receives a `context.Context` (from the request) and a typed value; it returns a typed value and an error. It has no knowledge of `http.ResponseWriter` or `*http.Request`. `Handle` and `HandleEmpty` also accept a `valid.Validator` parameter. After JSON decoding, the adapter calls `v.Struct(req)` before invoking the business function. Validation errors are returned to the caller via `Error(w, err)` without reaching business logic. A `HandlerFunc` type (`func(http.ResponseWriter, *http.Request) error`) is also provided for handlers that need direct HTTP access but still want automatic error mapping. ## Consequences - Business functions are pure Go — they take typed inputs and return typed outputs. They can be called directly in unit tests without constructing an HTTP request. - Type parameters are inferred at the call site: `Handle(v, svc.CreateOrder)` — no explicit type arguments needed if the function signature is concrete. - Validation is enforced before business logic runs. There is no path through `Handle` or `HandleEmpty` where an invalid `Req` reaches the function. - `HandleNoBody` skips validation entirely because there is no request body to validate. Path/query parameters are the caller's responsibility. - All three adapters share the same error mapping via `Error(w, err)`, so HTTP status codes are determined consistently by the xerrors code on the returned error. - The adapters impose a fixed response shape (JSON + fixed status). Handlers that need streaming, multipart, or redirect responses should use `HandlerFunc` or plain `http.HandlerFunc` directly.