Go语言 如何创建一个自定义错误,它是另一个自定义错误的子类型,比如继承?

rjee0c15  于 2023-01-03  发布在  Go
关注(0)|答案(2)|浏览(178)

对于给定的自定义错误类型:

type CustomError struct {
    // Err optionally wraps the original error.
    Err error `json:"-"`

    // Human readable message.
    Message string `json:"message" validate:"required,gte=3"`

    // StatusCode is a valid HTTP status code, e.g.: 404.
    StatusCode int `json:"-"`
}

它实现了Error() stringUnwrap() error接口,也有一个工厂:

func NewCustomError(m string, s int, e error) *CustomError {}

如何基于CustomType创建另一个“类型”-我们将其命名为FailedTo,以解决 “创建X失败” 等错误,默认情况下将具有:

  • 前缀为Failed toMessage
  • 500的状态代码

最重要的是,另一个,比如FailedToCreateSomething,在某种程度上...。

func createSomething() error {
    return FailedToCreateSomething(errors.New("File is busy"))
}

errCreateSomething := createSomething()

... errCreateSomethingFailedToCreateSomething的类型,也是FailedToCustomError的类型?

fxnxkyjh

fxnxkyjh1#

代码:

import (
    "fmt"
    logger "log"
    "os"
    "testing"
)

var log = logger.Default()

func TestErrorWrapping(t *testing.T) {
    _, err := os.ReadFile("wrong file path")
    if err != nil {
        // custom error with wrapped a source error
        err = fmt.Errorf("can't read file, %w", err)
        // print it
        log.Printf("something went wrong, %s", err.Error())
    }
}

输出:
出现错误,无法读取文件,打开错误文件路径:系统找不到指定的文件。

xxls0lw8

xxls0lw82#

让我们从这个例子中提炼出它的精髓。

package customerror

import (
    "errors"
)

type CustomError struct {
    Aux string
    Err error
}

func (cE *CustomError) Error() string { /*...*/ }
func (err *CustomError) Unwrap() error { return err.Err }

func NewFailedToError(aux string, err error) error {
    return &CustomError{ Aux: aux, Err: err }
}

var FailedToWriteToFile = NewFailedToError("write to file", nil)

我们现在可以进入要点中的问题:

// Some function just for demonstration.
func WriteToFile() error {
    // Something caused some error..
    errSome := errors.New("Failed to open file")

    // How can I set the `err` value of `FailedToWriteToFile` to `errSome`
    // without setting that for all `FailedToWriteToFile` instances (pointer)?
    // while still making it of `FailedToWriteToFile` type (errors.Is(errSome, FailedToWriteToFil))?
    return FailedToWriteToFile
}

让我们将这个问题重新组织为如何创建一个包含新消息的errSome,其中errors.Is(errSome, FailedToWriteToFil)包含。
我们可以查看errors.Is的文档:
报告err链中是否有任何错误与目标匹配。
该链由err本身以及通过重复调用Unwrap获得的错误序列组成。
如果一个错误等于一个目标,或者如果它实现了一个方法Is(error)bool,使得Is(target)返回true,则认为它匹配该目标。
错误类型可以提供Is方法,以便将其视为与现有错误等效。例如,如果MyError定义
func(m MyError)Is(目标错误)bool {返回目标== fs.ErrExist }
则Is(MyError{},fs.ErrExist)返回true。有关标准库中的示例,请参见syscall.Errno.Is。
这给了我们两条路径,其中一条路径是把FailedToWriteToFile放在Unwrap链上,如果我们使用Err字段指向FailedToWriteToFile,CustomError有足够的字段来完成这个任务,例如:

&CustomError{Aux: "Failed to open file", Err: FailedToWriteToFile}

Unwrap()== to FailedToWriteToFile。如果您试图捕获来自另一个源的错误值,则FWIW Aux可能是error类型的字段。只要Unwrap()链最终指向FailedToWriteToFile
另一种方法是在CustomError上定义一个Is predicate 。有很多可能的 predicate 可供选择。一个简单的方法是将所有 *CustomError视为等效。这将是:

func (e *CustomError) Is(target error) bool {
  _, ok := target.(*CustomError)
  return ok
}

相关问题