Since go 1.20, golang has improved the ergonomics of error handling. Noteworthy improvements include:
-
errors.Is
anderrors.As
have been updated to work on tree of errors -
fmt.Errorf
accepts multiple %w format verbs, allowing to join multiple errors - The
Unwrap []error
function allows traversing through tree of errors
Follow some tests demonstrating the practical application of these enhancements (also available in the golang playground):
package main
import (
"errors"
"fmt"
"testing"
)
func TestErrorfForWrapping(t *testing.T) {
err1 := errors.New("a")
err2 := errors.New("b")
// the parentheses in the string are arbitrary
wrap := fmt.Errorf("%w (%w)", err1, err2)
if errors.Is(wrap, err1) == false {
t.Fatal()
}
if errors.Is(wrap, err2) == false {
t.Fatal()
}
}
func TestStatusCodeInErrors(t *testing.T) {
var (
//
// client errors
//
clientError = errors.New("client error")
//
// client error causes
//
unauthorized = errors.New("unauthorized")
resourceNotFound = errors.New("resource not found")
//
// server errors
//
serverError = errors.New("server error")
//
// server error causes
//
badGatewayError = errors.New("badGateway")
internalServerError = errors.New("internal server error")
)
getStatusCode := func(err error) (int, string) {
if errors.Is(err, clientError) {
if errors.Is(err, unauthorized) {
return 400, "unauthorized"
}
if errors.Is(err, resourceNotFound) {
return 400, "resource not found"
}
return 400, "no error type"
} else if errors.Is(err, serverError) {
if errors.Is(err, badGatewayError) {
return 500, "bad gateway"
}
if errors.Is(err, internalServerError) {
return 500, "internal server error"
}
}
return 500, "no error type"
}
err := fmt.Errorf("%w (%w)", clientError, unauthorized)
if code, mex := getStatusCode(err); code != 400 || mex != "unauthorized" {
t.Fatal()
}
err = fmt.Errorf("%w (%w)", clientError, resourceNotFound)
if code, mex := getStatusCode(err); code != 400 || mex != "resource not found" {
t.Fatal()
}
err = fmt.Errorf("%w (%w)", serverError, internalServerError)
if code, mex := getStatusCode(err); code != 500 || mex != "internal server error" {
t.Fatal()
}
}
type customError struct {
wrapped []error
}
func (t *customError) Unwrap() []error {
return t.wrapped
}
func (t *customError) Error() string {
return "custom error"
}
func TestCustomErrorWrapping(t *testing.T) {
err1 := errors.New("a")
err2 := &customError{wrapped: []error{err1}}
// it's much more convenient to do this
// wrap := fmt.Errorf("%w (%w)", err1, err2) and you do not need to implement Unwrap
if errors.Is(err2, err1) == false {
t.Fatal()
}
if errors.Is(err2, err2) == false {
t.Fatal()
}
if errors.Is(err2.Unwrap()[0], err1) == false {
t.Fatal()
}
}
Top comments (0)