In a hurry 🏃? Jump to the conclusion
After some discussion on Reddit, I decided to write this blog post on how to have Go errors containing codes you can use in calling layers.
This is the case for example if you want to have the HTTP status code contained in an error created in your database
package.
Let’s start with some base code showing how this works.
You have two files:
./database/db.go
with contentpackage database import ( "errors" "net/http" ) type HTTPError struct { message string statusCode int } func (e HTTPError) Error() string { return e.message } func (e HTTPError) StatusCode() int { return e.statusCode } func GetUserNameFromID(ID string) (name string, err error) { return "", &HTTPError{ message: "user not found", statusCode: http.StatusNotFound, } }
./server/handler.go
with an HTTP handler using the database functionGetUserNameFromID
:package server import ( "errors" "net/http" "yourpackage/database" ) // ... func handlerUserIDToName(r *http.Request, w http.ResponseWriter) { id := r.URL.Query().Get("id") name, err := database.GetUserNameFromID(id) if err != nil { httpErr := new(HTTPError) switch { case errors.As(err, httpErr): http.Error(w, httpErr.Error(), httpErr.StatusCode()) default: // not an HTTPError http.Error(w, err.Error(), http.StatusInternalServerError) } } w.Write([]byte(name)) // no error }
The key steps are to:
Define our own error implementation
HTTPError
Use
errors.As(err, httpErr)
:httpErr := new(HTTPError) if errors.As(err, httpErr) { // access httpErr extra methods on top of .Error() } else { // you can only access .Error() }
Conclusion
- You can define your own
error
interface implementation with as many fields and methods you want - You can convert that error back to its implementation in callers using
errors.As(err, targetErrImplementation)
Comments