docs(mcp): document httputil handlers + WithStatus in wire builtin (v1.3.3)
This commit is contained in:
@@ -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))`:
|
||||
|
||||
Reference in New Issue
Block a user