Reputation: 3417
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
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.
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