fiber/bind_test.go

520 lines
12 KiB
Go

//nolint:wrapcheck,tagliatelle // We must not wrap errors in tests
package fiber
import (
"bytes"
"fmt"
"mime/multipart"
"net/url"
"regexp"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)
func Test_Binder(t *testing.T) {
t.Parallel()
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
ctx.values = [maxParams]string{"id string"}
ctx.route = &Route{Params: []string{"id"}}
ctx.Request().SetBody([]byte(`{"name": "john doe"}`))
ctx.Request().Header.Set("content-type", "application/json")
var req struct {
ID *string `param:"id"`
}
var body struct {
Name string `json:"name"`
}
err := ctx.Bind().Req(&req).JSON(&body).Err()
require.NoError(t, err)
require.Equal(t, "id string", *req.ID)
require.Equal(t, "john doe", body.Name)
}
func Test_Binder_Nested(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
c.Request().URI().SetQueryString("name=tom&nested.and.age=10&nested.and.test=john")
// TODO: pointer support for structs
var req struct {
Name string `query:"name"`
Nested struct {
And struct {
Age *int `query:"age"`
Test string `query:"test"`
} `query:"and"`
} `query:"nested"`
}
err := c.Bind().Req(&req).Err()
require.NoError(t, err)
require.Equal(t, "tom", req.Name)
require.Equal(t, "john", req.Nested.And.Test)
require.Equal(t, 10, *req.Nested.And.Age)
}
func Test_Binder_Nested_Slice(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
c.Request().URI().SetQueryString("name=tom&data[0][name]=john&data[0][age]=10&data[1][name]=doe&data[1][age]=12")
var req struct {
Name string `query:"name"`
Data []struct {
Name string `query:"name"`
Age int `query:"age"`
} `query:"data"`
}
err := c.Bind().Req(&req).Err()
require.NoError(t, err)
require.Equal(t, 2, len(req.Data))
require.Equal(t, "john", req.Data[0].Name)
require.Equal(t, 10, req.Data[0].Age)
require.Equal(t, "doe", req.Data[1].Name)
require.Equal(t, 12, req.Data[1].Age)
require.Equal(t, "tom", req.Name)
}
/*func Test_Binder_Nested_Deeper_Slice(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
c.Request().URI().SetQueryString("data[0][users][0][name]=john&data[0][users][0][age]=10&data[1][users][0][name]=doe&data[1][users][0][age]=12")
var req struct {
Data []struct {
Users []struct {
Name string `query:"name"`
Age int `query:"age"`
} `query:"subData"`
} `query:"data"`
}
err := c.Bind().Req(&req).Err()
require.NoError(t, err)
require.Equal(t, 2, len(req.Data))
require.Equal(t, "john", req.Data[0].Users[0].Name)
require.Equal(t, 10, req.Data[0].Users[0].Age)
require.Equal(t, "doe", req.Data[1].Users[0].Name)
require.Equal(t, 12, req.Data[1].Users[0].Age)
}*/
// go test -run Test_Bind_BasicType -v
func Test_Bind_BasicType(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
Flag bool `query:"enable"`
I8 int8 `query:"i8"`
I16 int16 `query:"i16"`
I32 int32 `query:"i32"`
I64 int64 `query:"i64"`
I int `query:"i"`
U8 uint8 `query:"u8"`
U16 uint16 `query:"u16"`
U32 uint32 `query:"u32"`
U64 uint64 `query:"u64"`
U uint `query:"u"`
S string `query:"s"`
}
var q Query
const qs = "i8=88&i16=166&i32=322&i64=644&i=101&u8=77&u16=165&u32=321&u64=643&u=99&s=john&enable=true"
c.Request().URI().SetQueryString(qs)
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, Query{
Flag: true,
I8: 88,
I16: 166,
I32: 322,
I64: 644,
I: 101,
U8: 77,
U16: 165,
U32: 321,
U64: 643,
U: 99,
S: "john",
}, q)
type Query2 struct {
Flag []bool `query:"enable"`
I8 []int8 `query:"i8"`
I16 []int16 `query:"i16"`
I32 []int32 `query:"i32"`
I64 []int64 `query:"i64"`
I []int `query:"i"`
U8 []uint8 `query:"u8"`
U16 []uint16 `query:"u16"`
U32 []uint32 `query:"u32"`
U64 []uint64 `query:"u64"`
U []uint `query:"u"`
S []string `query:"s"`
}
var q2 Query2
c.Request().URI().SetQueryString(qs)
require.NoError(t, c.Bind().Req(&q2).Err())
require.Equal(t, Query2{
Flag: []bool{true},
I8: []int8{88},
I16: []int16{166},
I32: []int32{322},
I64: []int64{644},
I: []int{101},
U8: []uint8{77},
U16: []uint16{165},
U32: []uint32{321},
U64: []uint64{643},
U: []uint{99},
S: []string{"john"},
}, q2)
}
// go test -run Test_Bind_Query -v
func Test_Bind_Query(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
ID int `query:"id"`
Name string `query:"name"`
Hobby []string `query:"hobby"`
}
var q Query
c.Request().SetBody([]byte{})
c.Request().Header.SetContentType("")
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football")
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, 2, len(q.Hobby))
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, 1, len(q.Hobby))
c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, 2, len(q.Hobby))
c.Request().URI().SetQueryString("")
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, 0, len(q.Hobby))
type Query2 struct {
Bool bool `query:"bool"`
ID int `query:"id"`
Name string `query:"name"`
Hobby string `query:"hobby"`
FavouriteDrinks string `query:"favouriteDrinks"`
Empty []string `query:"empty"`
Alloc []string `query:"alloc"`
No []int64 `query:"no"`
}
var q2 Query2
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")
require.NoError(t, c.Bind().Req(&q2).Err())
require.Equal(t, "basketball,football", q2.Hobby)
require.Equal(t, "tom", q2.Name) // check value get overwritten
require.Equal(t, "milo,coke,pepsi", q2.FavouriteDrinks)
require.Equal(t, []string{}, q2.Empty)
require.Equal(t, []string{""}, q2.Alloc)
require.Equal(t, []int64{1}, q2.No)
type ArrayQuery struct {
Data []string `query:"data[]"`
}
var aq ArrayQuery
c.Request().URI().SetQueryString("data[]=john&data[]=doe")
require.NoError(t, c.Bind().Req(&aq).Err())
require.Equal(t, ArrayQuery{Data: []string{"john", "doe"}}, aq)
}
// go test -run Test_Bind_Resp_Header -v
func Test_Bind_Resp_Header(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type resHeader struct {
Key string `respHeader:"k"`
Keys []string `respHeader:"keys"`
}
c.Set("k", "vv")
c.Response().Header.Add("keys", "v1")
c.Response().Header.Add("keys", "v2")
var q resHeader
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, "vv", q.Key)
require.Equal(t, []string{"v1", "v2"}, q.Keys)
}
var _ Binder = (*userCtxUnmarshaler)(nil)
type userCtxUnmarshaler struct {
V int
}
func (u *userCtxUnmarshaler) UnmarshalFiberCtx(ctx Ctx) error {
u.V++
return nil
}
// go test -run Test_Bind_CustomizedUnmarshaler -v
func Test_Bind_CustomizedUnmarshaler(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Req struct {
Key userCtxUnmarshaler
}
var r Req
require.NoError(t, c.Bind().Req(&r).Err())
require.Equal(t, 1, r.Key.V)
require.NoError(t, c.Bind().Req(&r).Err())
require.Equal(t, 1, r.Key.V)
}
// go test -run Test_Bind_TextUnmarshaler -v
func Test_Bind_TextUnmarshaler(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Req struct {
Time time.Time `query:"time"`
}
now := time.Now()
c.Request().URI().SetQueryString(url.Values{
"time": []string{now.Format(time.RFC3339Nano)},
}.Encode())
var q Req
require.NoError(t, c.Bind().Req(&q).Err())
require.Equal(t, false, q.Time.IsZero(), "time should not be zero")
require.Equal(t, true, q.Time.Before(now.Add(time.Second)))
require.Equal(t, true, q.Time.After(now.Add(-time.Second)))
}
// go test -run Test_Bind_error_message -v
func Test_Bind_error_message(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Req struct {
Time time.Time `query:"time"`
}
c.Request().URI().SetQueryString("time=john")
err := c.Bind().Req(&Req{}).Err()
require.Error(t, err)
require.Regexp(t, regexp.MustCompile(`unable to decode 'john' as time`), err.Error())
}
func Test_Bind_Form(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
c.Context().Request.Header.Set(HeaderContentType, MIMEApplicationForm)
c.Context().Request.SetBody([]byte(url.Values{
"username": {"u"},
"password": {"p"},
"likes": {"apple", "banana"},
}.Encode()))
type Req struct {
Username string `form:"username"`
Password string `form:"password"`
Likes []string `form:"likes"`
}
var r Req
err := c.Bind().Form(&r).Err()
require.NoError(t, err)
require.Equal(t, "u", r.Username)
require.Equal(t, "p", r.Password)
require.Equal(t, []string{"apple", "banana"}, r.Likes)
}
func Test_Bind_Multipart(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
buf := bytes.NewBuffer(nil)
boundary := multipart.NewWriter(nil).Boundary()
err := fasthttp.WriteMultipartForm(buf, &multipart.Form{
Value: map[string][]string{
"username": {"u"},
"password": {"p"},
"likes": {"apple", "banana"},
},
}, boundary)
require.NoError(t, err)
c.Context().Request.Header.Set(HeaderContentType, fmt.Sprintf("%s; boundary=%s", MIMEMultipartForm, boundary))
c.Context().Request.SetBody(buf.Bytes())
type Req struct {
Username string `multipart:"username"`
Password string `multipart:"password"`
Likes []string `multipart:"likes"`
}
var r Req
err = c.Bind().Multipart(&r).Err()
require.NoError(t, err)
require.Equal(t, "u", r.Username)
require.Equal(t, "p", r.Password)
require.Equal(t, []string{"apple", "banana"}, r.Likes)
}
type Req struct {
ID string `params:"id"`
I int `query:"I"`
J int `query:"j"`
K int `query:"k"`
Token string `header:"x-auth"`
}
func getBenchCtx() Ctx {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
ctx.values = [maxParams]string{"id string"}
ctx.route = &Route{Params: []string{"id"}}
var u = fasthttp.URI{}
u.SetQueryString("j=1&j=123&k=-1")
ctx.Request().SetURI(&u)
ctx.Request().Header.Set("a-auth", "bearer tt")
return ctx
}
func Benchmark_Bind_by_hand(b *testing.B) {
ctx := getBenchCtx()
for i := 0; i < b.N; i++ {
var req Req
var err error
if raw := ctx.Params("id"); raw != "" {
req.ID = raw
}
if raw := ctx.Query("i"); raw != "" {
req.I, err = strconv.Atoi(raw)
if err != nil {
b.Error(err)
b.FailNow()
}
}
if raw := ctx.Query("j"); raw != "" {
req.J, err = strconv.Atoi(raw)
if err != nil {
b.Error(err)
b.FailNow()
}
}
if raw := ctx.Query("k"); raw != "" {
req.K, err = strconv.Atoi(raw)
if err != nil {
b.Error(err)
b.FailNow()
}
}
req.Token = ctx.Get("x-auth")
}
}
func Benchmark_Bind(b *testing.B) {
ctx := getBenchCtx()
for i := 0; i < b.N; i++ {
var v = Req{}
err := ctx.Bind().Req(&v).Err()
if err != nil {
b.Error(err)
b.FailNow()
}
}
}
func Test_Binder_Float(t *testing.T) {
t.Parallel()
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
ctx.values = [maxParams]string{"3.14"}
ctx.route = &Route{Params: []string{"id"}}
var req struct {
ID1 float32 `param:"id"`
ID2 float64 `param:"id"`
}
err := ctx.Bind().Req(&req).Err()
require.NoError(t, err)
require.Equal(t, float32(3.14), req.ID1)
require.Equal(t, float64(3.14), req.ID2)
}