org
This commit is contained in:
360
kms/internal/errors/errors.go
Normal file
360
kms/internal/errors/errors.go
Normal file
@ -0,0 +1,360 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user