Skip to content
This repository has been archived by the owner on Dec 1, 2021. It is now read-only.

PROPOSAL: Add support for domain errors #130

Open
jcchavezs opened this issue Aug 30, 2017 · 5 comments
Open

PROPOSAL: Add support for domain errors #130

jcchavezs opened this issue Aug 30, 2017 · 5 comments

Comments

@jcchavezs
Copy link

jcchavezs commented Aug 30, 2017

As of now, when wrapping an error we keep the cause of the error meaning that if we want to handle the error in a upper layer we need to know details about the lower layers. For example:

package users

var ErrUserDoesNotExist = errors.New("user does not exist")

type Repository interface {
    GetUserById(id string) (*users.User, error)
}
package mysql

type mysqlRepository struct {
    ds datastore
}

func (mr *mysqlRepository) GetUserById (id string) (*users.User, error) {
    record, err := ds.exec(fmt.Sprintf("SELECT * FROM `users` WHERE id = %s", id))
    if err != nil {
        return nil, errors.Wrap(err, "could not retrieve user from persistence")
       // another option is to return ErrUserDoesNotExist
    }
}

Meaning that if I want to deal with the error, the handler should do something like:

package handlers

func (h *handler) UserProfile (c web.C, w http.ResponseWriter, r *http.Request) {
    uid := c.URLParams["uid"]
    u, err := h.userRepository.GetUserById(uid)

Option 1:
The handler should now about the implementation details of the database.

    if err.Cause() == mysql.NoRecordsFound {
        w.WriteHeader(http.StatusNotFound)
   }

Option 2:
We do a string comparation

    if err.Error() == "could not retrieve user from persistence"{
        w.WriteHeader(http.StatusNotFound)
   }

IMO this could be solved with another function:

func Map(causeErr error, meaningErr error) error {
	if causeErr == nil {
		return nil
	}
	err := &withDecoration{
		cause: causeErr,
		meaning: meaningErr,
	}
	return &withStack{
		err,
		callers(),
	}
}

Option 3:
We do a string comparation

    if err.Meaning() == ErrUserDoesNotExist {
        w.WriteHeader(http.StatusNotFound)
   }

I could open a PR for this.

@davecheney
Copy link
Member

davecheney commented Aug 30, 2017 via email

@kristofferingemansson
Copy link

Cause does not fully solve the problem.
Let's say you do a json.Unmarshal in a function and get an error.
You want to indicate to the caller that the input data is wrong (so that somewhere higher up in the call chain some code might inform a user with e.g. http 400). This should be distinguishable from other errors like "database is down = 503, resource already exists = 409, and so on.
Currently with pkg/errors you have to chose if you want to return an error with a package/app-defined meaning, or the actual error for e.g. logging (the error returned by json.Unmarshal).

Side note: Also the way Wrap works by prepending a message to the previous error's message, one is unable to easily hide internal information from a user.
An authorization endpoint should print message invalid username or password and not invalid username or password: could not find user: sql: no rows in result set

@lhchavez
Copy link

I agree with the spirit of https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 's "Assert errors for behaviour, not type" section. but in practice, creating an interface for each category of errors is a bit too onerous :( I found a comfortable middle ground that's more or less in the spirit of @jcchavezs' proposal in the sense that you can still get the domain error (I have named it Category inspired by C++), and it does provide Cause: https://github.com/omegaup/go-base/blob/master/errors.go

would that be something that could find its way into this repository?

@davecheney
Copy link
Member

davecheney commented Dec 17, 2018 via email

@knz
Copy link

knz commented Jul 5, 2019

FYI we have built domain errors in https://github.com/cockroachdb/errors

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants