Skip to content

Commit

Permalink
add new bind
Browse files Browse the repository at this point in the history
  • Loading branch information
trim21 committed Aug 23, 2022
1 parent 2cb58a2 commit 3251afc
Show file tree
Hide file tree
Showing 21 changed files with 1,222 additions and 8 deletions.
18 changes: 18 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ type App struct {
newCtxFunc func(app *App) CustomCtx
// TLS handler
tlsHandler *tlsHandler
// bind decoder cache
bindDecoderCache sync.Map
}

// Config is a struct holding the server settings.
Expand Down Expand Up @@ -329,6 +331,17 @@ type Config struct {
// Default: xml.Marshal
XMLEncoder utils.XMLMarshal `json:"-"`

// XMLDecoder set by an external client of Fiber it will use the provided implementation of a
// XMLUnmarshal
//
// Allowing for flexibility in using another XML library for encoding
// Default: utils.XMLUnmarshal
XMLDecoder utils.XMLUnmarshal `json:"-"`

// App validate. if nil, and context.EnableValidate will always return a error.
// Default: nil
Validator Validator

// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only)
// WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose.
//
Expand Down Expand Up @@ -513,9 +526,14 @@ func New(config ...Config) *App {
if app.config.JSONDecoder == nil {
app.config.JSONDecoder = json.Unmarshal
}

if app.config.XMLEncoder == nil {
app.config.XMLEncoder = xml.Marshal
}
if app.config.XMLDecoder == nil {
app.config.XMLDecoder = xml.Unmarshal
}

if app.config.Network == "" {
app.config.Network = NetworkTCP4
}
Expand Down
59 changes: 59 additions & 0 deletions bind.go
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
}
172 changes: 172 additions & 0 deletions bind_readme.md
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
})
}
```
Loading

0 comments on commit 3251afc

Please sign in to comment.