在Golang中将错误切片转换为具体类型

huangapple go评论74阅读模式
英文:

slice of errors to concrete types in Golang

问题

我正在玩弄Go语言中的错误包装,并且有一个返回包装的自定义错误类型的函数。我想做的是遍历一组预期错误,并测试函数的输出是否包装了这些预期错误。

我发现将自定义错误放入[]error中意味着自定义错误的类型将是*fmt.wrapError,这意味着errors.As()几乎总是返回true。

举个例子,考虑以下代码:

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]))

	}
}

这段代码的输出是:

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

理想情况下,我希望输出是:

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

使用错误切片的原因是,我希望能够为每个测试用例定义一组预期错误。假设我知道使用特定输入将使函数进入应返回包装特定错误的路径。

如何将[]error切片中的*fmt.wrapError类型转换回原始类型,以便我可以在errors.As中使用它?

我知道我可以使用.(AnotherError)将其强制转换为特定类型,但是为了在迭代切片时使其工作,我必须对函数可能返回的每个可能的错误都这样做,对吗?

英文:

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?)

答案1

得分: 2

你可以使用以下方法来欺骗errors.As

func main() {
	err := DoSomething()
	m := &MissingAttrError{}
	a := &AnotherError{}
	expected := []interface{}{&m, &a}

	for i := range expected {
		fmt.Printf("Is err '%v' type '%T'?: %t\n", err, expected[i], errors.As(err, expected[i]))
	}
}

打印出的类型可能不是你期望的,但是errors.As的功能是正常的。

你的示例没有起作用的原因是你传递给errors.As的是一个*error。因此,封装的错误值(即err)直接赋值给目标值。在我的示例中,传递给errors.As的值是一个**AnotherError,而err无法赋值给*AnotherError

英文:

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.

huangapple
  • 本文由 发表于 2023年1月27日 03:24:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75250770.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定