英文:
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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论