I'm playing around with error wrapping in Go and have a function that returns a wrapped custom error type. What I would like to do is iterate a list of expected errors and test if the output of the function wraps these the expected errors.
I've found that putting custom errors inside a []error
means that the type of the custom errors will be *fmt.wrapError
, which means errors.As()
pretty much always return true.
As an example, consider the following code:
package main
import (
"errors"
"fmt"
)
type AnotherError struct {
}
func (e *AnotherError) Error() string {
return "another error"
}
type MissingAttrError struct {
missingAttr string
}
func (e *MissingAttrError) Error() string {
return fmt.Sprintf("missing attribute: %s", e.missingAttr)
}
func DoSomething() error {
e := &MissingAttrError{missingAttr: "Key"}
return fmt.Errorf("DoSomething(): %w", e)
}
func main() {
err := DoSomething()
expectedErrOne := &MissingAttrError{}
expectedErrTwo := &AnotherError{}
expectedErrs := []error{expectedErrOne, expectedErrTwo}
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expectedErrOne, errors.As(err, &expectedErrOne))
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expectedErrTwo, errors.As(err, &expectedErrTwo))
for i := range expectedErrs {
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expectedErrs[i], errors.As(err, &expectedErrs[i]))
}
}
The output of this is
Is err 'DoSomething(): missing attribute: Key' type '*main.MissingAttrError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*main.AnotherError'?: false
Is err 'DoSomething(): missing attribute: Key' type '*fmt.wrapError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*fmt.wrapError'?: true
Ideally I'd like the output to be
Is err 'DoSomething(): missing attribute: Key' type '*main.MissingAttrError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*main.AnotherError'?: false
Is err 'DoSomething(): missing attribute: Key' type '*main.MissingAttrError'?: true
Is err 'DoSomething(): missing attribute: Key' type '*main.AnotherError'?: false
The reasons for having the slice of errors is that I'd like to be able to define a list of expected errors per test case entry. Say I know that providing the function with certain input will take it down a path that should return an error that wraps a specific error.
How can I convert the *fmt.wrapError
type from the []error
slice back into the original type, so I can use it with error.As
?
I know I can coerce it into a specific type with .(AnotherError)
, but to make that work when iterating the slice, I'd have to do that for every possible error the function can return, no?)
CodePudding user response:
You can trick errors.As
using this:
func main() {
err := DoSomething()
m := &MissingAttrError{}
a := &AnotherError{}
expected := []any{&m, &a}
for i := range expected {
fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expected[i], errors.As(err, expected[i]))
}
}
The printed type is not what you expect, but errors.As
works as it should.
The reason your example did not work is because what you are passing to errors.As
is a *error
. Thus, the wrapped error value (which is err
) is directly assigned to the target value. In my example, the value passed to errors.As
is a **AnotherError
, and err
cannot be assigned to *AnotherError
.