package utils import ( "errors" "fmt" "reflect" ) // ErrorHandler represents a function that handles a specific error type type ErrorHandler func(error) error // ErrorMatcher holds the error type/value and its handler type ErrorMatcher struct { ErrorType any // Can be error value (sentinel) or error type Handler ErrorHandler IsSentinel bool // true for sentinel errors, false for custom types } // HandleError processes an error against a list of matchers and executes the appropriate handler. // It returns (true, handlerResult) if a matching handler is found and executed, // or (false, nil) if no matcher matches the error. // If err is nil, returns (true, nil). // // Example: // // handled, result := HandleError(err, // OnSentinelError(io.EOF, func(e error) error { // return nil // EOF is expected, ignore it // }), // OnCustomError(func(e *CustomError) error { // return fmt.Errorf("custom error: %w", e) // }), // ) func HandleError(err error, matchers ...ErrorMatcher) (bool, error) { if err == nil { return true, nil } for _, matcher := range matchers { if matcher.IsSentinel { // Handle sentinel errors with errors.Is if sentinelErr, ok := matcher.ErrorType.(error); ok { if errors.Is(err, sentinelErr) { return true, matcher.Handler(err) } } } else { // Handle custom error types with errors.As errorType := reflect.TypeOf(matcher.ErrorType) errorValue := reflect.New(errorType).Interface() if errors.As(err, errorValue) { return true, matcher.Handler(err) } } } return false, err // No matcher found } // HandleErrorOrDie processes an error against a list of matchers and executes the appropriate handler. // If a matching handler is found, it returns the handler's result. // If no matcher matches the error, it panics with a descriptive message. // This function is useful when all expected error types must be handled explicitly. // // Example: // // result := HandleErrorOrDie(err, // OnSentinelError(context.Canceled, func(e error) error { // return fmt.Errorf("operation canceled") // }), // OnCustomError(func(e *ValidationError) error { // return fmt.Errorf("validation failed: %w", e) // }), // ) // Panics if err doesn't match any handler func HandleErrorOrDie(err error, matchers ...ErrorMatcher) error { ok, err := HandleError(err, matchers...) if !ok { panic(fmt.Sprintf("Unhandled error of type %T: %v", err, err)) } return err } func OnSentinelError(sentinelErr error, handler ErrorHandler) ErrorMatcher { return ErrorMatcher{ ErrorType: sentinelErr, Handler: handler, IsSentinel: true, } } func OnCustomError[T error](handler func(T) error) ErrorMatcher { var zero T return ErrorMatcher{ ErrorType: zero, Handler: func(err error) error { var typedErr T if errors.As(err, &typedErr) { return handler(typedErr) } return nil }, IsSentinel: false, } }