// Package apperr defines AppError, the single error type returned by every // service/repo function. Handlers translate AppError into HTTP responses. package apperr import ( "errors" "fmt" "net/http" ) type AppError struct { Code int // HTTP status to return Message string // user-facing message (safe to expose) Field string // optional: which field caused the error RetryAfter int // seconds, for 429 Err error // original error for logging (never exposed) } func (e *AppError) Error() string { if e.Err != nil { return fmt.Sprintf("%s: %v", e.Message, e.Err) } return e.Message } func (e *AppError) Unwrap() error { return e.Err } // As reports whether err is or wraps an *AppError. func As(err error) (*AppError, bool) { var ae *AppError if errors.As(err, &ae) { return ae, true } return nil, false } // --------------------------------------------------------------------------- // Constructors // --------------------------------------------------------------------------- func BadRequest(msg, field string, err error) *AppError { return &AppError{Code: http.StatusBadRequest, Message: msg, Field: field, Err: err} } func Unauthorized(msg string) *AppError { return &AppError{Code: http.StatusUnauthorized, Message: msg} } func Forbidden(msg string) *AppError { return &AppError{Code: http.StatusForbidden, Message: msg} } func NotFound(msg string) *AppError { return &AppError{Code: http.StatusNotFound, Message: msg} } func Conflict(msg string, err error) *AppError { return &AppError{Code: http.StatusConflict, Message: msg, Err: err} } func PayloadTooLarge(msg string) *AppError { return &AppError{Code: http.StatusRequestEntityTooLarge, Message: msg} } func UnprocessableEntity(msg string) *AppError { return &AppError{Code: http.StatusUnprocessableEntity, Message: msg} } func TooManyRequests(retryAfterSeconds int) *AppError { return &AppError{ Code: http.StatusTooManyRequests, Message: "rate limit exceeded", RetryAfter: retryAfterSeconds, } } func Internal(err error) *AppError { return &AppError{Code: http.StatusInternalServerError, Message: "internal server error", Err: err} }