Binding errors no longer return a structured error response
Issue Description
In earlier versions of echo, binding errors were serialized by DefaultHTTPErrorHandler into JSON. This has apparently changed with the commit fbfe216 fix(DefaultHTTPErrorHandler): return error message when message is an error because the error gets converted into a plain string message and included the "message" field here: https://github.com/labstack/echo/blob/de44c53a5b16f7dca451f337f7221a1448c92007/echo.go#L452
The following example shows that a binding error no longer returns a structured error response.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/labstack/echo/v4"
)
func GET(handler echo.HandlerFunc, target string) *httptest.ResponseRecorder {
e := echo.New()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, target, nil)
c := e.NewContext(req, rec)
err := handler(c)
if err != nil {
e.DefaultHTTPErrorHandler(err, c)
}
return rec
}
func main() {
handler := func(c echo.Context) error {
var docNum int
err := echo.PathParamsBinder(c).
MustInt("docNum", &docNum).
BindError()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
return c.JSON(http.StatusOK, map[string]interface{}{"received": docNum})
}
resp := GET(handler, "/doc/1234")
fmt.Println(resp.Body.String())
}
With v4.10.2:
➜ go run main.go
{"field":"docNum","message":"required field value is empty"}
With v4.13.3:
➜ go run main.go
{"message":"code=400, message=required field value is empty, field=docNum"}
If this change was on purpose and there is no aim to serialize binding errors, in this case the json tag in BindingError seems to be superfluous. https://github.com/labstack/echo/blob/de44c53a5b16f7dca451f337f7221a1448c92007/binder.go#L71
note here. This was not intentional and you can always create own error handler function.
e := echo.New()
e.HTTPErrorHandler = func(err error, c echo.Context) {
//
}
or if you are going to wrap binding error with echo.NewHTTPError
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
you could wrap then into something that implements json.Marshaler interface.
type jsonError struct {
err error
}
func (e jsonError) MarshalJSON() ([]byte, error) {
return json.Marshal(e.err)
}
and return it from handle as
err := echo.PathParamsBinder(c).
MustInt("docNum", &docNum).
BindError()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, &jsonError{err: err})
}
these are things that can be done without waiting...
we could make echo.BindingError to implement json.Marshaler interface
func (be *BindingError) MarshalJSON() ([]byte, error) {
type tmp struct {
BindingError
}
return json.Marshal(tmp{BindingError{be.Field, be.HTTPError, be.Values}})
}