Return custom errors

The app i’m working has a concept of plugable repositories and they respect the same interface. The interface methods always returns a error that implements the RepositoryError, but, they get returned as a simple error.

type Repositorier interface {
	Find(context.Context, *Pagination) ([]Resource, *Pagination, error)
	FindByID(context.Context, string) (*Resource, error)
	FindByURI(context.Context, string) (*Resource, error)
}

type RepositoryError interface {
	error
	AlreadyExists() bool
	NotFound() bool
}

The problem is, in lots of places i need to do type assertion to check if the returned error is the repository error. What you guys think of returning the custom error , RepositoryError, instead of the pure error?

Like this:

type Repositorier interface {
	Find(context.Context, *Pagination) ([]Resource, *Pagination, RepositoryError)
	FindByID(context.Context, string) (*Resource, RepositoryError)
	FindByURI(context.Context, string) (*Resource, RepositoryError)
}

It should be fine to return a concrete type as long as you handle the value in one place. This has been showcased in Error handling and Go.

It’s usually a mistake to pass back the concrete type of an error rather than error, for reasons discussed in the Go FAQ, but it’s the right thing to do here because ServeHTTP is the only place that sees the value and uses its contents.

But you need to be careful because your RepositoryError is an interface so you might encounter the nil error case mentioned in Go FAQ.

If you need to handle this error in many different places, a type assertion should be the right thing to do. If this error is handled across different packages and you wish to reduce coupling, you could use an interface on the client package to assert for behavior. For example:

// Is now a concrete type.
type RepositoryError struct { 
	// Info about the kind and the cause of the error.
}

func (e RepositoryError) NotFound() bool { ... }

Then on the client package that needs to inspect the error:

type notFounder interface {
    NotFound() bool
}

func IsNotFounder(err error) bool {
        nf, ok := err.(notFounder)
        return ok && nf.NotFound()
}

You could also check if some of the error handling techniques used in Upspin are suitable for your project.

4 Likes