My package in it's entirety is:
package errors
const ErrUnknown = 0
type RESTError struct {
HTTPStatus int `json:"-"`
Code int `json:"code,omitempty"`
Message string `json:"error"`
Err error `json:"-"`
}
func (e *RESTError) Error() string {
return e.Message
}
func New(status int, code int, message string) *RESTError {
return &RESTError{HTTPStatus: status, Code: code, Message: message}
}
Doc comments removed just to show the code and nothing else.When those errors finally get back to my web handlers, a standard renderError wraps all errors:
func renderError(w http.ResponseWriter, err error) int {
if e, ok := err.(*errors.RESTError); ok {
return render.JSON(w, e.HTTPStatus, e)
}
e = errors.New(http.StatusInternalServerError, errors.ErrUnknown, err.Error())
return render.JSON(w, e.HTTPStatus, e)
}
I handle all errors as soon as possible and then immediately assign the HTTP status code that should be returned, the error code to enable an external system to look up the error (without string parsing), a sanitised error message (to allow devs to get an idea without looking up the code) and I do put the original error in the Err so that my log files can contain the raw underlying error.It effectively acts as a map between internal errors, and external communication of errors, and allows the place where the error occurred to say "this HTTP status". And because the Err is never stringified I am comforted that internal sensitive data (this is a billing system and unsanitised data should never be leaked even via error messages) does not get leaked.
That's my goal, to handle the error in the way that allows it to best communicate to:
1. HTTP clients via the status
2. Developers via an error code and sanitised message (which they can use to communicate to users)
3. SREs via detail in the secure logs
I found the native errors nice, but not the best for dealing with my 3 audiences.