package response import ( "fmt" "net/http" ) // ErrorCode defines standardized error codes. // Pattern follows Chatwoot's error response format in API controllers. type ErrorCode string const ( // General errors ErrInternal ErrorCode = "INTERNAL_ERROR" ErrNotFound ErrorCode = "NOT_FOUND" ErrBadRequest ErrorCode = "BAD_REQUEST" ErrUnauthorized ErrorCode = "UNAUTHORIZED" ErrForbidden ErrorCode = "FORBIDDEN" ErrConflict ErrorCode = "CONFLICT" ErrValidation ErrorCode = "VALIDATION_ERROR" ErrRateLimit ErrorCode = "RATE_LIMITED" ErrServiceUnavail ErrorCode = "SERVICE_UNAVAILABLE" ErrPaymentRequired ErrorCode = "PAYMENT_REQUIRED" // Business-specific errors (ref: Chatwoot error patterns) ErrAccountNotFound ErrorCode = "ACCOUNT_NOT_FOUND" ErrInboxNotFound ErrorCode = "INBOX_NOT_FOUND" ErrChannelInvalid ErrorCode = "CHANNEL_INVALID" ErrContactNotFound ErrorCode = "CONTACT_NOT_FOUND" ErrConversationNotFound ErrorCode = "CONVERSATION_NOT_FOUND" ErrMessageNotFound ErrorCode = "MESSAGE_NOT_FOUND" ErrUserNotFound ErrorCode = "USER_NOT_FOUND" ErrDuplicateRecord ErrorCode = "DUPLICATE_RECORD" ErrChannelNotEnabled ErrorCode = "CHANNEL_NOT_ENABLED" // Knowledge Base / Help Center errors (M4) ErrPortalNotFound ErrorCode = "PORTAL_NOT_FOUND" ErrArticleNotFound ErrorCode = "ARTICLE_NOT_FOUND" ErrCategoryNotFound ErrorCode = "CATEGORY_NOT_FOUND" ErrFolderNotFound ErrorCode = "FOLDER_NOT_FOUND" ErrPortalMemberNotFound ErrorCode = "PORTAL_MEMBER_NOT_FOUND" ErrKBFeatureNotEnabled ErrorCode = "KB_FEATURE_NOT_ENABLED" ErrArticleSlugExists ErrorCode = "ARTICLE_SLUG_EXISTS" ErrPortalSlugExists ErrorCode = "PORTAL_SLUG_EXISTS" ) // AppError is the unified application error type. // All business errors should be wrapped as AppError for consistent API responses. type AppError struct { Code ErrorCode `json:"code"` Message string `json:"message"` Detail string `json:"detail,omitempty"` Status int `json:"-"` } func (e *AppError) Error() string { if e.Detail != "" { return fmt.Sprintf("%s: %s (%s)", e.Code, e.Message, e.Detail) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // NewAppError creates a new AppError func NewAppError(code ErrorCode, message string, status int) *AppError { return &AppError{ Code: code, Message: message, Status: status, } } // WithDetail adds detail to an AppError func (e *AppError) WithDetail(detail string) *AppError { e.Detail = detail return e } // Convenience constructors for common error types func ErrInternalError(msg string) *AppError { return NewAppError(ErrInternal, msg, http.StatusInternalServerError) } func ErrNotFoundError(resource string) *AppError { return NewAppError(ErrNotFound, fmt.Sprintf("%s not found", resource), http.StatusNotFound) } func ErrBadRequestError(msg string) *AppError { return NewAppError(ErrBadRequest, msg, http.StatusBadRequest) } func ErrUnauthorizedError(msg string) *AppError { return NewAppError(ErrUnauthorized, msg, http.StatusUnauthorized) } func ErrForbiddenError(msg string) *AppError { return NewAppError(ErrForbidden, msg, http.StatusForbidden) } func ErrValidationError(msg string) *AppError { return NewAppError(ErrValidation, msg, http.StatusBadRequest) } func ErrConflictError(msg string) *AppError { return NewAppError(ErrConflict, msg, http.StatusConflict) } func ErrRateLimitError(msg string) *AppError { return NewAppError(ErrRateLimit, msg, http.StatusTooManyRequests) } // HTTPStatus maps ErrorCode to HTTP status code func ErrorToHTTPStatus(code ErrorCode) int { switch code { case ErrNotFound, ErrAccountNotFound, ErrInboxNotFound, ErrContactNotFound, ErrConversationNotFound, ErrMessageNotFound, ErrUserNotFound: return http.StatusNotFound case ErrBadRequest, ErrValidation: return http.StatusBadRequest case ErrUnauthorized: return http.StatusUnauthorized case ErrForbidden, ErrChannelNotEnabled: return http.StatusForbidden case ErrConflict, ErrDuplicateRecord, ErrChannelInvalid: return http.StatusConflict case ErrRateLimit: return http.StatusTooManyRequests case ErrServiceUnavail: return http.StatusServiceUnavailable default: return http.StatusInternalServerError } }