user3690467
user3690467

Reputation: 3417

Go - How to combine multiple error objects

Say I have the following code:

package lib

import (
    "errors"
    "strconv"
)

var ErrSomething = errors.New("foobar")

func SomeFunc(str string) (int, error) {
    i, err := strconv.Atoi(str)
    if err != nil {
        // how to combine ErrSomething with err?
        return 0, fmt.Errorf("%w: %w", ErrSomething, err)
    }
    
    // do other things, potentially return other errors

    return i

}

How do I combine the error returned from strconv.Atoi with my named error ErrSomething. The reason for combining is so that users of my SomeFunc() can check what exactly went wrong using my error "constants" while not losing information about the underlying error.

I have read similar questions but the usual answer is to just do: return 0, fmt.Errorf("foobar: %w", err) but this way my users can't check the error using errors.Is(err, ???)

Upvotes: 2

Views: 3388

Answers (1)

Sam Herrmann
Sam Herrmann

Reputation: 6986

You can achieve the desired behavior by creating an error type that implements the Is and Unwrap methods as follows:

package lib

import (
    "fmt"
    "strconv"
)

type FoobarError struct {
    msg      string
    original error
}

func (err *FoobarError) Error() string {
    return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}

func (err *FoobarError) Unwrap() error {
    return err.original
}

func (err *FoobarError) Is(target error) bool {
    _, ok := target.(*FoobarError)
    return ok
}

func SomeFunc() error {
    // strconv.ErrSyntax is used as a dummy error here for the error
    // that might be returned by strconv.Atoi or any other operation.
    err := strconv.ErrSyntax
    return &FoobarError{"foobar", err}
}

Usage:

package main

import (
    "errors"
    "fmt"
    "strconv"

    "lib"
)

func main() {
    if err := lib.SomeFunc(); err != nil {
        fmt.Println(err)                                // foobar: invalid syntax
        fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
        fmt.Println(errors.Is(err, strconv.ErrSyntax))  // true
    }
}

You can read more about this approach here.

Bonus

Similar to Go's os.IsExist, you may be interested in adding a helper function to your library that makes it easier for the user to check the error:

package lib

import (
  "errors"
  // ...
)

// ...

func IsFoobar(err error) bool {
    return errors.Is(err, &FoobarError{})
}

Usage:

package main

// ...

func main() {
    err := lib.SomeFunc(); 
    if lib.IsFoobar(err) {
      // ...
    }
}

Upvotes: 4

Related Questions