DEV Community

Nicola Apicella
Nicola Apicella

Posted on

2

Golang error updates

Since go 1.20, golang has improved the ergonomics of error handling. Noteworthy improvements include:

  • errors.Is and errors.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()
    }
}

Enter fullscreen mode Exit fullscreen mode

Heroku

Save time with this productivity hack.

See how Heroku MCP Server connects tools like Cursor to Heroku, so you can build, deploy, and manage apps—right from your editor.

Learn More

Top comments (0)

Developer-first embedded dashboards

Developer-first embedded dashboards

Embed in minutes, load in milliseconds, extend infinitely. Import any chart, connect to any database, embed anywhere. Scale elegantly, monitor effortlessly, CI/CD & version control.

Get early access

👋 Kindness is contagious

Explore this practical breakdown on DEV’s open platform, where developers from every background come together to push boundaries. No matter your experience, your viewpoint enriches the conversation.

Dropping a simple “thank you” or question in the comments goes a long way in supporting authors—your feedback helps ideas evolve.

At DEV, shared discovery drives progress and builds lasting bonds. If this post resonated, a quick nod of appreciation can make all the difference.

Okay