feat(httputil): add Bind/BindEmpty request binding from path and query; v1.6.0
Bind and BindEmpty fill Req from path/query/json struct tags and validate once, extending the typed decode->validate->call->encode pipeline to routes with identifiers and filters. Conversion via builtins + encoding.TextUnmarshaler (uuid.UUID, time.Time); malformed value -> 400 naming the parameter; default: applies only when absent; repeated query -> slice; mis-tagged struct panics at wiring. Purely additive; existing adapters unchanged. Coordinated lockstep v1.6.0.
This commit is contained in:
+26
-6
@@ -22,6 +22,26 @@
|
||||
// return CreateUserRes{ID: u.ID}, nil
|
||||
// }))
|
||||
//
|
||||
// # Request binding
|
||||
//
|
||||
// [Bind] and [BindEmpty] extend the same decode → validate → call → encode pipeline
|
||||
// to requests that carry more than a JSON body. Each field declares its source with
|
||||
// a struct tag — path:, query: or json: — and the assembled struct is validated once:
|
||||
//
|
||||
// type getRoleReq struct {
|
||||
// RoleID uuid.UUID `path:"roleID" validate:"required"`
|
||||
// Expand []string `query:"expand"`
|
||||
// }
|
||||
//
|
||||
// r.Get("/roles/{roleID}", httputil.Bind(v, logger, func(ctx context.Context, req getRoleReq) (RoleRes, error) {
|
||||
// return svc.GetRole(ctx, req.RoleID)
|
||||
// }))
|
||||
//
|
||||
// A malformed value is a 400 naming the parameter (never a 500), uuid.UUID and
|
||||
// time.Time bind via [encoding.TextUnmarshaler], and a mis-tagged struct fails at
|
||||
// wiring rather than on a request. Use these instead of [HandlerFunc] for any route
|
||||
// with an identifier or a filter.
|
||||
//
|
||||
// # Centralized error handler
|
||||
//
|
||||
// [Error] is the single point of error processing for all handlers:
|
||||
@@ -29,16 +49,16 @@
|
||||
// - 4xx → Warn level (client mistake — not a server failure)
|
||||
// - 499 → Info level (client cancelled the request intentionally)
|
||||
//
|
||||
// Call it directly from [HandlerFunc] when you need path parameters or custom logic:
|
||||
// Call it directly from [HandlerFunc] for genuinely custom responses — streaming,
|
||||
// file downloads, non-JSON content types:
|
||||
//
|
||||
// r.Get("/users/{id}", httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
// id := chi.URLParam(r, "id")
|
||||
// u, err := svc.GetUser(r.Context(), id)
|
||||
// r.Get("/reports/{id}.csv", httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
// rows, err := svc.Export(r.Context(), chi.URLParam(r, "id"))
|
||||
// if err != nil {
|
||||
// httputil.Error(logger, w, r, err)
|
||||
// return nil
|
||||
// }
|
||||
// httputil.JSON(w, http.StatusOK, u)
|
||||
// return nil
|
||||
// w.Header().Set("Content-Type", "text/csv")
|
||||
// return csv.NewWriter(w).WriteAll(rows)
|
||||
// }).ServeHTTP)
|
||||
package httputil
|
||||
|
||||
Reference in New Issue
Block a user