187 lines
5.3 KiB
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)
|
|
}
|