We have one function that returns errors one of them is wrapped by errors.Wrap(), the others are not.
var ErrTest1 = errors.New("error test 1")
var ErrTest2 = errors.New("error test 2")
var ErrRPC = errors.New("error rpc")
func rpcCall() error {
return ErrRPC
}
func testErrWrap(a int) error {
if a == 1 {
return ErrTest1
} else if a == 2 {
return ErrTest2
} else {
err := rpcCall()
if err != nil {
return errors.Wrap(ErrRPC, "call rpc err")
}
}
return nil
}
We have two solutions, one is
err := testErrWrap(3)
if errors.Unwrap(err) != nil {
fmt.Println(errors.Unwrap(err))
}
the other is
err := testErrWrap(3)
if !errors.Is(err, ErrTest2) && !errors.Is(err, ErrTest1) {
tErr := errors.Unwrap(err)
fmt.Println(tErr)
}
We want to know the elegant way to distinguish errors are wrapped or not in Go?
In most cases we're looking to find out if we got a specific type of error in order to do some special logic to handle it. I'd argue there are better ways to do this than looking at whether the error was wrapped or not.
In this case I'd propose using custom error types and using errors.Is or errors.As
First let's create some code that has a custom error type:
type ErrRPC struct {
retryPort int
}
func (e ErrRPC) Error() string {
return "Oh no, an rpc error!"
}
func testErrWrap(a int) error {
switch a {
case 1:
return errors.New("errors test 1")
case 2:
return errors.New("errors test 1")
default:
err := rpcCall(9000)
return fmt.Errorf("errors test: %w", err) // Wraps the ErrRPC
}
}
func rpcCall(port int) error {
return ErrRPC{retryPort: port + 1}
}
If we're only interested in going down a different code path when we get a specific error type I'd go with errors.Is
func main() {
for i := 1; i <= 3; i++ {
if err := testErrWrap(i); err != nil {
if errors.Is(err, ErrRPC{}) {
println("rpc error")
} else {
println("regular error")
}
}
}
}
If we need to use a property of the error value for something, errors.As comes in handy.
func main() {
for i := 1; i <= 3; i++ {
if err := testErrWrap(i); err != nil {
var rpcErr ErrRPC
if errors.As(err, &rpcErr) {
fmt.Printf("rpc error, retrying on port: %d", rpcErr.retryPort)
} else {
println("regular error")
}
}
}
}
Another method from puellanivis
Also consider testing for behavior:
err := testErrWrap(3)
var wrappedErr interface { Unwrap() error }
if errors.As(err, &wrappedErr) {
fmt.Println(errors.Unwrap(err))
}
But also of note,
errors.Wrap(…)double wraps your error: oneWithMessageand oneWithStack. So using errors.Unwrap(err) on the error fromerrors.Wrap(ErrRPC, "call rpc err")will not give you ErrRPC.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With