This module introduces the fuctionality of handling Go errors as first-class citizens by:
- providing a way to attach inspectable context to an existing error;
- making errors' contexts inspectable;
- making errors unit-testing friendly.
This module was inspired by the github.com/pkg/errors one which solves some error handling issues in Go, but still is hardly usable for the unit-testing.
All errors returned from the utility functions Wrap, WithMessage and WrapWithMessage return an object conforming to the error interface with a stacktrace created at the moment of function invocation.
err := errors.Wrap(errors.New("inner error"), errors.New("outer error"))
fmt.Printf("%s", err)
// Output:
// outer error : inner error
fmt.Printf("%+v", err)
// Output:
// outter error : inner error
// github.com/ameteiko/errors/errors.go:52 errors.Wrap()
// github.com/ameteiko/errors/examples/main.go:10 main.main()
The error message consists of all error messages joined with " : " sequence.
Internally all errors are merged into an internal error queue object with a stacktrace at the moment of creation. It's recommended to have a single error-flow path for the application, meaning that parameters to the Wrap function must not be composite errors coming from different executions paths, because merging them won't make much sense from the operational perspective.
go get github.com/ameteiko/errors
This function is a wrapper for the fmt.Errorf function to allow use a single package for error handling.
err := errors.New("validation failed for %q", username)
Utility function Wrap merges several errors passed as a variadic parameters into one. All nil errors will be filtered out of the resulting error. The recommended way of using the function is to attach an application error to the error returned from the 3rd-party module.
// Some package-level error
var errParsing = errors.New("parsing error")
func parseResponse(r []byte) error {
var resp Response
if err := json.Unmarshal(r, &resp); err != nil {
// Wrap the error returned from the json package with application identifiable error.
return errors.Wrap(err, errParsing)
}
}
If several errors contain stacktraces, the new one will be created at the moment of the Wrap instantiation. If only one contains a stacktrace, then it will be reused.
WithMessage attaches a formatted message for the error. Best used to add some error context to the error, like parameters that caused an error.
func parseResponse(r []byte) error {
var resp Response
if err := json.Unmarshal(r, &resp); err != nil {
return errors.WithMessage(err, "unmarshalling error for %v", r)
}
}
WrapWithMessage wraps several errors and attaches a message to them. This function is a combination of Wrap and WithMessage.
var errParsing = errors.New("parsing error")
func parseResponse(r []byte) error {
var resp Response
if err := json.Unmarshal(r, &resp); err != nil {
// Wrap the error returned from the json package with application identifiable error and a message.
return errors.WrapWithMessage(err, errParsing, "parsing error for %v", r)
}
}
Inspects the source error and returns matched target error object from it.
parsingErr := parseResponse()
if err := errors.Fetch(parsingErr, errParsing); err != nil {
log.Println("parsing error", err)
}
Inspects the error and returns the entry matched by the type. Returns nil if If target is nil or not a pointer.
// LoggableErr log error messages when they are handled.
type LoggableErr struct {
msg string
}
func (e LoggableErr) Error() string { return e.msg }
func (e LoggableErr) Log() string { log.Println(e.msg) }
errParsing := LoggableErr{msg: "parsing error"}
func main() {
parsingErr := parseResponse("}")
if err := errors.FetchByType(parsingErr, (*LoggableErr)(nil)); err != nil {
loggableErr := err.(LoggableErr)
loggableErr.Log()
}
}
Inspects the error and returns all the entries matched by the type. Returns nil if If target is nil or not a pointer.
type ValidationError struct {
msg string
}
func (e ValidationError) Error() string { return e.msg }
var (
errNameIsEmpty = ValidationError{ msg: "name is empty" }
errNameIsTooLong = ValidationError{ msg: "name is too long" }
errNameIsTooShort = ValidationError{ msg: "name is too short" }
errPwdIsEmpty = ValidationError{ msg: "password is empty" }
)
func validateUser(name, pwd string) (err error) {
if name == "" {
err = errors.Wrap(err, errNameIsEmpty)
}
if pwd == "" {
err = errors.Wrap(err, errPwdIsEmpty)
}
// ... the rest of validations
return err
}
func main() {
err := validateUser("", "")
errs := errors.FetchAllByType(err, (*ValidationError)(nil))
fmt.Println(errs)
// Prints two errors: errPwdIsEmpty, errNameIsEmpty
}