first commit
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user