dscrn1974 2018-06-17 16:45
浏览 124
已采纳

如何确定我应该从golang API返回哪种错误消息?

I have a GoLang API with an SPA to consume it. What I do to errors in my API is return them until the handler where I test if an error from previous functions exist. If there is an error, I put it inside the response body, set status code to either 400 or 500 then return the response

in the handler function, to be able to create a clear message to the client side, I need to know what kind of error was returned, how do I do it?

I know about error types but I read about Dave Cheney's recommendation to just return an error along with a message (or wrap them in other words).

But if there are so many kinds of errors which might occur in the API call, then does it mean before returning the response, I need to check them all just to know what message I should return?

  • 写回答

1条回答 默认 最新

  • douying2243 2018-06-17 19:51
    关注

    The first thing to say about errors is that just because there's an error interface

    type error interface {
        Error() string
    }
    

    Does not mean that the error returned from any given method can only have that method / information attached to it.

    One common method is to define your own error interface:

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

    When writing methods and functions it's really important to return an error and not myError, else you couple that method to your error implementation and cause dependency nightmares for yourself later.

    Now that we've decided we can return extra information from error, using our own error interfaces you've got 3 main choices.

    1. Sentinel errors
    2. Error Failure types
    3. Errors with Behaviour

    Sentinel errors

    Sentinel errors are error values that are defined as package level variables, are exported and allow comparison to check for errors.

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

    A consumer of this example could use the connect function:

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

    You can do a comparison to check if the error returned is equal to the sentinel error of the package. The drawback is that any error created with errors.New("connection failed") will be equal, and not just the error from myPackage.

    Error failure types

    Slightly better than sentinel errors are error failure types. We've already seen that you can define your own error interface, and if we say ours is now:

    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}
    

    In the consumer code you can get an error, check if it implements MyError and then do things with it.

    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
        }
    }
    

    Now you have an idea of what caused the error which is nice. But do you really care what the cause was? Or do you only really care what you want to do to handle that error.

    This is where errors with behaviour are nice.

    Errors with behaviour

    This is similar to defining your own error type, but instead you define methods that report information about that error. Given the example above, do you really care that the connection failed, or do you really only care if you can retry or if you need to error up the call stack again?

    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
    }
    

    Now in the consumer (note you don't need to use the myPackage.tempErr), you can test using type assertions if the error is temporary and handle the retry case:

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

    To answer the question, it's very hard to say without the specifics of the service that you are trying to implement. But as broad advice, I would try and use the last of the 3 examples as much as possible.

    If the consumer of your service sends you some input that's not valid:

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

    If you want to return a specific message to a consumer, you could make that a method of your error type. Warning: this would give your packages knowledge that they are being used in a web service, etc.

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

    TL;DR you would need to handle all the cases, but you can create error types that make the code much easier to work with!

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 PADS Logic 原理图
  • ¥15 PADS Logic 图标
  • ¥15 电脑和power bi环境都是英文如何将日期层次结构转换成英文
  • ¥20 气象站点数据求取中~
  • ¥15 如何获取APP内弹出的网址链接
  • ¥15 wifi 图标不见了 不知道怎么办 上不了网 变成小地球了
  • ¥50 STM32单片机传感器读取错误
  • ¥15 (关键词-阻抗匹配,HFSS,RFID标签天线)
  • ¥15 机器人轨迹规划相关问题
  • ¥15 word样式右侧翻页键消失