package app import ( "errors" "net/http" "git.threesix.ai/jordan/sp4-test-1770369255/pkg/httperror" "git.threesix.ai/jordan/sp4-test-1770369255/pkg/httpresponse" "git.threesix.ai/jordan/sp4-test-1770369255/pkg/logging" ) // HandlerFunc is a handler function that returns an error. // Use with Wrap() to automatically handle error responses. // // Example: // // func GetUser(w http.ResponseWriter, r *http.Request) error { // user, err := svc.Get(ctx, id) // if err != nil { // return httperror.NotFoundf("user %s not found", id) // } // httpresponse.OK(w, r, user) // return nil // } type HandlerFunc func(w http.ResponseWriter, r *http.Request) error // Wrap converts a HandlerFunc to http.HandlerFunc, automatically handling // error responses. HTTPErrors are written with their status code and details; // other errors are logged and returned as 500 Internal Server Error. // // Example: // // r.Get("/users/{id}", app.Wrap(handlers.GetUser)) // // func (h *Handlers) GetUser(w http.ResponseWriter, r *http.Request) error { // id := chi.URLParam(r, "id") // user, err := h.userSvc.Get(r.Context(), id) // if err != nil { // if errors.Is(err, ErrUserNotFound) { // return httperror.NotFoundf("user %s not found", id) // } // return err // Will be logged and returned as 500 // } // httpresponse.OK(w, r, user) // return nil // } func Wrap(h HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := h(w, r); err != nil { writeErrorFromErr(w, r, err, nil) } } } // WrapWithLogger is like Wrap but accepts a logger for internal error logging. // Use this when you want to log internal errors that aren't HTTPErrors. // // Example: // // r.Get("/users/{id}", app.WrapWithLogger(handlers.GetUser, logger)) func WrapWithLogger(h HandlerFunc, logger *logging.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := h(w, r); err != nil { writeErrorFromErr(w, r, err, logger) } } } // writeErrorFromErr writes an error response based on the error type. // HTTPErrors are written with their status code; other errors become 500. func writeErrorFromErr(w http.ResponseWriter, r *http.Request, err error, logger *logging.Logger) { var httpErr *httperror.HTTPError if errors.As(err, &httpErr) { // Write the HTTPError directly httpresponse.WriteError(w, r, httpErr.Status, httpErr.Code, httpErr.Message, httpErr.Details) return } // For non-HTTP errors, log and return generic 500 if logger != nil { logger.Error("internal error", "error", err, "method", r.Method, "path", r.URL.Path, ) } httpresponse.InternalError(w, r, "internal error") } // ----------------------------------------------------------------------------- // Middleware Helpers // ----------------------------------------------------------------------------- // MiddlewareFunc is a middleware that returns an error. // Use with WrapMiddleware() to automatically handle error responses. type MiddlewareFunc func(next http.Handler) func(w http.ResponseWriter, r *http.Request) error // WrapMiddleware converts a MiddlewareFunc to a standard http middleware. // // Example: // // func AuthMiddleware(next http.Handler) func(w http.ResponseWriter, r *http.Request) error { // return func(w http.ResponseWriter, r *http.Request) error { // token := r.Header.Get("Authorization") // if token == "" { // return httperror.Unauthorized("missing authorization header") // } // user, err := validateToken(token) // if err != nil { // return httperror.Unauthorized("invalid token") // } // ctx := context.WithValue(r.Context(), userKey, user) // next.ServeHTTP(w, r.WithContext(ctx)) // return nil // } // } // // r.Use(app.WrapMiddleware(AuthMiddleware)) func WrapMiddleware(m MiddlewareFunc) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := m(next)(w, r); err != nil { writeErrorFromErr(w, r, err, nil) } }) } }