backend-service/internal/errors/errors.go

236 lines
7.8 KiB
Go
Raw Permalink Normal View History

package errors
import (
"fmt"
"net/http"
)
// ErrorCode represents application-specific error codes
type ErrorCode string
const (
// Authentication errors
ErrCodeInvalidCredentials ErrorCode = "INVALID_CREDENTIALS"
ErrCodeTokenExpired ErrorCode = "TOKEN_EXPIRED"
ErrCodeTokenInvalid ErrorCode = "TOKEN_INVALID"
ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED"
ErrCodeForbidden ErrorCode = "FORBIDDEN"
// Validation errors
ErrCodeValidationFailed ErrorCode = "VALIDATION_FAILED"
ErrCodeInvalidInput ErrorCode = "INVALID_INPUT"
ErrCodeMissingField ErrorCode = "MISSING_FIELD"
// Resource errors
ErrCodeNotFound ErrorCode = "NOT_FOUND"
ErrCodeAlreadyExists ErrorCode = "ALREADY_EXISTS"
ErrCodeConflict ErrorCode = "CONFLICT"
// Business logic errors
ErrCodeSlotUnavailable ErrorCode = "SLOT_UNAVAILABLE"
ErrCodeBookingNotFound ErrorCode = "BOOKING_NOT_FOUND"
ErrCodePaymentFailed ErrorCode = "PAYMENT_FAILED"
ErrCodePaymentRequired ErrorCode = "PAYMENT_REQUIRED"
ErrCodeMeetingCreateFailed ErrorCode = "MEETING_CREATE_FAILED"
// System errors
ErrCodeInternalServer ErrorCode = "INTERNAL_SERVER_ERROR"
ErrCodeDatabaseError ErrorCode = "DATABASE_ERROR"
ErrCodeExternalAPI ErrorCode = "EXTERNAL_API_ERROR"
ErrCodeServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
// Rate limiting errors
ErrCodeRateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED"
ErrCodeTooManyRequests ErrorCode = "TOO_MANY_REQUESTS"
)
// AppError represents an application error with structured information
type AppError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
HTTPStatus int `json:"-"`
Fields map[string]interface{} `json:"fields,omitempty"`
Cause error `json:"-"`
}
// Error implements the error interface
func (e *AppError) Error() string {
if e.Details != "" {
return fmt.Sprintf("%s: %s - %s", e.Code, e.Message, e.Details)
}
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
// Unwrap returns the underlying cause error
func (e *AppError) Unwrap() error {
return e.Cause
}
// WithDetails adds details to the error
func (e *AppError) WithDetails(details string) *AppError {
e.Details = details
return e
}
// WithField adds a field to the error
func (e *AppError) WithField(key string, value interface{}) *AppError {
if e.Fields == nil {
e.Fields = make(map[string]interface{})
}
e.Fields[key] = value
return e
}
// WithFields adds multiple fields to the error
func (e *AppError) WithFields(fields map[string]interface{}) *AppError {
if e.Fields == nil {
e.Fields = make(map[string]interface{})
}
for k, v := range fields {
e.Fields[k] = v
}
return e
}
// WithCause adds a cause error
func (e *AppError) WithCause(cause error) *AppError {
e.Cause = cause
return e
}
// New creates a new AppError
func New(code ErrorCode, message string, httpStatus int) *AppError {
return &AppError{
Code: code,
Message: message,
HTTPStatus: httpStatus,
}
}
// Wrap wraps an existing error with application error information
func Wrap(err error, code ErrorCode, message string, httpStatus int) *AppError {
return &AppError{
Code: code,
Message: message,
HTTPStatus: httpStatus,
Cause: err,
}
}
// Predefined common errors
var (
// Authentication errors
ErrInvalidCredentials = New(ErrCodeInvalidCredentials, "Invalid email or password", http.StatusUnauthorized)
ErrTokenExpired = New(ErrCodeTokenExpired, "Authentication token has expired", http.StatusUnauthorized)
ErrTokenInvalid = New(ErrCodeTokenInvalid, "Invalid authentication token", http.StatusUnauthorized)
ErrUnauthorized = New(ErrCodeUnauthorized, "Authentication required", http.StatusUnauthorized)
ErrForbidden = New(ErrCodeForbidden, "Access denied", http.StatusForbidden)
// Validation errors
ErrValidationFailed = New(ErrCodeValidationFailed, "Request validation failed", http.StatusBadRequest)
ErrInvalidInput = New(ErrCodeInvalidInput, "Invalid input provided", http.StatusBadRequest)
ErrMissingField = New(ErrCodeMissingField, "Required field is missing", http.StatusBadRequest)
// Resource errors
ErrNotFound = New(ErrCodeNotFound, "Resource not found", http.StatusNotFound)
ErrAlreadyExists = New(ErrCodeAlreadyExists, "Resource already exists", http.StatusConflict)
ErrConflict = New(ErrCodeConflict, "Resource conflict", http.StatusConflict)
// Business logic errors
ErrSlotUnavailable = New(ErrCodeSlotUnavailable, "Selected time slot is not available", http.StatusConflict)
ErrBookingNotFound = New(ErrCodeBookingNotFound, "Booking not found", http.StatusNotFound)
ErrPaymentFailed = New(ErrCodePaymentFailed, "Payment processing failed", http.StatusPaymentRequired)
ErrPaymentRequired = New(ErrCodePaymentRequired, "Payment is required", http.StatusPaymentRequired)
ErrMeetingCreateFailed = New(ErrCodeMeetingCreateFailed, "Failed to create meeting room", http.StatusInternalServerError)
// System errors
ErrInternalServer = New(ErrCodeInternalServer, "Internal server error", http.StatusInternalServerError)
ErrDatabaseError = New(ErrCodeDatabaseError, "Database operation failed", http.StatusInternalServerError)
ErrExternalAPI = New(ErrCodeExternalAPI, "External API error", http.StatusBadGateway)
ErrServiceUnavailable = New(ErrCodeServiceUnavailable, "Service temporarily unavailable", http.StatusServiceUnavailable)
// Rate limiting errors
ErrRateLimitExceeded = New(ErrCodeRateLimitExceeded, "Rate limit exceeded", http.StatusTooManyRequests)
ErrTooManyRequests = New(ErrCodeTooManyRequests, "Too many requests", http.StatusTooManyRequests)
)
// IsAppError checks if an error is an AppError
func IsAppError(err error) bool {
_, ok := err.(*AppError)
return ok
}
// GetAppError extracts AppError from error, returns nil if not an AppError
func GetAppError(err error) *AppError {
if appErr, ok := err.(*AppError); ok {
return appErr
}
return nil
}
// ErrorResponse represents the JSON error response structure
type ErrorResponse struct {
Error ErrorInfo `json:"error"`
}
// ErrorInfo contains error information for API responses
type ErrorInfo struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
Fields map[string]interface{} `json:"fields,omitempty"`
}
// ToErrorResponse converts an AppError to an ErrorResponse
func (e *AppError) ToErrorResponse() ErrorResponse {
return ErrorResponse{
Error: ErrorInfo{
Code: e.Code,
Message: e.Message,
Details: e.Details,
Fields: e.Fields,
},
}
}
// ValidationError represents a field validation error
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
Value interface{} `json:"value,omitempty"`
}
// ValidationErrors represents multiple validation errors
type ValidationErrors []ValidationError
// Error implements the error interface for ValidationErrors
func (ve ValidationErrors) Error() string {
if len(ve) == 0 {
return "validation failed"
}
if len(ve) == 1 {
return fmt.Sprintf("validation failed: %s %s", ve[0].Field, ve[0].Message)
}
return fmt.Sprintf("validation failed: %d errors", len(ve))
}
// ToAppError converts ValidationErrors to AppError
func (ve ValidationErrors) ToAppError() *AppError {
fields := make(map[string]interface{})
for _, err := range ve {
fields[err.Field] = err.Message
}
return ErrValidationFailed.WithFields(fields).WithDetails(ve.Error())
}
// NewValidationError creates a new validation error
func NewValidationError(field, message string, value interface{}) ValidationError {
return ValidationError{
Field: field,
Message: message,
Value: value,
}
}