package handlers import ( "encoding/json" "log" "net/http" ) // ErrorResponse represents a structured error response type ErrorResponse struct { Error string `json:"error"` Message string `json:"message,omitempty"` Code int `json:"code"` } // AppError represents an application error with context type AppError struct { Err error Message string StatusCode int Internal bool // If true, don't expose details to client } // Error implements the error interface func (e *AppError) Error() string { if e.Err != nil { return e.Err.Error() } return e.Message } // NewAppError creates a new application error func NewAppError(err error, message string, statusCode int, internal bool) *AppError { return &AppError{ Err: err, Message: message, StatusCode: statusCode, Internal: internal, } } // HandleError handles errors consistently across the application func HandleError(w http.ResponseWriter, r *http.Request, err error) { var appErr *AppError // Check if it's an AppError switch e := err.(type) { case *AppError: appErr = e default: // Unknown error - treat as internal server error appErr = NewAppError(err, "Internal Server Error", http.StatusInternalServerError, true) } // Log the error if appErr.Internal { log.Printf("ERROR [%s %s]: %v", r.Method, r.URL.Path, appErr.Err) } else { log.Printf("CLIENT ERROR [%s %s]: %s (status: %d)", r.Method, r.URL.Path, appErr.Message, appErr.StatusCode) } // Determine response based on Accept header accept := r.Header.Get("Accept") isJSON := accept == "application/json" isHTMX := r.Header.Get("HX-Request") != "" if isJSON { // JSON response w.Header().Set("Content-Type", "application/json") w.WriteHeader(appErr.StatusCode) response := ErrorResponse{ Error: http.StatusText(appErr.StatusCode), Code: appErr.StatusCode, } // Only include message if not internal if !appErr.Internal { response.Message = appErr.Message } if err := json.NewEncoder(w).Encode(response); err != nil { log.Printf("ERROR encoding JSON response: %v", err) } return } if isHTMX { // HTMX response - return simple HTML fragment w.Header().Set("Content-Type", "text/html") w.WriteHeader(appErr.StatusCode) message := appErr.Message if appErr.Internal { message = "An error occurred. Please try again later." } if _, err := w.Write([]byte("