feat(web): httputil.WithStatus — configurable success status on the handler adapters (v1.5.0)

This commit is contained in:
2026-08-12 17:55:28 -06:00
parent 80b28dfcc4
commit bcccfef443
8 changed files with 217 additions and 23 deletions
+16 -9
View File
@@ -14,9 +14,11 @@ import (
// - Decodes the JSON request body into Req.
// - Validates Req using the provided [valid.Validator].
// - Calls fn with the request context and decoded Req.
// - Encodes Res as JSON with HTTP 200 on success.
// - Encodes Res as JSON on success — HTTP 200 by default, or the code given via
// [WithStatus] (e.g. WithStatus(http.StatusCreated) for a resource-creating POST).
// - On error: logs via [Error] (level derived from HTTP status) and writes the standardized JSON body.
func Handle[Req, Res any](v valid.Validator, logger logging.Logger, fn func(ctx context.Context, req Req) (Res, error)) http.HandlerFunc {
func Handle[Req, Res any](v valid.Validator, logger logging.Logger, fn func(ctx context.Context, req Req) (Res, error), opts ...Option) http.HandlerFunc {
status := resolveStatus(http.StatusOK, opts)
return func(w http.ResponseWriter, r *http.Request) {
var req Req
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -32,28 +34,33 @@ func Handle[Req, Res any](v valid.Validator, logger logging.Logger, fn func(ctx
Error(logger, w, r, err)
return
}
JSON(w, http.StatusOK, res)
JSON(w, status, res)
}
}
// HandleNoBody adapts a typed function with no request body (GET, HEAD).
// Calls fn with the request context; encodes the result as JSON with HTTP 200.
// Calls fn with the request context; encodes the result as JSON HTTP 200 by
// default, or the code given via [WithStatus].
// On error: logs via [Error] and writes the standardized JSON body.
func HandleNoBody[Res any](logger logging.Logger, fn func(ctx context.Context) (Res, error)) http.HandlerFunc {
func HandleNoBody[Res any](logger logging.Logger, fn func(ctx context.Context) (Res, error), opts ...Option) http.HandlerFunc {
status := resolveStatus(http.StatusOK, opts)
return func(w http.ResponseWriter, r *http.Request) {
res, err := fn(r.Context())
if err != nil {
Error(logger, w, r, err)
return
}
JSON(w, http.StatusOK, res)
JSON(w, status, res)
}
}
// HandleEmpty adapts a typed function with a request body but no response body.
// Decodes and validates Req, calls fn, returns 204 No Content on success.
// Decodes and validates Req, calls fn, and writes a body-less success — 204 No
// Content by default, or the code given via [WithStatus] (e.g.
// WithStatus(http.StatusAccepted) for async processing).
// On error: logs via [Error] and writes the standardized JSON body.
func HandleEmpty[Req any](v valid.Validator, logger logging.Logger, fn func(ctx context.Context, req Req) error) http.HandlerFunc {
func HandleEmpty[Req any](v valid.Validator, logger logging.Logger, fn func(ctx context.Context, req Req) error, opts ...Option) http.HandlerFunc {
status := resolveStatus(http.StatusNoContent, opts)
return func(w http.ResponseWriter, r *http.Request) {
var req Req
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -68,6 +75,6 @@ func HandleEmpty[Req any](v valid.Validator, logger logging.Logger, fn func(ctx
Error(logger, w, r, err)
return
}
NoContent(w)
w.WriteHeader(status)
}
}