Robust Error Handling with Error Wrapping in Go
Error handling is a crucial aspect of writing reliable Go programs. Often, errors propagate up the call stack without providing sufficient context about where and why they occurred. This challenge focuses on implementing error wrapping to enrich error messages with contextual information, making debugging and troubleshooting significantly easier.
Problem Description
You are tasked with creating a Go package that provides utility functions for wrapping errors. Error wrapping allows you to add context to an existing error without losing the original error information. Your package should include the following:
-
WrapError(err error, message string)Function: This function takes an existingerrorand amessage(string) as input. It should return a new error that wraps the original error, adding the provided message to the error's description. The new error should still contain the original error, allowing for unwrapping later. -
UnwrapError(err error)Function: This function takes an error as input and attempts to unwrap it. If the error is wrapped (i.e., it implements theUnwrapperinterface), it returns the underlying error. If the error is not wrapped, it returns the original error unchanged. -
IsErrorOfType(err error, targetErrorType error)Function: This function takes an error and a target error type as input. It checks if the error or any of its wrapped errors are of the specified type. It returnstrueif a matching error is found, andfalseotherwise.
You should define a custom type WrappedError to represent wrapped errors. This type should implement the error interface and the Unwrapper interface (from the errors package).
Examples
Example 1:
Input:
err := errors.New("original error")
wrappedErr := WrapError(err, "Failed to process data")
Output:
wrappedErr.Error() // Returns: "Failed to process data: original error"
unwrappedErr := UnwrapError(wrappedErr)
unwrappedErr.Error() // Returns: "original error"
Explanation: WrapError creates a new error that includes the original error and the added message. UnwrapError successfully retrieves the original error.
Example 2:
Input:
type MyCustomError error
func (e MyCustomError) Error() string {
return "Custom Error"
}
err := errors.New("another error")
wrappedErr := WrapError(err, "Something went wrong")
result := IsErrorOfType(wrappedErr, MyCustomError(err))
Output:
result // Returns: false
Explanation: IsErrorOfType correctly identifies that the wrapped error is not of the MyCustomError type.
Example 3:
Input:
type DatabaseError error
func (e DatabaseError) Error() string {
return "Database connection failed"
}
dbErr := DatabaseError("connection timeout")
wrappedErr := WrapError(dbErr, "Failed to connect to the database")
result := IsErrorOfType(wrappedErr, DatabaseError(""))
Output:
result // Returns: true
Explanation: IsErrorOfType correctly identifies that the wrapped error contains a DatabaseError.
Constraints
- The
WrapErrorfunction must not panic. - The
UnwrapErrorfunction must handle errors that are not wrapped gracefully (return the original error). - The
IsErrorOfTypefunction should handle nil errors correctly (return false). - The package should be well-documented with clear comments explaining the purpose of each function and type.
- The solution should be idiomatic Go.
Notes
- Consider using the
errors.Isfunction for type checking withinIsErrorOfTypefor more robust error type comparisons. - Think about how to handle multiple levels of error wrapping.
- The
Unwrapperinterface is defined in theerrorspackage:type Unwrapper interface { Unwrap() error } - Focus on creating a reusable and well-structured error wrapping utility. The goal is to provide a clean and reliable way to add context to errors in your Go applications.