Skip to content

Commit

Permalink
General rewrite of Binding Guide to improve readability
Browse files Browse the repository at this point in the history
  • Loading branch information
bruc3mackenzi3 authored and aldas committed Aug 9, 2022
1 parent 77b41e8 commit 8f17635
Showing 1 changed file with 58 additions and 62 deletions.
120 changes: 58 additions & 62 deletions website/content/guide/binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,92 @@ description = "Binding request data"
parent = "guide"
+++

## Bind using struct tags
## Bind Using Struct Tags

Echo provides following method to bind data from different sources (path params, query params, request body) to structure using
`Context#Bind(i interface{})` method.
The default binder supports decoding application/json, application/xml and
application/x-www-form-urlencoded data based on the Content-Type header.
Echo provides the following methods to bind data from different sources to Go Structs using the `Context#Bind(i interface{})` method:

In the struct definitions each field can be tagged to restrict binding to specific source.
* URL Path parameter
* URL Query parameter
* Request body
* Header

* `query` - source is request query parameters.
* `param` - source is route path parameter.
* `header` - source is header parameter.
* `form` - source is form. Values are taken from query and request body. Uses Go standard library form parsing.
* `json` - source is request body. Uses Go [json](https://golang.org/pkg/encoding/json/) package for unmarshalling.
* `xml` - source is request body. Uses Go [xml](https://golang.org/pkg/encoding/xml/) package for unmarshalling.
The default binder supports decoding the following data types specified by the `Content-Type` header:

* `application/json`
* `application/xml`
* `application/x-www-form-urlencoded`

In the struct definition, tags are used to specify binding from specific sources:

* `query` - query parameter
* `param` - route path parameter
* `header` - header parameter
* `form` - form data. Values are taken from query and request body. Uses Go standard library form parsing.
* `json` - request body. Uses builtin Go [json](https://golang.org/pkg/encoding/json/) package for unmarshalling.
* `xml` - request body. Uses builtin Go [xml](https://golang.org/pkg/encoding/xml/) package for unmarshalling.

```go
type User struct {
ID string `param:"id" query:"id" header:"id" form:"id" json:"id" xml:"id"`
}
```

Request data is bound to the struct in given order:
When multiple sources are specified, request data is bound in the given order:

1. Path parameters
2. Query parameters (only for GET/DELETE methods)
3. Request body

Notes:

* For `query`, `param`, `header`, `form` **only** fields **with** tags are bound.
* For `json` and `xml` can bind to *public* fields without tags but this is by their standard library implementation.
* Each step can overwrite bound fields from the previous step. This means if your json request has query param
`&name=query` and body is `{"name": "body"}` then the result will be `User{Name: "body"}`.
* To avoid security flaws try to avoid passing bound structs directly to other methods if
these structs contain fields that should not be bindable. It is advisable to have separate struct for binding and map it
explicitly to your business struct. Consider what will happen if your bound struct has public
field `IsAdmin bool` and request body would contain `{IsAdmin: true, Name: "hacker"}`.
* When binding forms take note that Echo implementation uses standard library form parsing which parses form data
from BOTH URL and BODY if content type is not MIMEMultipartForm. See documentation for [non-MIMEMultipartForm](https://golang.org/pkg/net/http/#Request.ParseForm)
and [MIMEMultipartForm](https://golang.org/pkg/net/http/#Request.ParseMultipartForm)
* To bind data only from request body use following code
* For `query`, `param`, `header`, and `form` **only** fields **with** tags are bound.
* For `json` and `xml` binding works with either the struct field name or its tag. This is according to the behaviour of [Go's standard json.Unmarshal method](https://pkg.go.dev/encoding/json#Unmarshal).
* Each step can overwrite bound fields from the previous step. This means if your json request contains the query param `name=query` and body `{"name": "body"}` then the result will be `User{Name: "body"}`.
* For `form`, note that Echo uses Go standard library form parsing. This parses form data from both the request URL and body if content type is not `MIMEMultipartForm`. See documentation for [non-MIMEMultipartForm](https://golang.org/pkg/net/http/#Request.ParseForm)and [MIMEMultipartForm](https://golang.org/pkg/net/http/#Request.ParseMultipartForm)
* To avoid security flaws avoid passing bound structs directly to other methods if these structs contain fields that should not be bindable. It is advisable to have a separate struct for binding and map it explicitly to your business struct. Consider what will happen if your bound struct has an Exported field `IsAdmin bool` and the request body contains `{IsAdmin: true, Name: "hacker"}`.
* It is also possible to bind data directly from a specific source:

Request body:
```go
if err := (&DefaultBinder{}).BindBody(c, &payload); err != nil {
return err
}
err := (&DefaultBinder{}).BindBody(c, &payload)
```
* To bind data only from query parameters use following code

Query parameters:
```go
if err := (&DefaultBinder{}).BindQueryParams(c, &payload); err != nil {
return err
}
err := (&DefaultBinder{}).BindQueryParams(c, &payload)
```
* To bind data only from path parameters use following code

Path parameters:
```go
if err := (&DefaultBinder{}).BindPathParams(c, &payload); err != nil {
return err
}
err := (&DefaultBinder{}).BindPathParams(c, &payload)
```
* To bind data only from header parameters use following code

Header parameters:
```go
if err := (&DefaultBinder{}).BindHeaders(c, &payload); err != nil {
return err
}
err := (&DefaultBinder{}).BindHeaders(c, &payload)
```

### Example

Example below binds the request payload into `User` struct based on tags:
In this example we define a `User` struct type with field tags to bind from `json`, `form`, or `query` request data:

```go
// User
type User struct {
Name string `json:"name" form:"name" query:"name"`
Email string `json:"email" form:"email" query:"email"`
}
```

And a handler at the POST `/users` route binds request data to the struct:

```go
e.POST("/users", func(c echo.Context) (err error) {
u := new(User)
if err = c.Bind(u); err != nil {
return
}
// To avoid security flaws try to avoid passing bound structs directly to other methods
// if these structs contain fields that should not be bindable.
// To avoid security flaws try to avoid passing bound structs directly to other
// methods if these structs contain fields that should not be bindable.
user := UserDTO{
Name: u.Name,
Email: u.Email,
Expand All @@ -103,46 +103,44 @@ e.POST("/users", func(c echo.Context) (err error) {
}
```

### JSON Data
#### JSON Data

```sh
curl -X POST http://localhost:1323/users \
-H 'Content-Type: application/json' \
-d '{"name":"Joe","email":"joe@labstack"}'
```

### Form Data
#### Form Data

```sh
curl -X POST http://localhost:1323/users \
-d 'name=Joe' \
-d '[email protected]'
```

### Query Parameters
#### Query Parameters

```sh
curl -X GET http://localhost:1323/users\?name\=Joe\&email\[email protected]
```

## Fast binding with dedicated helpers
## Fast binding with Dedicated Helpers

For binding data found in a request a handful of helper functions are provided. This will allow binding of query parameters,
path parameters or form data found in the body.
Echo provides a handful of helper functions for binding request data. Binding of query parameters, path parameters, and form data found in the body are supported.

Following functions provide a handful of methods for binding to Go native types from request query or path parameters.
These binders offer a fluent syntax and can be chained to configure, execute binding and handle errors.
The following functions provide a handful of methods for binding to Go data type. These binders offer a fluent syntax and can be chained to configure & execute binding, and handle errors.

* `echo.QueryParamsBinder(c)` - binds query parameters (source URL)
* `echo.PathParamsBinder(c)` - binds path parameters (source URL)
* `echo.FormFieldBinder(c)` - binds form fields (source URL + body). See also [Request.ParseForm](https://golang.org/pkg/net/http/#Request.ParseForm).

A binder is usually completed by `BindError()` or `BindErrors()` which returns errors if binding fails.
With `FailFast()` the binder can be configured stop binding on the first error or continue binding for
A binder is usually completed by `BindError()` or `BindErrors()` which returns errors if binding has failed.
With `FailFast()` the binder can be configured to stop binding on the first error or continue exsecuting
the binder call chain. Fail fast is enabled by default and should be disabled when using `BindErrors()`.

`BindError()` returns the first bind error from binder and resets all errors in this binder.
`BindErrors()` returns all bind errors from binder and resets errors in binder.
`BindError()` returns the first bind error encountered and resets all errors in its binder.
`BindErrors()` returns all bind errors and resets errors in its binder.

```go
// url = "/api/search?active=true&id=1&id=2&id=3&length=25"
Expand All @@ -160,9 +158,7 @@ err := echo.QueryParamsBinder(c).
BindError() // returns first binding error
```

### Supported types

Types that are supported:
### Supported Data Types

* bool
* float32
Expand All @@ -185,18 +181,18 @@ Types that are supported:
* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
* CustomFunc() - callback function for your custom conversion logic

For every supported type there are following methods:
Each supported type has the following methods:

* `<Type>("param", &destination)` - if parameter value exists then binds it to given destination of that type i.e `Int64(...)`.
* `Must<Type>("param", &destination)` - parameter value is required to exist, binds it to given destination of that type i.e `MustInt64(...)`.
* `<Type>s("param", &destination)` - (for slices) if parameter values exists then binds it to given destination of that type i.e `Int64s(...)`.
* `Must<Type>s("param", &destination)` - (for slices) parameter value is required to exist, binds it to given destination of that type i.e `MustInt64s(...)`.

for some slice types `BindWithDelimiter("param", &dest, ",")` supports splitting parameter values before type conversion is done. For example URL `/api/search?id=1,2,3&id=1` can be bind to `[]int64{1,2,3,1}`
For certain slice types `BindWithDelimiter("param", &dest, ",")` supports splitting parameter values before type conversion is done. For example binding an integer slice from the URL `/api/search?id=1,2,3&id=1` will result in `[]int64{1,2,3,1}`.

## Custom Binder

Custom binder can be registered using `Echo#Binder`.
A custom binder can be registered using `Echo#Binder`.

```go
type CustomBinder struct {}
Expand Down

0 comments on commit 8f17635

Please sign in to comment.