如何确定我应该从golang API返回什么样的错误消息?

时间:2018-06-17 16:45:39

标签: rest go

我有一个带有SPA的GoLang API来使用它。我对API中的错误所做的是将它们返回到我测试的处理程序,如果存在来自先前函数的错误。如果有错误,我把它放在响应体内,将状态代码设置为400或500然后返回响应

在处理函数中,为了能够向客户端创建一个明确的消息,我需要知道返回了什么样的错误,我该怎么做?

我知道错误类型,但我读到Dave Cheney建议只返回一个错误和一条消息(换句话说换行)。

但是如果API调用中可能出现这么多种错误,那么在返回响应之前它是什么意思,我需要检查它们只是为了知道我应该返回什么消息?

1 个答案:

答案 0 :(得分:7)

关于错误的第一件事就是因为那里有一个错误接口

type error interface {
    Error() string
}

并不意味着从任何给定方法返回的error只能 附加了该方法/信息。

一种常见的方法是定义自己的错误界面:

type myError interface {
    error // embeds the standard error interface
    OtherMethod() string // can define own methods here
}

在编写方法和函数时,非常重要返回error而非myError,否则您将该方法与错误实现结合并导致依赖以后为你做恶梦。

现在我们已经决定我们可以从错误中返回额外信息,使用我们自己的错误界面,您有3个主要选择。

  1. Sentinel错误
  2. 错误失败类型
  3. 行为错误
  4. Sentinel错误

    Sentinel错误是定义为包级别变量的错误值,导出并允许进行比较以检查错误。

    package myPackage
    
    var ErrConnectionFailed = errors.New("connection failed")
    
    func Connect() error {
        // trimmed ...
        return ErrConnectionFailed
    }
    

    此示例的使用者可以使用connect函数:

    if err := myPackage.Connect(); err == myPackage.ErrConnectionFailed {
        // handle connection failed state
    }
    

    您可以进行比较以检查返回的错误是否等于包的sentinel错误。缺点是使用errors.New("connection failed")创建的任何错误都是相同的,不仅来自myPackage的错误。

    错误类型

    比sentinel错误稍微好一点就是错误失败类型。 我们已经看到您可以定义自己的错误界面,如果我们现在说我们的错误:

    type MyError interface {
        error
        Failure() string
    } 
    
    type Err struct {
        failure string
    }
    
    func (e *Err) Error() string {
        // implement standard error
    }
    
    func (e *Err) Failure() string {
        return e.failure
    }
    
    const ConnFailed = "connection failed"
    
    err := &Err{failure: ConnFailed}
    

    在使用者代码中,您可能会收到错误,请检查它是否实现MyError,然后使用它执行操作。

    err := myPackage.Connect()
    
    if myErr, ok := err.(myPackage.MyError); ok {
        // here you know err is a MyError
        if myErr.Failure() == myPackage.ConnFailed {
            // handle connection failed, could also use a switch instead of if
        }
    }
    

    现在你已经知道导致错误的原因了。但你真的关心原因是什么吗?或者你真的只关心你想做什么来处理那个错误。

    这是行为错误的好处。

    行为错误

    这类似于定义您自己的错误类型,而是定义报告有关该错误的信息的方法。鉴于上面的示例,您是否真的关心连接失败,或者您是否真的只关心是否可以重试或者是否需要再次错误调用堆栈?

    package myPackage
    
    // this interface could report if the error
    // is temporary and if you could retry it
    type tempErr interface {
        Temporary() bool
    }
    
    func (e *Err) Temporary() bool {
        // return if the error is temporary or not
    }
    

    现在在消费者中(请注意,您不需要使用myPackage.tempErr),如果错误是临时错误并且处理重试案例,则可以使用类型断言进行测试:

    err := myPackage.Connect()
    
    if tmp, ok := err.(interface { Temporary() bool }); ok && tmp.Temporary() {
        // the error is temporary and you can retry the connection
    }
    

    要回答这个问题,如果没有您尝试实施的服务细节,很难说。但作为广泛的建议,我会尝试尽可能多地使用3个例子中的最后一个。

    如果您服务的消费者向您发送了一些无效的输入:

    err := doThing(...)
    
    if inv, ok := err.(interface { Invalid() bool }); ok && inv.Invalid() {
        // input is invalid, return 400 bad request status code etc.
    }
    

    如果要将特定消息返回给使用者,可以将其设为错误类型的方法。警告:这将使您的软件包知道它们正在Web服务中使用等。

    err := doThing(...)
    
    if msg, ok := err.(interface { ResponseMsg() string }); ok {
        // write the message to the http response
        io.WriteString(response, msg.ResponseMsg())
    }
    

    TL; DR 您需要处理所有情况,但您可以创建错误类型,使代码更容易使用!