docs(mcp): document httputil handlers + WithStatus in wire builtin (v1.3.3)

This commit is contained in:
2026-08-12 17:59:16 -06:00
parent 87c5eda1bc
commit 766adb2989
5 changed files with 109 additions and 37 deletions
+48 -1
View File
@@ -348,7 +348,7 @@ func withUsers(
repo := userrepo.New(db)
uow := postgres.NewUnitOfWork(logger, db)
svc := usersvc.New(repo, uow)
h := userhandler.New(svc, v)
h := userhandler.New(svc, v, logger) // handler carries v + logger for httputil
// Literal-segment routes register BEFORE parametrised siblings.
srv.Put("/api/v1/users/me/password", h.ChangeOwnPassword)
@@ -384,6 +384,53 @@ srv.Put("/api/v1/users/{user_id}", h.UpdateUser)
srv.Put("/api/v1/users/me/password", h.ChangeOwnPassword)
```
## HTTP handlers (httputil)
Handler methods (`h.CreateUser`, `h.ListUsers`, …) wrap `web/httputil`, which does the
decode → validate → call → encode. The method body is just the typed business call:
```go
import (
"net/http"
"code.nochebuena.dev/einherjar/web/httputil"
)
// POST that creates a resource → 201 Created via WithStatus.
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
httputil.Handle(h.v, h.logger, func(ctx context.Context, req CreateUserReq) (CreateUserRes, error) {
id, err := h.svc.Create(ctx, req)
if err != nil {
return CreateUserRes{}, err // xerror → HTTP status is automatic (see below)
}
return CreateUserRes{ID: id}, nil
}, httputil.WithStatus(http.StatusCreated))(w, r)
}
```
**Adapters and their default success status:**
| Adapter | Body | Default | Use for |
|---|---|---|---|
| `httputil.Handle(v, logger, fn, opts…)` | req + res | **200** | POST/PUT returning a body |
| `httputil.HandleNoBody(logger, fn, opts…)` | res only | **200** | GET/HEAD |
| `httputil.HandleEmpty(v, logger, fn, opts…)` | req only | **204** | DELETE / body-less PUT |
**Override the success status with `httputil.WithStatus(code)`**`WithStatus(http.StatusCreated)`
(201) on a create, `WithStatus(http.StatusAccepted)` (202) on an async `HandleEmpty`. The code **must
be 2xx**: these adapters own only the success path, so a non-2xx code is a routing mistake and
**panics at wiring** (the service fails to boot rather than emit a wrong status at runtime). Never
pass a 4xx/5xx to `WithStatus`.
**Error status is automatic — never set it by hand.** Return the right `*xerrors.Err`
(`xerrors.NotFound(…)`, `xerrors.InvalidInput(…)`, `xerrors.PermissionDenied(…)`, …) and
`httputil.Error` maps it to the HTTP status and logs at the derived level (5xx→Error, 4xx→Warn,
499→Info). Validation failures become 400 automatically.
**Escape hatch:** for a response the adapters don't cover, write a raw `http.HandlerFunc` and call
`httputil.JSON(w, status, v)` or `httputil.NoContent(w)` yourself — that is the only place you pass
a status literal.
## Authorization
Every protected route registers with `.With(authz(provider, resource, grant))`: