Hacker News new | past | comments | ask | show | jobs | submit login

What is not covered here, and what I'm still searching for a good pattern for, is being able to return different errors depending on the type of failure.

Suppose you have a function that fetches a model from your database. It can return an error if the given user doesn't have permission to fetch this model, or it can return an error if your db connection barfs for some reason. The calling function needs to be able to differentiate between the two errors. Most of what I've read on the subject makes it seem like people prefer to only ever check if err != nil.

The two options I've seen in the wild are:

1. Create a constant for a given error, like:

  var ErrFetchForbidden = errors.New("FETCH_FORBIDDEN")
Then the calling function can do:

  if err == ErrFetchForbidden {
    return 403
  } else if err == ErrFetchNotFound {
    return 404
  } else {
    return 500
  }
2. Create a custom type for your error like so:

  type ErrFetchForbidden string
this has the benefit that the errorer can put more specific info into the error besides the Error() string.

  var err ErrFetchForbidden = "error retrieving the user object"
  return err
and then the caller can switch on type

  switch v := err.(type) {
    case ErrFetchForbidden:
      return 403
    case ErrFetchNotFound:
      return 404
    default:
      return 500
  }
We've gone with option 2 for now, (wrapping them with the pkg/errors package) because it seems simpler. Anyone else have good patterns for handling this?



There's another one I often use:

Create a custom error type, for example DB Error:

  type DBError struct {
     Temporary bool
     NetworkBased bool
     Cause error
  }

Now you can provide functions like IsTemporary(err).

Otherwise, you can use 2# with a twist, instead of matching on a type, you can do:

  switch {
     case isErrFetchForbidden(err):
     case isErrFetchNotFound(err):
  }
or even:

  IsBadRequest(err)
  IsInternal(err)
  IsTimeout(err)


So you then define your function to return the type DBError instead of a generic err type. That makes sense to me but for some reason some of the stuff I've suggests that just returning err is more go-like.


No, you don't, DBError should implement the error interface.

IsTemporary also takes an error and does something like:

  if err, ok := errors.Cause(err).(*DBError) {
    return err.temporary
  } else {
    return false
  }


oooh!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: