Skip to content

Commit d899094

Browse files
aldaslammel
andauthored
Docs for #1736 fluent binder (#172)
* Improve request binding documentation. See Echo pull request #1736 (labstack/echo#1736) * Split binding to dedicated guide, some wording improvements * Bind example more alike with other router examples. Mention `UnixTimeNano()` as supported fluent binder function Co-authored-by: Roland Lammel <[email protected]>
1 parent 74b075c commit d899094

File tree

2 files changed

+244
-170
lines changed

2 files changed

+244
-170
lines changed

website/content/guide/binding.md

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
+++
2+
title = "Binding Request Data"
3+
description = "Binding request data"
4+
[menu.main]
5+
name = "Binding"
6+
parent = "guide"
7+
+++
8+
9+
## Bind using struct tags
10+
11+
Echo provides following method to bind data from different sources (path params, query params, request body) to structure using
12+
`Context#Bind(i interface{})` method.
13+
The default binder supports decoding application/json, application/xml and
14+
application/x-www-form-urlencoded data based on the Content-Type header.
15+
16+
In the struct definitions each field can be tagged to restrict binding to specific source.
17+
18+
* `query` - source is request query parameters.
19+
* `param` - source is route path parameter.
20+
* `form` - source is form. Values are taken from query and request body. Uses Go standard library form parsing.
21+
* `json` - source is request body. Uses Go [json](https://golang.org/pkg/encoding/json/) package fo unmarshalling.
22+
* `xml` - source is request body. Uses Go [xml](https://golang.org/pkg/encoding/xml/) package fo unmarshalling.
23+
24+
```go
25+
type User struct {
26+
ID string `path:"id" query:"id" form:"id" json:"id" xml:"id"`
27+
}
28+
```
29+
30+
Request data is binded to the struct in given order:
31+
32+
1. Path parameters
33+
2. Query parameters (only for GET/DELETE methods)
34+
3. Request body
35+
36+
Notes:
37+
38+
* For `query`, `param`, `form` **only** fields **with** tags are bound.
39+
* For `json` and `xml` can bind to *public* fields without tags but this is by their standard library implementation.
40+
* Each step can overwrite binded fields from the previous step. This means if your json request has query param
41+
`&name=query` and body is `{"name": "body"}` then the result will be `User{Name: "body"}`.
42+
* To avoid security flaws try to avoid passing binded structs directly to other methods if
43+
these structs contain fields that should not be bindable. It is advisable to have separate struct for binding and map it
44+
explicitly to your business struct. Consider what will happen if your binded struct has public
45+
field `IsAdmin bool` and request body would contain `{IsAdmin: true, Name: "hacker"}`.
46+
* When binding forms take note that Echo implementation uses standard library form parsing which parses form data
47+
from BOTH URL and BODY if content type is not MIMEMultipartForm. See documentation for [non-MIMEMultipartForm](https://golang.org/pkg/net/http/#Request.ParseForm)
48+
and [MIMEMultipartForm](https://golang.org/pkg/net/http/#Request.ParseMultipartForm)
49+
* To bind data only from request body use following code
50+
```go
51+
if err := (&DefaultBinder{}).BindBody(c, &payload); err != nil {
52+
return err
53+
}
54+
```
55+
* To bind data only from query parameters use following code
56+
```go
57+
if err := (&DefaultBinder{}).BindQueryParams(c, &payload); err != nil {
58+
return err
59+
}
60+
```
61+
* To bind data only from path parameters use following code
62+
```go
63+
if err := (&DefaultBinder{}).BindPathParams(c, &payload); err != nil {
64+
return err
65+
}
66+
```
67+
68+
### Example
69+
70+
Example below binds the request payload into `User` struct based on tags:
71+
72+
```go
73+
// User
74+
type User struct {
75+
Name string `json:"name" form:"name" query:"name"`
76+
Email string `json:"email" form:"email" query:"email"`
77+
}
78+
```
79+
80+
```go
81+
e.POST("/users", func(c echo.Context) (err error) {
82+
u := new(User)
83+
if err = c.Bind(u); err != nil {
84+
return
85+
}
86+
// To avoid security flaws try to avoid passing binded structs directly to other methods
87+
// if these structs contain fields that should not be bindable.
88+
user := UserDTO{
89+
Name: u.Name,
90+
Email: u.Email,
91+
IsAdmin: false // because you could accidentally expose fields that should not be bind
92+
}
93+
executeSomeBusinessLogic(user)
94+
95+
return c.JSON(http.StatusOK, u)
96+
}
97+
```
98+
99+
### JSON Data
100+
101+
```sh
102+
curl -X POST http://localhost:1323/users \
103+
-H 'Content-Type: application/json' \
104+
-d '{"name":"Joe","email":"joe@labstack"}'
105+
```
106+
107+
### Form Data
108+
109+
```sh
110+
curl -X POST http://localhost:1323/users \
111+
-d 'name=Joe' \
112+
113+
```
114+
115+
### Query Parameters
116+
117+
```sh
118+
curl -X GET http://localhost:1323/users\?name\=Joe\&email\=[email protected]
119+
```
120+
121+
## Fast binding with dedicated helpers
122+
123+
For binding data found in a request a handful of helper functions are provided. This will allow binding of query parameters, path parameters or data found in the body like forms or JSON data.
124+
125+
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.
126+
127+
* `echo.QueryParamsBinder(c)` - binds query parameters (source URL)
128+
* `echo.PathParamsBinder(c)` - binds path parameters (source URL)
129+
* `echo.FormFieldBinder(c)` - binds form fields (source URL + body). See also [Request.ParseForm](https://golang.org/pkg/net/http/#Request.ParseForm).
130+
131+
A binder is usually completed by `BindError()` or `BindErrors()` which returns errors if binding fails.
132+
With `FailFast()` the binder can be configured stop binding on the first error or continue binding for
133+
the binder call chain. Fail fast is enabled by default and should be disabled when using `BindErrors()`.
134+
135+
`BindError()` returns the first bind error from binder and resets all errors in this binder.
136+
`BindErrors()` returns all bind errors from binder and resets errors in binder.
137+
138+
```go
139+
// url = "/api/search?active=true&id=1&id=2&id=3&length=25"
140+
var opts struct {
141+
IDs []int64
142+
Active bool
143+
}
144+
length := int64(50) // default length is 50
145+
146+
// creates query params binder that stops binding at first error
147+
err := echo.QueryParamsBinder(c).
148+
Int64("length", &length).
149+
Int64s("ids", &opts.IDs).
150+
Bool("active", &opts.Active).
151+
BindError() // returns first binding error
152+
```
153+
154+
### Supported types
155+
156+
Types that are supported:
157+
158+
* bool
159+
* float32
160+
* float64
161+
* int
162+
* int8
163+
* int16
164+
* int32
165+
* int64
166+
* uint
167+
* uint8/byte (does not support `bytes()`. Use BindUnmarshaler/CustomFunc to convert value from base64 etc to []byte{})
168+
* uint16
169+
* uint32
170+
* uint64
171+
* string
172+
* time
173+
* duration
174+
* BindUnmarshaler() interface
175+
* UnixTime() - converts unix time (integer) to time.Time
176+
* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
177+
* CustomFunc() - callback function for your custom conversion logic
178+
179+
For every supported type there are following methods:
180+
181+
* `<Type>("param", &destination)` - if parameter value exists then binds it to given destination of that type i.e `Int64(...)`.
182+
* `Must<Type>("param", &destination)` - parameter value is required to exist, binds it to given destination of that type i.e `MustInt64(...)`.
183+
* `<Type>s("param", &destination)` - (for slices) if parameter values exists then binds it to given destination of that type i.e `Int64s(...)`.
184+
* `Must<Type>s("param", &destination)` - (for slices) parameter value is required to exist, binds it to given destination of that type i.e `MustInt64s(...)`.
185+
186+
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}`
187+
188+
## Custom Binder
189+
190+
Custom binder can be registered using `Echo#Binder`.
191+
192+
```go
193+
type CustomBinder struct {}
194+
195+
func (cb *CustomBinder) Bind(i interface{}, c echo.Context) (err error) {
196+
// You may use default binder
197+
db := new(echo.DefaultBinder)
198+
if err = db.Bind(i, c); err != echo.ErrUnsupportedMediaType {
199+
return
200+
}
201+
202+
// Define your custom implementation here
203+
return
204+
}
205+
```

0 commit comments

Comments
 (0)