diff --git a/context.go b/context.go index bcbef034..c61a8085 100644 --- a/context.go +++ b/context.go @@ -99,8 +99,12 @@ type ( // does it based on Content-Type header. Bind(i interface{}) error + // Validate validates provided `i`. It is usually called after `Context#Bind()`. + // Validator must be registered using `Echo#Validator`. + Validate(i interface{}) error + // Render renders a template with data and sends a text/html response with status - // code. Templates can be registered using `Echo.Renderer`. + // code. Renderer must be registered using `Echo.Renderer`. Render(code int, name string, data interface{}) error // HTML sends an HTTP response with status code. @@ -350,6 +354,13 @@ func (c *context) Bind(i interface{}) error { return c.echo.Binder.Bind(i, c) } +func (c *context) Validate(i interface{}) error { + if c.echo.Validator == nil { + return ErrValidatorNotRegistered + } + return c.echo.Validator.Validate(i) +} + func (c *context) Render(code int, name string, data interface{}) (err error) { if c.echo.Renderer == nil { return ErrRendererNotRegistered diff --git a/echo.go b/echo.go index 2db6b12d..9044cb6a 100644 --- a/echo.go +++ b/echo.go @@ -63,6 +63,7 @@ type ( Debug bool HTTPErrorHandler HTTPErrorHandler Binder Binder + Validator Validator Renderer Renderer AutoTLSManager autocert.Manager ShutdownTimeout time.Duration @@ -102,7 +103,7 @@ type ( // Validator is the interface that wraps the Validate function. Validator interface { - Validate() error + Validate(i interface{}) error } // Renderer is the interface that wraps the Render function. @@ -217,6 +218,7 @@ var ( ErrUnauthorized = NewHTTPError(http.StatusUnauthorized) ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) + ErrValidatorNotRegistered = errors.New("validator not registered") ErrRendererNotRegistered = errors.New("renderer not registered") ErrInvalidRedirectCode = errors.New("invalid redirect status code") ErrCookieNotFound = errors.New("cookie not found") diff --git a/website/content/guide/customization.md b/website/content/guide/customization.md index f769ee48..77699b95 100644 --- a/website/content/guide/customization.md +++ b/website/content/guide/customization.md @@ -21,7 +21,7 @@ You can set a custom HTTP error handler using `Echo#HTTPErrorHandler`. ## Debugging -`Echo#Debug` enables/disables debug mode. +`Echo#Debug` enable / disable debug mode. ## Logging diff --git a/website/content/guide/request.md b/website/content/guide/request.md index 1a1effa8..9bfac941 100644 --- a/website/content/guide/request.md +++ b/website/content/guide/request.md @@ -7,88 +7,188 @@ description = "Handling HTTP request in Echo" weight = 6 +++ -## Bind Request Body +## Bind Data -To bind request body into a provided Go type use `Context#Bind(interface{})`. +To bind request body into a Go type use `Context#Bind(i interface{})`. The default binder supports decoding application/json, application/xml and -application/x-www-form-urlencoded payload based on Context-Type header. +application/x-www-form-urlencoded data based on the Context-Type header. -*Example* +Example below binds the request payload into `User` struct based on tags: -TODO +```go +// User +User struct { + Name string `json:"name" form:"name" query:"name"` + Email string `json:"email" form:"email" query:"email"` +} +``` -> Custom binder can be registered via `Echo#SetBinder(Binder)` +```go +// Handler +func(c echo.Context) (err error) { + u := new(User) + if err = c.Bind(u); err != nil { + return + } + return c.JSON(http.StatusOK, u) +} +``` -## Query Parameter +### JSON Data -Query parameter can be retrieved by name using `Context#QueryParam(name string)`. +```sh +curl \ + -X POST \ + http://localhost:1323/users \ + -H 'Content-Type: application/json' \ + -d '{"name":"Joe","email":"joe@labstack"}' +``` + +### Form Data + +```sh +curl \ + -X POST \ + http://localhost:1323/users \ + -d 'name=Joe' \ + -d 'email=joe@labstack.com' +``` + +### Query Parameters + +```sh +curl \ + -X GET \ + http://localhost:1323/users\?name\=Joe\&email\=joe@labstack.com +``` + +## Custom Binder + +Custom binder can be registered using `Echo#Binder`. *Example* ```go -e.GET("/users", func(c echo.Context) error { +type CustomBinder struct {} + +func (cb *CustomBinder) Bind(i interface{}, c echo.Context) (err error) { + // You may use default binder + db := new(echo.DefaultBinder) + if err = db.Bind(i, c); err != echo.ErrUnsupportedMediaType { + return + } + + // Define your custom implementation + + return +} +``` + +## Retrieve Data + +### Form Data + +Form data can be retrieved by name using `Context#FormValue(name string)`. + +*Example* + +```go +// Handler +func(c echo.Context) error { + name := c.FormValue("name") + return c.String(http.StatusOK, name) +} +``` + +```sh +curl \ + -X POST \ + http://localhost:1323 \ + -d 'name=Joe' +``` + +### Query Parameters + +Query parameters can be retrieved by name using `Context#QueryParam(name string)`. + +*Example* + +```go +// Handler +func(c echo.Context) error { name := c.QueryParam("name") return c.String(http.StatusOK, name) }) ``` ```sh -$ curl -G -d "name=joe" http://localhost:1323/users +curl \ + -X GET \ + http://localhost:1323\?name\=Joe ``` -## Form Parameter +### Path Parameters -Form parameter can be retrieved by name using `Context#FormValue(name string)`. - -*Example* - -```go -e.POST("/users", func(c echo.Context) error { - name := c.FormValue("name") - return c.String(http.StatusOK, name) -}) -``` - -```sh -$ curl -d "name=joe" http://localhost:1323/users -``` - -## Path Parameter - -Registered path parameter can be retrieved by name `Context#Param(name string) string`. +Registered path parameters can be retrieved by name using `Context#Param(name string) string`. *Example* ```go e.GET("/users/:name", func(c echo.Context) error { - // By name name := c.Param("name") - return c.String(http.StatusOK, name) }) ``` ```sh -$ curl http://localhost:1323/users/joe +$ curl http://localhost:1323/users/Joe ``` +## Validate Data -## Handler Path +Echo doesn't have built-in data validation capabilities, however you can set a +custom validator using `Echo#Validator` and leverage third-party [libraries](https://github.com/avelino/awesome-go#validation). -`Context#Path()` returns the registered path for the handler, it can be used in the -middleware for logging purpose. - -*Example* +Example below uses https://github.com/go-playground/validator framework for validation: ```go -e.Use(func(handler echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - println(c.Path()) - return handler(c) +type ( + User struct { + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required,email"` } -}) -e.GET("/users/:name", func(c echo.Context) error) { - return c.String(http.StatusOK, name) -}) + CustomValidator struct { + validator *validator.Validate + } +) + +func (cv *CustomValidator) Validate(i interface{}) error { + return cv.validator.Struct(i) +} + +func main() { + e := echo.New() + e.Validator = &CustomValidator{validator: validator.New()} + e.POST("/users", func(c echo.Context) (err error) { + u := new(User) + if err = c.Bind(u); err != nil { + return + } + if err = c.Validate(u); err != nil { + return + } + return c.JSON(http.StatusOK, u) + }) + e.Logger.Fatal(e.Start(":1323")) +} +``` + +```sh +curl \ + -X POST \ + http://localhost:1323/users \ + -H 'Content-Type: application/json' \ + -d '{"name":"Joe","email":"joe@invalid-domain"}' +{"message":"Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag"} ```