Hone logo
Hone
Problems

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:

  1. WrapError(err error, message string) Function: This function takes an existing error and a message (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.

  2. 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 the Unwrapper interface), it returns the underlying error. If the error is not wrapped, it returns the original error unchanged.

  3. 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 returns true if a matching error is found, and false otherwise.

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 WrapError function must not panic.
  • The UnwrapError function must handle errors that are not wrapped gracefully (return the original error).
  • The IsErrorOfType function 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.Is function for type checking within IsErrorOfType for more robust error type comparisons.
  • Think about how to handle multiple levels of error wrapping.
  • The Unwrapper interface is defined in the errors package: 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.
Loading editor...
go