monorepo/cloud/maplepress-backend/pkg/httperror/error.go

187 lines
5.3 KiB
Go

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)
}