Files
2026-04-14 01:21:54 +00:00

337 lines
8.2 KiB
Go

package errors
import (
"fmt"
"sync"
"time"
"google.golang.org/grpc/codes"
)
// ErrorType represents a custom error type
type ErrorType interface {
Name() string
New(message string, metadata Metadata) Error
FromError(err error) Error
Validate(metadata Metadata) error
}
// ErrorRegistry manages custom error types
type ErrorRegistry struct {
types map[string]ErrorType
handlers map[string]func(Error) Error
mu sync.RWMutex
}
var (
registry = &ErrorRegistry{
types: make(map[string]ErrorType),
handlers: make(map[string]func(Error) Error),
}
)
// RegisterErrorType registers a custom error type
func RegisterErrorType(name string, errorType ErrorType) {
registry.mu.Lock()
defer registry.mu.Unlock()
registry.types[name] = errorType
}
// GetErrorType returns error type by name
func GetErrorType(name string) (ErrorType, bool) {
registry.mu.RLock()
defer registry.mu.RUnlock()
errorType, exists := registry.types[name]
return errorType, exists
}
// RegisterErrorHandler registers custom error handler
func RegisterErrorHandler(code string, handler func(Error) Error) {
registry.mu.Lock()
defer registry.mu.Unlock()
registry.handlers[code] = handler
}
// GetErrorHandler returns error handler for code
func GetErrorHandler(code string) (func(Error) Error, bool) {
registry.mu.RLock()
defer registry.mu.RUnlock()
handler, exists := registry.handlers[code]
return handler, exists
}
// ApplyHandlers applies registered handlers to error
func ApplyHandlers(err Error) Error {
if err == nil {
return nil
}
// Apply specific handler for error code
if handler, exists := GetErrorHandler(err.Code()); exists {
err = handler(err)
}
// Apply category handlers
categoryHandlers := GetCategoryHandlers(err.Category())
for _, handler := range categoryHandlers {
err = handler(err)
}
return err
}
// CategoryHandler handles errors by category
type CategoryHandler struct {
Category string
Handler func(Error) Error
}
var (
categoryHandlers = make(map[string][]func(Error) Error)
categoryMu sync.RWMutex
)
// RegisterCategoryHandler registers handler for error category
func RegisterCategoryHandler(category string, handler func(Error) Error) {
categoryMu.Lock()
defer categoryMu.Unlock()
if categoryHandlers[category] == nil {
categoryHandlers[category] = make([]func(Error) Error, 0)
}
categoryHandlers[category] = append(categoryHandlers[category], handler)
}
// GetCategoryHandlers returns handlers for category
func GetCategoryHandlers(category string) []func(Error) Error {
categoryMu.RLock()
defer categoryMu.RUnlock()
handlers := make([]func(Error) Error, len(categoryHandlers[category]))
copy(handlers, categoryHandlers[category])
return handlers
}
// BaseErrorType provides base implementation for custom error types
type BaseErrorType struct {
name string
defaultCode string
category string
httpStatus int
grpcCode int
validator func(Metadata) error
}
// NewBaseErrorType creates new base error type
func NewBaseErrorType(name, defaultCode, category string, httpStatus int, grpcCode int) *BaseErrorType {
return &BaseErrorType{
name: name,
defaultCode: defaultCode,
category: category,
httpStatus: httpStatus,
grpcCode: grpcCode,
}
}
// Name returns error type name
func (bet *BaseErrorType) Name() string {
return bet.name
}
// New creates new error of this type
func (bet *BaseErrorType) New(message string, metadata Metadata) Error {
code := bet.defaultCode
if c, exists := metadata["code"].(string); exists {
code = c
}
return &AppError{
code: code,
message: message,
category: bet.category,
httpStatus: bet.httpStatus,
grpcCode: codes.Code(bet.grpcCode),
metadata: metadata,
timestamp: time.Now(),
stackTrace: captureStackTrace(),
}
}
// FromError converts existing error to this type
func (bet *BaseErrorType) FromError(err error) Error {
appErr := FromError(err)
if appErr == nil {
return bet.New(err.Error(), make(Metadata))
}
return bet.New(appErr.Error(), appErr.Metadata())
}
// Validate validates metadata
func (bet *BaseErrorType) Validate(metadata Metadata) error {
if bet.validator != nil {
return bet.validator(metadata)
}
return nil
}
// SetValidator sets validator function
func (bet *BaseErrorType) SetValidator(validator func(Metadata) error) {
bet.validator = validator
}
// BusinessErrorType represents business logic errors
type BusinessErrorType struct {
*BaseErrorType
ruleName string
}
// NewBusinessErrorType creates new business error type
func NewBusinessErrorType(name, ruleName string) *BusinessErrorType {
return &BusinessErrorType{
BaseErrorType: NewBaseErrorType(name, ErrCodeBusinessRule, CategoryBusiness, 400, int(codes.FailedPrecondition)),
ruleName: ruleName,
}
}
// RuleName returns business rule name
func (bet *BusinessErrorType) RuleName() string {
return bet.ruleName
}
// ValidationErrorType represents validation errors
type ValidationErrorType struct {
*BaseErrorType
field string
rule string
}
// NewValidationErrorType creates new validation error type
func NewValidationErrorType(name, field, rule string) *ValidationErrorType {
return &ValidationErrorType{
BaseErrorType: NewBaseErrorType(name, ErrCodeValidationFailed, CategoryValidation, 400, int(codes.InvalidArgument)),
field: field,
rule: rule,
}
}
// Field returns field name
func (vet *ValidationErrorType) Field() string {
return vet.field
}
// Rule returns validation rule
func (vet *ValidationErrorType) Rule() string {
return vet.rule
}
// ExternalErrorType represents external service errors
type ExternalErrorType struct {
*BaseErrorType
serviceName string
retryable bool
}
// NewExternalErrorType creates new external error type
func NewExternalErrorType(name, serviceName string, retryable bool) *ExternalErrorType {
return &ExternalErrorType{
BaseErrorType: NewBaseErrorType(name, ErrCodeExternalError, CategoryExternal, 502, int(codes.Unavailable)),
serviceName: serviceName,
retryable: retryable,
}
}
// ServiceName returns external service name
func (eet *ExternalErrorType) ServiceName() string {
return eet.serviceName
}
// Retryable returns if error is retryable
func (eet *ExternalErrorType) Retryable() bool {
return eet.retryable
}
// ErrorFactory creates errors from configuration
type ErrorFactory struct {
config map[string]interface{}
mu sync.RWMutex
}
// NewErrorFactory creates new error factory
func NewErrorFactory() *ErrorFactory {
return &ErrorFactory{
config: make(map[string]interface{}),
}
}
// Configure configures error factory
func (ef *ErrorFactory) Configure(config map[string]interface{}) {
ef.mu.Lock()
defer ef.mu.Unlock()
ef.config = config
}
// CreateError creates error from configuration
func (ef *ErrorFactory) CreateError(errorName string, message string, metadata Metadata) Error {
ef.mu.RLock()
defer ef.mu.RUnlock()
// Try to get error type from configuration
if errorTypeConfig, exists := ef.config[errorName]; exists {
if configMap, ok := errorTypeConfig.(map[string]interface{}); ok {
name, _ := configMap["name"].(string)
if errorType, exists := GetErrorType(name); exists {
return errorType.New(message, metadata)
}
}
}
// Fallback to creating standard error
return NewWithMetadata(ErrCodeInternalError, message, metadata)
}
// GetRegisteredTypes returns all registered error types
func GetRegisteredTypes() map[string]ErrorType {
registry.mu.RLock()
defer registry.mu.RUnlock()
types := make(map[string]ErrorType)
for k, v := range registry.types {
types[k] = v
}
return types
}
// GetRegisteredHandlers returns all registered handlers
func GetRegisteredHandlers() map[string]func(Error) Error {
registry.mu.RLock()
defer registry.mu.RUnlock()
handlers := make(map[string]func(Error) Error)
for k, v := range registry.handlers {
handlers[k] = v
}
return handlers
}
// ValidateErrorType validates error type configuration
func ValidateErrorType(errorType ErrorType) error {
if errorType.Name() == "" {
return fmt.Errorf("error type name cannot be empty")
}
// Test error creation
testErr := errorType.New("test", make(Metadata))
if testErr == nil {
return fmt.Errorf("error type failed to create error")
}
if testErr.Code() == "" {
return fmt.Errorf("error type must produce error with code")
}
return nil
}