Files
gochat/pkg/response/error.go
T
2026-06-04 15:44:48 +08:00

133 lines
4.2 KiB
Go

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
}
}