Reputation: 1084
I was going through https://blog.golang.org/error-handling-and-go and at the end it gave a good example on how to handle returning errors in a cleaner way and just made something simple:
// Wrapper for handler functions.
type rootHandler func(http.ResponseWriter, *http.Request) error
// Implement the http.Handler interface.
func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := fn(w, r) // Call handler function.
if err == nil {
return
}
...
}
// POST /api/test/
testRouter.
Handle("/", rootHandler(create)).
Methods("POST")
// GET /api/test/{id}/
testRouter.
HandleFunc("/{id:[0-9]+}/", rootHandler(getByID)).
Methods("GET")
func create(w http.ResponseWriter, r *http.Request) error {
// CustomError implementes error
return &CustomError{
Kind: EPARSE,
Status: http.StatusBadRequest,
Message: "some message",
Op: "create",
Err: err,
}
}
This worked very well, but I would prefer not to wrap every controller method (create
in this case) in rootHandler
, and figured the best way is to figure out some sort of post-middleware. I've failed at trying to create a post-middlewhere which the router uses instead of each controller method, and wondering how you may go about implementing this. The closest answer on SO I could find was emmbee's answer on How can I combine Go middleware pattern with error returning request handlers? except for fn in AuthMiddleware would be a controller method.
So ideally, I would have the below handler which handles the CustomError
if it exists
// POST /api/test
testRouter.
Handle("/", create).
Methods("POST")
For context, I'm using gorilla mux and negroni.
Any ideas are appreciated! Thank you very much.
Upvotes: 1
Views: 4170
Reputation: 121129
Your ideal solution will not work. The mux API supports http.Handler
and func(http.ResponseWriter, *http.Request)
arguments. You have a func(http.ResponseWriter, *http.Request) error
. A func(http.ResponseWriter, *http.Request) error
cannot be passed as one of the argument types supported by the mux API.
The only solution is to adapt each controller function to a type supported by the mux API using the rootHandler
wrapper. The wrapper does not add overhead compared to the standard http.HandlerFunc
wrapper.
Use a helper function to reduce the amount of code required for the wrappers:
func handle(r *mux.Router, p string, fn func(http.ResponseWriter, *http.Request) error) *mux.Route {
return r.Handle(path, rootHandler(fn))
}
...
handle(testRouter, "/", create).Methods("POST")
handle(testRouter, "/{id:[0-9]+}/", getByID).Methods("GET")
Use a type switch in the helper function to handle different controller types:
func handle(r *mux.Router, p string, h interface{}) *mux.Route {
switch h := h.(type) {
case func(http.ResponseWriter, *http.Request) error:
return r.Handle(p, rootHandler(h))
case func(http.ResponseWriter, *http.Request):
return r.HandleFunc(p, h)
case http.Handler:
return r.Handle(p, h)
default:
panic(fmt.Sprintf("handler type %T not supported", h))
}
}
Upvotes: 4