Here’s my ErrorHandler which is a middleware that wraps my other handlers –
func ErrorHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
if err, ok := r.Context().Value(auth.ErrorContextKey).(error); ok && err != nil {
switch e := err.(type) {
case *app.AppError:
w.WriteHeader(e.Status)
render.JSON(w, r, e)
return
default:
internalErr := app.NewInternalError("internal-error", "Something went wrong", nil)
w.WriteHeader(internalErr.Status)
render.JSON(w, r, internalErr)
return
}
}
})
}
In my other handlers, I have been trying to add the error to the context, but I get some issues. Here’s an example snippet
func (h *Http) signup(w http.ResponseWriter, r *http.Request) {
var params dto.SignUpDTO
decoder := json.NewDecoder(r.Body)
defer r.Body.Close()
if err := decoder.Decode(¶ms); err != nil {
ctx := context.WithValue(r.Context(), auth.ErrorContextKey, app.NewBadRequestError("auth/invalid-signup-params", "invalid email or password", err))
r = r.WithContext(ctx)
return
}
}
I get an issue on the r = r.WithContext(ctx):
this value of r is never used (SA4006)go-staticcheck
WithContext doesn’t have side effects and its return value is ignored (SA4017)go-staticcheck
>Solution :
Tried to write a comment but it was too long, so here I go:
About OP’s question if there is a way of doing that in a cleaner way:
The cleaner way is simply don’t do it. Since it’s kind of a bad practice for a method to "know" that there is a decorator/middleware above it.
Instead, you can do a shared method for rendering error
func (h *HTTP) renderError(w http.ResponseWriter, r *http.Request, err error) {
switch e := err.(type) {
case *app.AppError:
w.WriteHeader(e.Status)
render.JSON(w, r, e)
return
default:
internalErr := app.NewInternalError("internal-error", "Something went wrong", nil)
w.WriteHeader(internalErr.Status)
render.JSON(w, r, internalErr)
return
}
}
In middleware, you can log/trace/recover from panic/authentication etc, the idea of a middleware is that you add functionality, that the "inner" handler shouldn’t really know or care about. Here your handlers know about the middleware simply because they are doing this line:
ctx := context.WithValue(r.Context(), auth.ErrorContextKey, app.NewBadRequestError("auth/invalid-signup-params", "invalid email or password", err))
r = r.WithContext(ctx)
Instead you can just do:
if err := decoder.Decode(¶ms); err != nil {
h.renderError(w, r, err)
return
}