// Package httputil provides typed handler adapters and HTTP response helpers. // // Handler adapters eliminate HTTP boilerplate — business functions stay pure Go // with no knowledge of request parsing or response encoding. All errors flow // through a centralized [Error] handler that logs once at the correct level and // writes a standardized JSON response body. // // # Typed handler adapters // // type CreateUserReq struct { // Email string `json:"email" validate:"required,email"` // } // type CreateUserRes struct { // ID string `json:"id"` // } // // r.Post("/users", httputil.Handle(v, logger, func(ctx context.Context, req CreateUserReq) (CreateUserRes, error) { // u, err := svc.CreateUser(ctx, req.Email) // if err != nil { // return CreateUserRes{}, err // propagates to Error — logged once, correct HTTP status // } // 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: // - 5xx → Error level (logz auto-enriches with error_code and WithContext fields) // - 4xx → Warn level (client mistake — not a server failure) // - 499 → Info level (client cancelled the request intentionally) // // Call it directly from [HandlerFunc] for genuinely custom responses — streaming, // file downloads, non-JSON content types: // // 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 // } // w.Header().Set("Content-Type", "text/csv") // return csv.NewWriter(w).WriteAll(rows) // }).ServeHTTP) package httputil