httputil depends on xerrors (Tier 0) and valid (Tier 1), placing it at Tier 2. No infrastructure or lifecycle dependencies exist in this module.
87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package httputil
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"code.nochebuena.dev/go/valid"
|
|
"code.nochebuena.dev/go/xerrors"
|
|
)
|
|
|
|
// HandlerFunc is an http.Handler that can return an error.
|
|
// On non-nil error the error is mapped to the appropriate HTTP response via Error.
|
|
// Useful for manual handlers that don't need the typed adapter.
|
|
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
|
|
|
|
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if err := h(w, r); err != nil {
|
|
Error(w, err)
|
|
}
|
|
}
|
|
|
|
// Handle adapts a typed business function to http.HandlerFunc.
|
|
// - Decodes JSON request body into Req.
|
|
// - Validates Req using the provided Validator.
|
|
// - Calls fn with the request context and decoded Req.
|
|
// - Encodes Res as JSON with HTTP 200.
|
|
// - Maps any returned error to the appropriate HTTP status via xerrors code.
|
|
func Handle[Req, Res any](v valid.Validator, fn func(ctx context.Context, req Req) (Res, error)) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req Req
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
Error(w, badRequest("invalid JSON: "+err.Error()))
|
|
return
|
|
}
|
|
if err := v.Struct(req); err != nil {
|
|
Error(w, err)
|
|
return
|
|
}
|
|
res, err := fn(r.Context(), req)
|
|
if err != nil {
|
|
Error(w, err)
|
|
return
|
|
}
|
|
JSON(w, http.StatusOK, res)
|
|
}
|
|
}
|
|
|
|
// HandleNoBody adapts a typed function with no request body (GET, DELETE).
|
|
// Calls fn with request context; encodes result as JSON with HTTP 200.
|
|
func HandleNoBody[Res any](fn func(ctx context.Context) (Res, error)) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
res, err := fn(r.Context())
|
|
if err != nil {
|
|
Error(w, err)
|
|
return
|
|
}
|
|
JSON(w, http.StatusOK, res)
|
|
}
|
|
}
|
|
|
|
// HandleEmpty adapts a typed function with a body but no response body (returns 204).
|
|
// Decodes JSON body into Req, validates, calls fn. Returns 204 on success.
|
|
func HandleEmpty[Req any](v valid.Validator, fn func(ctx context.Context, req Req) error) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req Req
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
Error(w, badRequest("invalid JSON: "+err.Error()))
|
|
return
|
|
}
|
|
if err := v.Struct(req); err != nil {
|
|
Error(w, err)
|
|
return
|
|
}
|
|
if err := fn(r.Context(), req); err != nil {
|
|
Error(w, err)
|
|
return
|
|
}
|
|
NoContent(w)
|
|
}
|
|
}
|
|
|
|
// badRequest wraps a message in an ErrInvalidInput error.
|
|
func badRequest(msg string) error {
|
|
return xerrors.New(xerrors.ErrInvalidInput, msg)
|
|
}
|