Home > Net >  slice of errors to concrete types in Golang
slice of errors to concrete types in Golang

Time:01-27

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.

  • Related