package httpx import ( "encoding/json" "log/slog" "net/http" ) type ErrorCode string const ( CodeInternalError ErrorCode = "internal.error" CodeNotFound ErrorCode = "not_found" CodeBadRequest ErrorCode = "request.bad" CodeRateLimited ErrorCode = "ratelimit.hit" CodeNotReady ErrorCode = "instance.not_ready" // CodeOriginForbidden — state-changing запрос пришёл с чужого Origin. CodeOriginForbidden ErrorCode = "origin.forbidden" ) type Error struct { Code ErrorCode `json:"code"` Message string `json:"message"` Details any `json:"details,omitempty"` } type errorEnvelope struct { Error Error `json:"error"` } func (e Error) HTTPStatus() int { switch e.Code { case CodeInternalError: return http.StatusInternalServerError case CodeNotFound: return http.StatusNotFound case CodeBadRequest: return http.StatusBadRequest case CodeRateLimited: return http.StatusTooManyRequests case CodeNotReady: return http.StatusServiceUnavailable case CodeOriginForbidden: return http.StatusForbidden default: return http.StatusInternalServerError } } func NewError(code ErrorCode, message string) Error { return Error{Code: code, Message: message} } func WriteJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(status) if body == nil { return } if err := json.NewEncoder(w).Encode(body); err != nil { slog.Error("write json response", slog.Any("error", err)) } } func WriteError(w http.ResponseWriter, apiErr Error) { WriteJSON(w, apiErr.HTTPStatus(), errorEnvelope{Error: apiErr}) } func WriteErrorStatus(w http.ResponseWriter, status int, code ErrorCode, message string) { WriteJSON(w, status, errorEnvelope{Error: NewError(code, message)}) } func StatusForCode(code ErrorCode) int { return NewError(code, "").HTTPStatus() }