skip to Main Content

I’ve worked with Go for a while now and has yet to really nail how I work with errors.

Even the standard library has a number of different ways to deal with errors (some not even making it possible to inspect the errors without having to resort to string matching).

I’ve recently read the blog post Inspecting Errors by Dave Cheney. This sounds like its a step in the right direction, but I’m still having a hard time really putting it to real use.

Say I have made a package a that does requests to a 3rd party REST API (such as Facebook Graph for instance). I’d like this package to expose functions matching those of the API – lets say GetUser.

Calling GetUser can have a number of outcomes:

  1. Success
  2. Failed due to request failure like 3rd party API being down
  3. 3rd party API returned an error (like user not found)
  4. Deserialization of response or similar failed

Asserting the error for behaviour works very well in the second case. However it falls short in distinguishing between the third and fourth case.

My current use case is implementing my own REST API that use this first package. In this case I’d like be able to return 200 OK, 503 Service Unavailable, 400 Bad Request, 500 Internal Server Error responses respectively to the possible outcomes.

What would be a good general approach to solving this problem without having to include returning http response codes or similar from package a?

2

Answers


  1. Chosen as BEST ANSWER

    After working with this for a while without coming any closer to a solution that feels just right I'm coming to terms with there not being a good single solution for this.

    I guess this might also be what Dave Cheney means by:

    However, I have concluded that there is no single way to handle errors.

    Since I still want to avoid type asserting the error due to the dependency challenges that brings I've ended up introducing an Internal() bool behaviour similar to the Temporary() bool pattern mentioned in the linked articles.

    This at least allow me to assert the behavior without being forced to import the package that originally created the error.

    It's not the best fit, but it will have to do for now.

    Ainar-G's answer is worth taking a look at as well.


  2. What I’d do is define a few “wrapper errors” to signify where error has actually happened. E.g.

    type SerializationError struct { Error error }
    func (err SerializationError) Error() string { return err.Error.Error() }
    
    type HTTPError struct { Error error }
    // ...
    

    And then in the code of your API Client:

    b, err := json.Marshal(v)
    if err != nil {
         return nil, SerializationError{err}
    }
    
    // ...
    
    resp, err := client.Post(url, ct, body)
    if err != nil {
        return nil, HTTPError{err}
    }
    

    Then, you can do this:

    err := client.GetUser(id)
    switch err.(type) {
    case SerializationError:
        // respond with 400
    case HTTPError:
        // respond with 500
    // etc.
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search