mirror of
https://github.com/aykhans/go-utils.git
synced 2025-10-15 18:25:57 +00:00
186 lines
5.8 KiB
Go
186 lines
5.8 KiB
Go
package errors
|
|
|
|
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
|
|
}
|
|
|
|
// HandleErrorOrDefault 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 executes the default handler (dft) and returns its result.
|
|
// If dft is nil, unmatched errors return nil (effectively suppressing the error).
|
|
// This function is useful when you want to handle specific error cases explicitly
|
|
// while providing a fallback handler for all other errors.
|
|
//
|
|
// Example:
|
|
//
|
|
// result := HandleErrorOrDefault(err,
|
|
// func(e error) error {
|
|
// // Default handler for unmatched errors
|
|
// return fmt.Errorf("unexpected error: %w", e)
|
|
// },
|
|
// OnSentinelError(context.Canceled, func(e error) error {
|
|
// return fmt.Errorf("operation canceled")
|
|
// }),
|
|
// OnCustomError(func(e *ValidationError) error {
|
|
// return fmt.Errorf("validation failed: %w", e)
|
|
// }),
|
|
// )
|
|
//
|
|
// // Suppress unmatched errors by passing nil as default handler
|
|
// result := HandleErrorOrDefault(err, nil,
|
|
// OnSentinelError(io.EOF, func(e error) error {
|
|
// return errors.New("EOF handled")
|
|
// }),
|
|
// ) // Returns nil for unmatched errors
|
|
func HandleErrorOrDefault(err error, dft ErrorHandler, matchers ...ErrorMatcher) error {
|
|
ok, err := HandleError(err, matchers...)
|
|
if !ok {
|
|
if dft == nil {
|
|
return nil
|
|
}
|
|
return dft(err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// OnSentinelError creates an ErrorMatcher for sentinel errors.
|
|
// Sentinel errors are predefined error values that are compared using errors.Is.
|
|
//
|
|
// This is used with HandleError or HandleErrorOrDie to match specific error
|
|
// values like io.EOF, context.Canceled, or custom sentinel errors defined with
|
|
// errors.New or fmt.Errorf.
|
|
//
|
|
// The handler function receives the original error and can return a new error
|
|
// or nil to suppress it.
|
|
//
|
|
// Example:
|
|
//
|
|
// matcher := OnSentinelError(io.EOF, func(e error) error {
|
|
// log.Println("reached end of file")
|
|
// return nil // suppress EOF error
|
|
// })
|
|
func OnSentinelError(sentinelErr error, handler ErrorHandler) ErrorMatcher {
|
|
return ErrorMatcher{
|
|
ErrorType: sentinelErr,
|
|
Handler: handler,
|
|
IsSentinel: true,
|
|
}
|
|
}
|
|
|
|
// OnCustomError creates an ErrorMatcher for custom error types.
|
|
// Custom error types are struct types that implement the error interface,
|
|
// and are matched using errors.As to unwrap error chains.
|
|
//
|
|
// The type parameter T specifies the error type to match. The handler function
|
|
// receives the unwrapped typed error, allowing you to access type-specific fields
|
|
// and methods.
|
|
//
|
|
// This is particularly useful for handling errors with additional context or data,
|
|
// such as validation errors, network errors, or domain-specific errors.
|
|
//
|
|
// Example:
|
|
//
|
|
// type ValidationError struct {
|
|
// Field string
|
|
// Msg string
|
|
// }
|
|
// func (e *ValidationError) Error() string {
|
|
// return fmt.Sprintf("%s: %s", e.Field, e.Msg)
|
|
// }
|
|
//
|
|
// matcher := OnCustomError(func(e *ValidationError) error {
|
|
// log.Printf("validation failed on field %s: %s", e.Field, e.Msg)
|
|
// return fmt.Errorf("invalid input: %w", e)
|
|
// })
|
|
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,
|
|
}
|
|
}
|