-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
1,222 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package fiber | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/gofiber/fiber/v3/internal/bind" | ||
) | ||
|
||
type Binder interface { | ||
UnmarshalFiberCtx(ctx Ctx) error | ||
} | ||
|
||
// decoder should set a field on reqValue | ||
// it's created with field index | ||
type decoder interface { | ||
Decode(ctx Ctx, reqValue reflect.Value) error | ||
} | ||
|
||
type fieldCtxDecoder struct { | ||
index int | ||
fieldName string | ||
fieldType reflect.Type | ||
} | ||
|
||
func (d *fieldCtxDecoder) Decode(ctx Ctx, reqValue reflect.Value) error { | ||
v := reflect.New(d.fieldType) | ||
unmarshaler := v.Interface().(Binder) | ||
|
||
if err := unmarshaler.UnmarshalFiberCtx(ctx); err != nil { | ||
return err | ||
} | ||
|
||
reqValue.Field(d.index).Set(v.Elem()) | ||
return nil | ||
} | ||
|
||
type fieldTextDecoder struct { | ||
index int | ||
fieldName string | ||
tag string // query,param,header,respHeader ... | ||
reqField string | ||
dec bind.TextDecoder | ||
get func(c Ctx, key string, defaultValue ...string) string | ||
} | ||
|
||
func (d *fieldTextDecoder) Decode(ctx Ctx, reqValue reflect.Value) error { | ||
text := d.get(ctx, d.reqField) | ||
if text == "" { | ||
return nil | ||
} | ||
|
||
err := d.dec.UnmarshalString(text, reqValue.Field(d.index)) | ||
if err != nil { | ||
return fmt.Errorf("unable to decode '%s' as %s: %w", text, d.reqField, err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# Fiber Binders | ||
|
||
Bind is new request/response binding feature for Fiber. | ||
By against old Fiber parsers, it supports custom binder registration, | ||
struct validation with high performance and easy to use. | ||
|
||
It's introduced in Fiber v3 and a replacement of: | ||
|
||
- BodyParser | ||
- ParamsParser | ||
- GetReqHeaders | ||
- GetRespHeaders | ||
- AllParams | ||
- QueryParser | ||
- ReqHeaderParser | ||
|
||
## Guides | ||
|
||
### Binding basic request info | ||
|
||
Fiber supports binding basic request data into the struct: | ||
|
||
all tags you can use are: | ||
|
||
- respHeader | ||
- header | ||
- query | ||
- param | ||
- cookie | ||
|
||
(binding for Request/Response header are case in-sensitive) | ||
|
||
private and anonymous fields will be ignored. | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"time" | ||
|
||
fiber "github.com/gofiber/fiber/v3" | ||
) | ||
|
||
type Req struct { | ||
ID int `param:"id"` | ||
Q int `query:"q"` | ||
Likes []int `query:"likes"` | ||
T time.Time `header:"x-time"` | ||
Token string `header:"x-auth"` | ||
} | ||
|
||
func main() { | ||
app := fiber.New() | ||
|
||
app.Get("/:id", func(c fiber.Ctx) error { | ||
var req Req | ||
if err := c.Bind().Req(&req).Err(); err != nil { | ||
return err | ||
} | ||
return c.JSON(req) | ||
}) | ||
|
||
req := httptest.NewRequest(http.MethodGet, "/1?&s=a,b,c&q=47&likes=1&likes=2", http.NoBody) | ||
req.Header.Set("x-auth", "ttt") | ||
req.Header.Set("x-time", "2022-08-08T08:11:39+08:00") | ||
resp, err := app.Test(req) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
b, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println(resp.StatusCode, string(b)) | ||
// Output: 200 {"ID":1,"S":["a","b","c"],"Q":47,"Likes":[1,2],"T":"2022-08-08T08:11:39+08:00","Token":"ttt"} | ||
} | ||
|
||
``` | ||
|
||
### Defining Custom Binder | ||
|
||
We support 2 types of Custom Binder | ||
|
||
#### a `encoding.TextUnmarshaler` with basic tag config. | ||
|
||
like the `time.Time` field in the previous example, if a field implement `encoding.TextUnmarshaler`, it will be called | ||
to | ||
unmarshal raw string we get from request's query/header/... | ||
|
||
#### a `fiber.Binder` interface. | ||
|
||
You don't need to set a field tag and it's binding tag will be ignored. | ||
|
||
``` | ||
type Binder interface { | ||
UnmarshalFiberCtx(ctx fiber.Ctx) error | ||
} | ||
``` | ||
|
||
If your type implement `fiber.Binder`, bind will pass current request Context to your and you can unmarshal the info | ||
you need. | ||
|
||
### Parse Request Body | ||
|
||
you can call `ctx.BodyJSON(v any) error` or `BodyXML(v any) error` | ||
|
||
These methods will check content-type HTTP header and call configured JSON or XML decoder to unmarshal. | ||
|
||
```golang | ||
package main | ||
|
||
type Body struct { | ||
ID int `json:"..."` | ||
Q int `json:"..."` | ||
Likes []int `json:"..."` | ||
T time.Time `json:"..."` | ||
Token string `json:"..."` | ||
} | ||
|
||
func main() { | ||
app := fiber.New() | ||
|
||
app.Get("/:id", func(c fiber.Ctx) error { | ||
var data Body | ||
if err := c.Bind().JSON(&data).Err(); err != nil { | ||
return err | ||
} | ||
return c.JSON(data) | ||
}) | ||
} | ||
``` | ||
|
||
### Bind With validation | ||
|
||
Normally, `bind` will only try to unmarshal data from request and pass it to request handler. | ||
|
||
you can call `.Validate()` to validate previous binding. | ||
|
||
And you will need to set a validator in app Config, otherwise it will always return an error. | ||
|
||
```go | ||
package main | ||
|
||
type Validator struct{} | ||
|
||
func (validator *Validator) Validate(v any) error { | ||
return nil | ||
} | ||
|
||
func main() { | ||
app := fiber.New(fiber.Config{ | ||
Validator: &Validator{}, | ||
}) | ||
|
||
app.Get("/:id", func(c fiber.Ctx) error { | ||
var req struct{} | ||
var body struct{} | ||
if err := c.Bind().Req(&req).Validate().JSON(&body).Validate().Err(); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
``` |
Oops, something went wrong.