package httperror import ( "encoding/json" "net/http" ) // ErrorResponse represents an HTTP error response (legacy format) type ErrorResponse struct { Error string `json:"error"` Message string `json:"message"` Code int `json:"code"` } // ProblemDetail represents an RFC 9457 compliant error response // See: https://datatracker.ietf.org/doc/html/rfc9457 type ProblemDetail struct { Type string `json:"type"` // URI reference identifying the problem type Title string `json:"title"` // Short, human-readable summary Status int `json:"status"` // HTTP status code Detail string `json:"detail,omitempty"` // Human-readable explanation Instance string `json:"instance,omitempty"` // URI reference identifying the specific occurrence Errors map[string][]string `json:"errors,omitempty"` // Validation errors (extension field) Extra map[string]interface{} `json:"-"` // Additional extension members } // WriteError writes an error response with pretty printing (legacy format) func WriteError(w http.ResponseWriter, code int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) response := ErrorResponse{ Error: http.StatusText(code), Message: message, Code: code, } encoder := json.NewEncoder(w) encoder.SetIndent("", " ") encoder.Encode(response) } // WriteProblemDetail writes an RFC 9457 compliant error response func WriteProblemDetail(w http.ResponseWriter, problem *ProblemDetail) { w.Header().Set("Content-Type", "application/problem+json") w.WriteHeader(problem.Status) encoder := json.NewEncoder(w) encoder.SetIndent("", " ") encoder.Encode(problem) } // BadRequest writes a 400 error func BadRequest(w http.ResponseWriter, message string) { WriteError(w, http.StatusBadRequest, message) } // Unauthorized writes a 401 error func Unauthorized(w http.ResponseWriter, message string) { WriteError(w, http.StatusUnauthorized, message) } // Forbidden writes a 403 error func Forbidden(w http.ResponseWriter, message string) { WriteError(w, http.StatusForbidden, message) } // NotFound writes a 404 error func NotFound(w http.ResponseWriter, message string) { WriteError(w, http.StatusNotFound, message) } // Conflict writes a 409 error func Conflict(w http.ResponseWriter, message string) { WriteError(w, http.StatusConflict, message) } // TooManyRequests writes a 429 error func TooManyRequests(w http.ResponseWriter, message string) { WriteError(w, http.StatusTooManyRequests, message) } // InternalServerError writes a 500 error func InternalServerError(w http.ResponseWriter, message string) { WriteError(w, http.StatusInternalServerError, message) } // ValidationError writes an RFC 9457 validation error response (400) func ValidationError(w http.ResponseWriter, errors map[string][]string, detail string) { if detail == "" { detail = "One or more validation errors occurred" } problem := &ProblemDetail{ Type: "about:blank", // Using about:blank as per RFC 9457 when no specific problem type URI is defined Title: "Validation Error", Status: http.StatusBadRequest, Detail: detail, Errors: errors, } WriteProblemDetail(w, problem) } // ProblemBadRequest writes an RFC 9457 bad request error (400) func ProblemBadRequest(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Bad Request", Status: http.StatusBadRequest, Detail: detail, } WriteProblemDetail(w, problem) } // ProblemUnauthorized writes an RFC 9457 unauthorized error (401) func ProblemUnauthorized(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Unauthorized", Status: http.StatusUnauthorized, Detail: detail, } WriteProblemDetail(w, problem) } // ProblemForbidden writes an RFC 9457 forbidden error (403) func ProblemForbidden(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Forbidden", Status: http.StatusForbidden, Detail: detail, } WriteProblemDetail(w, problem) } // ProblemNotFound writes an RFC 9457 not found error (404) func ProblemNotFound(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Not Found", Status: http.StatusNotFound, Detail: detail, } WriteProblemDetail(w, problem) } // ProblemConflict writes an RFC 9457 conflict error (409) func ProblemConflict(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Conflict", Status: http.StatusConflict, Detail: detail, } WriteProblemDetail(w, problem) } // ProblemTooManyRequests writes an RFC 9457 too many requests error (429) func ProblemTooManyRequests(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Too Many Requests", Status: http.StatusTooManyRequests, Detail: detail, } WriteProblemDetail(w, problem) } // ProblemInternalServerError writes an RFC 9457 internal server error (500) func ProblemInternalServerError(w http.ResponseWriter, detail string) { problem := &ProblemDetail{ Type: "about:blank", Title: "Internal Server Error", Status: http.StatusInternalServerError, Detail: detail, } WriteProblemDetail(w, problem) }