Documentation
¶
Overview ¶
Package rest is a tiny typed-handler layer over net/http.
A HandlerFunc returns a ResponseEncoder — a value that knows how to serialize itself — instead of writing to the ResponseWriter directly. This keeps response encoding and error handling in one place (Respond), makes handlers trivial to unit-test (assert on the returned value, not a recorded response), and lets a handler signal failure by returning an *errs.Error, which Respond maps to the right HTTP status. Mount these handlers with the router package.
HandlerFunc implements http.Handler (via ServeHTTP, which runs the handler and calls Respond), so this typed application layer plugs into any net/http router with no adapter: it is the single boundary to the transport layer, and net/http middleware (func(http.Handler) http.Handler) always wraps it from the outside. See the router package for the two-layer model and the parallel Use/UseApp, With/WithApp, Handle/HandleApp method pairs.
ResponseEncoders and status ¶
A ResponseEncoder reports its body and content type; if it also implements an HTTPStatus() int method it sets the response status (otherwise 200, or 204 for a nil ResponseEncoder). JSON and JSONStatus wrap any value as a JSON ResponseEncoder, and *errs.Error is itself a ResponseEncoder, so a handler returns one uniform type for both success and failure.
Usage ¶
func getWidget(ctx context.Context, r *http.Request) rest.ResponseEncoder {
var in CreateWidget
if err := rest.Decode(r, &in); err != nil {
return err.(*errs.Error) // InvalidArgument -> 400
}
w, err := store.Find(ctx, in.ID)
if err != nil {
return errs.New(errs.NotFound, err) // -> 404
}
return rest.JSON(w) // -> 200 application/json
}
// Compose cross-cutting behavior, outermost first:
h := rest.ChainMiddleware(getWidget, authMid, logMid)
resp := h(ctx, r)
_ = rest.Respond(ctx, w, resp)
API ¶
- HandlerFunc: func(ctx, *http.Request) ResponseEncoder — the typed handler shape; also an http.Handler (ServeHTTP), the boundary to the net/http layer.
- MidFunc / ChainMiddleware: wrap a HandlerFunc; mw[0] is the outermost layer. ChainMiddleware returns a HandlerFunc, so a chained typed handler is itself an http.Handler: mux.Handle(p, rest.ChainMiddleware(h, mids...)).
- Chain: collapse several MidFunc into one reusable MidFunc (same order; nil elements skipped, so empty/all-nil is the identity). Use it to name a bundle and pass it where a single MidFunc is expected; the []MidFunc + spread form stays the primary way to accumulate middleware on a router group.
- Respond: write a ResponseEncoder to an http.ResponseWriter (nil -> 204; encode failure -> 500 with a generic body; cancelled ctx -> returns ctx.Err).
- JSON / JSONStatus: wrap a value as a JSON ResponseEncoder (status 200, or explicit).
- Decode: read and validate a JSON request body (DisallowUnknownFields + errs.Check), returning an *errs.Error (InvalidArgument) on bad input.
- NoResponse: a ResponseEncoder telling Respond to write nothing (the handler already wrote the response — redirect, stream, hijack).
- Param: read a path parameter (thin wrapper over http.Request.PathValue).
To supervise the router as a server, wrap it with httpserver (or the server package's HTTP brick, Name "rest-server") so it runs and drains under a worker.Group alongside the gRPC and status servers — REST has no server type of its own; it is just an http.Handler.
Index ¶
- func Decode(r *http.Request, v any) error
- func GetWriter(ctx context.Context) http.ResponseWriter
- func Param(r *http.Request, key string) string
- func Respond(ctx context.Context, w http.ResponseWriter, resp ResponseEncoder) error
- func WithWriter(ctx context.Context, w http.ResponseWriter) context.Context
- type Handle
- type HandlerFunc
- type MidFunc
- type NoResponse
- type ResponseEncoder
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Decode ¶
Decode reads a JSON request body into v and validates it with errs.Check. It returns an *errs.Error (InvalidArgument) on malformed input.
func GetWriter ¶
func GetWriter(ctx context.Context) http.ResponseWriter
GetWriter returns the ResponseWriter installed by the boundary, or nil when absent (e.g. a HandlerFunc invoked directly without ServeHTTP, as in a unit test). It is an escape hatch for application middleware that must set response headers (Cache-Control, ETag) — concerns the ResponseEncoder cannot express. Ordinary handlers never need it: return a ResponseEncoder and let Respond write. A middleware that uses it to set headers must NOT also call WriteHeader, so Respond stays the single place that writes status and body.
func Param ¶
Param returns the named path parameter from the request — a thin wrapper over http.Request.PathValue, for symmetry with the other rest helpers.
func Respond ¶
func Respond(ctx context.Context, w http.ResponseWriter, resp ResponseEncoder) error
Respond writes resp to w. A nil ResponseEncoder yields 204 No Content. If encoding fails, a 500 with a generic error body is written instead.
func WithWriter ¶
WithWriter returns a context carrying the response writer. The boundary (HandlerFunc.ServeHTTP) installs it automatically, so application middleware reached through the typed chain can recover the ResponseWriter via GetWriter.
Types ¶
type Handle ¶
type Handle func(pattern string, h HandlerFunc, mids ...MidFunc)
Handle registers a typed HandlerFunc under a "METHOD /pattern" route with optional per-route middleware. It is the registration seam a feature's Routes method receives so the feature need not depend on the router type: a router's HandleApp method value is a Handle, and Routes calls it once per endpoint. This keeps route registration in the feature while route composition (mounting, grouping, global middleware) stays with the router in one Install function, which passes router.HandleApp as the Handle.
type HandlerFunc ¶
type HandlerFunc func(ctx context.Context, r *http.Request) ResponseEncoder
HandlerFunc handles a request and returns a ResponseEncoder describing the response. Returning a non-nil error value (e.g. *errs.Error) is the idiomatic way to signal failure — Respond will set the right status.
func ChainMiddleware ¶
func ChainMiddleware(h HandlerFunc, mw ...MidFunc) HandlerFunc
ChainMiddleware wraps h with mw in order, so mw[0] is the outermost layer.
func (HandlerFunc) ServeHTTP ¶
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP makes HandlerFunc an http.Handler, mirroring the stdlib http.HandlerFunc: it runs the handler and writes the returned ResponseEncoder via Respond. This is the single boundary between the typed application layer (HandlerFunc/MidFunc, which speak ResponseEncoder) and the net/http transport layer (http.Handler, which speaks bytes). Because of it a typed handler — including one wrapped by ChainMiddleware, since that returns a HandlerFunc — registers directly on any net/http router:
mux.Handle("POST /widgets", rest.ChainMiddleware(h.create, auth, decode))
Transport middleware (func(http.Handler) http.Handler) therefore always wraps the boundary, i.e. runs outside the MidFunc chain; that nesting is correct by construction (anything needing the ResponseEncoder is a MidFunc, anything needing raw bytes is transport middleware).
type MidFunc ¶
type MidFunc func(HandlerFunc) HandlerFunc
MidFunc wraps a HandlerFunc to add cross-cutting behavior.
func Chain ¶
Chain collapses several MidFunc into a single one, applied outermost-first (Chain(a, b)(h) wraps as a(b(h)) — the same order as ChainMiddleware). nil elements are skipped, so an all-nil or empty Chain is a no-op (the identity middleware).
Whereas ChainMiddleware applies middleware to a known handler right away, Chain seals a group into a reusable, first-class MidFunc value: name a bundle once and pass it anywhere a single MidFunc is expected — a per-route slot, another Chain, or a router's UseApp/WithApp. The slice form ([]MidFunc + spread) stays the primary way to accumulate middleware on a group; Chain is for when a bundle must travel as one value.
type NoResponse ¶
type NoResponse struct{}
NoResponse is a ResponseEncoder that tells Respond to write nothing, for handlers that have already written to the ResponseWriter themselves (a redirect, a streamed body, a hijacked connection).
type ResponseEncoder ¶
ResponseEncoder is a value that can serialize itself into a response body.
func JSON ¶
func JSON(v any) ResponseEncoder
JSON wraps v as a JSON ResponseEncoder with status 200.
func JSONStatus ¶
func JSONStatus(v any, status int) ResponseEncoder
JSONStatus wraps v as a JSON ResponseEncoder with an explicit status.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package mid provides application-layer middleware: rest.MidFunc values that wrap a typed rest.HandlerFunc and operate on the ResponseEncoder it returns (the typed value), not on raw bytes.
|
Package mid provides application-layer middleware: rest.MidFunc values that wrap a typed rest.HandlerFunc and operate on the ResponseEncoder it returns (the typed value), not on raw bytes. |
|
Package router wraps the standard library's net/http.ServeMux with nestable groups and two cooperating handler/middleware layers: net/http transport middleware and typed rest application middleware.
|
Package router wraps the standard library's net/http.ServeMux with nestable groups and two cooperating handler/middleware layers: net/http transport middleware and typed rest application middleware. |