Files
gochat/backend/pkg/response/error.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +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
}
}