Files
pargar/backend/internal/api/errors.go
T

104 lines
2.8 KiB
Go
Raw Normal View History

package api
import (
"net/http"
"pargar/backend/internal/observability"
)
// Stable machine-readable error codes for clients.
const (
CodeBadRequest = "bad_request"
CodeUnauthorized = "unauthorized"
CodeForbidden = "forbidden"
CodeNotFound = "not_found"
CodeConflict = "conflict"
CodeLocked = "account_locked"
CodePayloadTooLarge = "payload_too_large"
CodeRateLimited = "rate_limited"
CodeValidation = "validation_error"
CodeInternal = "internal_error"
)
type apiError struct {
Error string `json:"error"`
Code string `json:"code,omitempty"`
RequestID string `json:"requestId,omitempty"`
}
func requestIDFromWriter(w http.ResponseWriter) string {
if w == nil {
return ""
}
return w.Header().Get("X-Request-ID")
}
func defaultCodeForStatus(status int) string {
switch status {
case http.StatusBadRequest:
return CodeBadRequest
case http.StatusUnauthorized:
return CodeUnauthorized
case http.StatusForbidden:
return CodeForbidden
case http.StatusNotFound:
return CodeNotFound
case http.StatusConflict:
return CodeConflict
case http.StatusLocked:
return CodeLocked
case http.StatusRequestEntityTooLarge:
return CodePayloadTooLarge
case http.StatusTooManyRequests:
return CodeRateLimited
case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable:
return CodeInternal
default:
if status >= 500 {
return CodeInternal
}
if status >= 400 {
return CodeBadRequest
}
return ""
}
}
// writeErr responds with a Persian user message and a stable code derived from status.
func writeErr(w http.ResponseWriter, status int, msg string) {
writeErrCode(w, status, defaultCodeForStatus(status), msg)
}
// writeErrCode responds with an explicit machine-readable code.
func writeErrCode(w http.ResponseWriter, status int, code, msg string) {
if code == "" {
code = defaultCodeForStatus(status)
}
if msg == "" {
msg = "خطایی رخ داد؛ دوباره تلاش کنید"
}
writeJSON(w, status, apiError{
Error: msg,
Code: code,
RequestID: requestIDFromWriter(w),
})
}
// writeInternalErr logs the real error server-side and returns a safe 500 to the client.
func writeInternalErr(w http.ResponseWriter, op string, err error, userMsg string) {
if userMsg == "" {
userMsg = "مشکل موقتی سرور؛ کمی بعد دوباره تلاش کنید."
}
attrs := []any{"op", op, "request_id", requestIDFromWriter(w)}
if err != nil {
attrs = append(attrs, "error", err)
}
observability.L().Error("api.error", attrs...)
writeErrCode(w, http.StatusInternalServerError, CodeInternal, userMsg)
}
// writeValidationErr is a 400 with validation_error code.
func writeValidationErr(w http.ResponseWriter, msg string) {
writeErrCode(w, http.StatusBadRequest, CodeValidation, msg)
}