package errors import ( "fmt" "net/http" ) // ErrorCode represents different types of errors in the system type ErrorCode string const ( // Authentication and Authorization errors ErrUnauthorized ErrorCode = "UNAUTHORIZED" ErrForbidden ErrorCode = "FORBIDDEN" ErrInvalidToken ErrorCode = "INVALID_TOKEN" ErrTokenExpired ErrorCode = "TOKEN_EXPIRED" ErrInvalidCredentials ErrorCode = "INVALID_CREDENTIALS" // Validation errors ErrValidationFailed ErrorCode = "VALIDATION_FAILED" ErrInvalidInput ErrorCode = "INVALID_INPUT" ErrMissingField ErrorCode = "MISSING_FIELD" ErrInvalidFormat ErrorCode = "INVALID_FORMAT" // Resource errors ErrNotFound ErrorCode = "NOT_FOUND" ErrAlreadyExists ErrorCode = "ALREADY_EXISTS" ErrConflict ErrorCode = "CONFLICT" // System errors ErrInternal ErrorCode = "INTERNAL_ERROR" ErrDatabase ErrorCode = "DATABASE_ERROR" ErrExternal ErrorCode = "EXTERNAL_SERVICE_ERROR" ErrTimeout ErrorCode = "TIMEOUT" ErrRateLimit ErrorCode = "RATE_LIMIT_EXCEEDED" // Business logic errors ErrInsufficientPermissions ErrorCode = "INSUFFICIENT_PERMISSIONS" ErrApplicationNotFound ErrorCode = "APPLICATION_NOT_FOUND" ErrTokenNotFound ErrorCode = "TOKEN_NOT_FOUND" ErrPermissionNotFound ErrorCode = "PERMISSION_NOT_FOUND" ErrInvalidApplication ErrorCode = "INVALID_APPLICATION" ErrTokenCreationFailed ErrorCode = "TOKEN_CREATION_FAILED" ) // AppError represents an application error with context type AppError struct { Code ErrorCode `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` StatusCode int `json:"-"` Internal error `json:"-"` Context map[string]interface{} `json:"context,omitempty"` } // Error implements the error interface func (e *AppError) Error() string { if e.Internal != nil { return fmt.Sprintf("%s: %s (internal: %v)", e.Code, e.Message, e.Internal) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // WithContext adds context information to the error func (e *AppError) WithContext(key string, value interface{}) *AppError { if e.Context == nil { e.Context = make(map[string]interface{}) } e.Context[key] = value return e } // WithDetails adds additional details to the error func (e *AppError) WithDetails(details string) *AppError { e.Details = details return e } // WithInternal adds the underlying error func (e *AppError) WithInternal(err error) *AppError { e.Internal = err return e } // New creates a new application error func New(code ErrorCode, message string) *AppError { return &AppError{ Code: code, Message: message, StatusCode: getHTTPStatusCode(code), } } // Wrap wraps an existing error with application error context func Wrap(err error, code ErrorCode, message string) *AppError { return &AppError{ Code: code, Message: message, StatusCode: getHTTPStatusCode(code), Internal: err, } } // getHTTPStatusCode maps error codes to HTTP status codes func getHTTPStatusCode(code ErrorCode) int { switch code { case ErrUnauthorized, ErrInvalidToken, ErrTokenExpired, ErrInvalidCredentials: return http.StatusUnauthorized case ErrForbidden, ErrInsufficientPermissions: return http.StatusForbidden case ErrValidationFailed, ErrInvalidInput, ErrMissingField, ErrInvalidFormat: return http.StatusBadRequest case ErrNotFound, ErrApplicationNotFound, ErrTokenNotFound, ErrPermissionNotFound: return http.StatusNotFound case ErrAlreadyExists, ErrConflict: return http.StatusConflict case ErrRateLimit: return http.StatusTooManyRequests case ErrTimeout: return http.StatusRequestTimeout case ErrInternal, ErrDatabase, ErrExternal, ErrTokenCreationFailed: return http.StatusInternalServerError default: return http.StatusInternalServerError } } // IsRetryable determines if an error is retryable func (e *AppError) IsRetryable() bool { switch e.Code { case ErrTimeout, ErrExternal, ErrDatabase: return true default: return false } } // IsClientError determines if an error is a client error (4xx) func (e *AppError) IsClientError() bool { return e.StatusCode >= 400 && e.StatusCode < 500 } // IsServerError determines if an error is a server error (5xx) func (e *AppError) IsServerError() bool { return e.StatusCode >= 500 } // Common error constructors for frequently used errors // NewUnauthorizedError creates an unauthorized error func NewUnauthorizedError(message string) *AppError { return New(ErrUnauthorized, message) } // NewForbiddenError creates a forbidden error func NewForbiddenError(message string) *AppError { return New(ErrForbidden, message) } // NewValidationError creates a validation error func NewValidationError(message string) *AppError { return New(ErrValidationFailed, message) } // NewNotFoundError creates a not found error func NewNotFoundError(resource string) *AppError { return New(ErrNotFound, fmt.Sprintf("%s not found", resource)) } // NewAlreadyExistsError creates an already exists error func NewAlreadyExistsError(resource string) *AppError { return New(ErrAlreadyExists, fmt.Sprintf("%s already exists", resource)) } // NewInternalError creates an internal server error func NewInternalError(message string) *AppError { return New(ErrInternal, message) } // NewDatabaseError creates a database error func NewDatabaseError(operation string, err error) *AppError { return Wrap(err, ErrDatabase, fmt.Sprintf("Database operation failed: %s", operation)) } // NewTokenError creates a token-related error func NewTokenError(message string) *AppError { return New(ErrInvalidToken, message) } // NewApplicationError creates an application-related error func NewApplicationError(message string) *AppError { return New(ErrInvalidApplication, message) } // NewPermissionError creates a permission-related error func NewPermissionError(message string) *AppError { return New(ErrInsufficientPermissions, message) } // NewAuthenticationError creates an authentication error func NewAuthenticationError(message string) *AppError { return New(ErrUnauthorized, message) } // NewConfigurationError creates a configuration error func NewConfigurationError(message string) *AppError { return New(ErrInternal, message) } // ErrorResponse represents the JSON error response format type ErrorResponse struct { Error string `json:"error"` Message string `json:"message"` Code ErrorCode `json:"code"` Details string `json:"details,omitempty"` Context map[string]interface{} `json:"context,omitempty"` } // ToResponse converts an AppError to an ErrorResponse func (e *AppError) ToResponse() ErrorResponse { return ErrorResponse{ Error: string(e.Code), Message: e.Message, Code: e.Code, Details: e.Details, Context: e.Context, } } // Recovery handles panic recovery and converts to appropriate errors func Recovery(recovered interface{}) *AppError { switch v := recovered.(type) { case *AppError: return v case error: return Wrap(v, ErrInternal, "Internal server error occurred") case string: return New(ErrInternal, v) default: return New(ErrInternal, "Unknown internal error occurred") } } // Chain represents a chain of errors for better error tracking type Chain struct { errors []*AppError } // NewChain creates a new error chain func NewChain() *Chain { return &Chain{ errors: make([]*AppError, 0), } } // Add adds an error to the chain func (c *Chain) Add(err *AppError) *Chain { c.errors = append(c.errors, err) return c } // HasErrors returns true if the chain has any errors func (c *Chain) HasErrors() bool { return len(c.errors) > 0 } // First returns the first error in the chain func (c *Chain) First() *AppError { if len(c.errors) == 0 { return nil } return c.errors[0] } // Last returns the last error in the chain func (c *Chain) Last() *AppError { if len(c.errors) == 0 { return nil } return c.errors[len(c.errors)-1] } // All returns all errors in the chain func (c *Chain) All() []*AppError { return c.errors } // Error implements the error interface for the chain func (c *Chain) Error() string { if len(c.errors) == 0 { return "no errors" } if len(c.errors) == 1 { return c.errors[0].Error() } return fmt.Sprintf("multiple errors: %s (and %d more)", c.errors[0].Error(), len(c.errors)-1) } // Helper functions to check error types // IsNotFound checks if an error is a not found error func IsNotFound(err error) bool { if appErr, ok := err.(*AppError); ok { return appErr.Code == ErrNotFound || appErr.Code == ErrApplicationNotFound || appErr.Code == ErrTokenNotFound || appErr.Code == ErrPermissionNotFound } return false } // IsValidationError checks if an error is a validation error func IsValidationError(err error) bool { if appErr, ok := err.(*AppError); ok { return appErr.Code == ErrValidationFailed || appErr.Code == ErrInvalidInput || appErr.Code == ErrMissingField || appErr.Code == ErrInvalidFormat } return false } // IsAuthenticationError checks if an error is an authentication error func IsAuthenticationError(err error) bool { if appErr, ok := err.(*AppError); ok { return appErr.Code == ErrUnauthorized || appErr.Code == ErrInvalidToken || appErr.Code == ErrTokenExpired || appErr.Code == ErrInvalidCredentials } return false } // IsAuthorizationError checks if an error is an authorization error func IsAuthorizationError(err error) bool { if appErr, ok := err.(*AppError); ok { return appErr.Code == ErrForbidden || appErr.Code == ErrInsufficientPermissions } return false } // IsConflictError checks if an error is a conflict error func IsConflictError(err error) bool { if appErr, ok := err.(*AppError); ok { return appErr.Code == ErrAlreadyExists || appErr.Code == ErrConflict } return false } // IsInternalError checks if an error is an internal server error func IsInternalError(err error) bool { if appErr, ok := err.(*AppError); ok { return appErr.Code == ErrInternal || appErr.Code == ErrDatabase || appErr.Code == ErrExternal || appErr.Code == ErrTokenCreationFailed } return false } // IsConfigurationError checks if an error is a configuration error func IsConfigurationError(err error) bool { if appErr, ok := err.(*AppError); ok { // Configuration errors are typically mapped to internal errors return appErr.Code == ErrInternal && appErr.Message != "" } return false }