//nolint:wrapcheck,tagliatelle // We must not wrap errors in tests package fiber import ( "bytes" "compress/gzip" "encoding/json" "errors" "fmt" "mime/multipart" "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3/binder" "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" ) const helloWorld = "hello world" // go test -run Test_returnErr -v func Test_returnErr(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) err := c.Bind().WithAutoHandling().returnErr(nil) require.NoError(t, err) } // go test -run Test_Bind_Query -v func Test_Bind_Query(t *testing.T) { t.Parallel() app := New(Config{ EnableSplittingOnParsers: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Query struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := new(Query) require.NoError(t, c.Bind().Query(q)) require.Len(t, q.Hobby, 2) c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q = new(Query) require.NoError(t, c.Bind().Query(q)) require.Len(t, q.Hobby, 2) c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") q = new(Query) require.NoError(t, c.Bind().Query(q)) require.Len(t, q.Hobby, 3) empty := new(Query) c.Request().URI().SetQueryString("") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) type Query2 struct { Name string Hobby string Default string `query:"default,default:hello"` FavouriteDrinks []string Empty []string Alloc []string Defaults []string `query:"defaults,default:hello|world"` No []int64 ID int Bool bool } c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") q2 := new(Query2) q2.Bool = true q2.Name = helloWorld require.NoError(t, c.Bind().Query(q2)) require.Equal(t, "basketball,football", q2.Hobby) require.True(t, q2.Bool) require.Equal(t, "tom", q2.Name) // check value get overwritten require.Equal(t, []string{"milo", "coke", "pepsi"}, q2.FavouriteDrinks) var nilSlice []string require.Equal(t, nilSlice, q2.Empty) require.Equal(t, []string{""}, q2.Alloc) require.Equal(t, []int64{1}, q2.No) require.Equal(t, "hello", q2.Default) require.Equal(t, []string{"hello", "world"}, q2.Defaults) type RequiredQuery struct { Name string `query:"name,required"` } rq := new(RequiredQuery) c.Request().URI().SetQueryString("") require.Equal(t, "bind: name is empty", c.Bind().Query(rq).Error()) type ArrayQuery struct { Data []string } aq := new(ArrayQuery) c.Request().URI().SetQueryString("data[]=john&data[]=doe") require.NoError(t, c.Bind().Query(aq)) require.Len(t, aq.Data, 2) } // go test -run Test_Bind_Query_Map -v func Test_Bind_Query_Map(t *testing.T) { t.Parallel() app := New(Config{ EnableSplittingOnParsers: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := make(map[string][]string) require.NoError(t, c.Bind().Query(&q)) require.Len(t, q["hobby"], 2) c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q = make(map[string][]string) require.NoError(t, c.Bind().Query(&q)) require.Len(t, q["hobby"], 2) c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") q = make(map[string][]string) require.NoError(t, c.Bind().Query(&q)) require.Len(t, q["hobby"], 3) c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer") qq := make(map[string]string) require.NoError(t, c.Bind().Query(&qq)) require.Equal(t, "1", qq["id"]) empty := make(map[string][]string) c.Request().URI().SetQueryString("") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["hobby"]) em := make(map[string][]int) c.Request().URI().SetQueryString("") require.ErrorIs(t, c.Bind().Query(&em), binder.ErrMapNotConvertable) } // go test -run Test_Bind_Query_WithSetParserDecoder -v func Test_Bind_Query_WithSetParserDecoder(t *testing.T) { type NonRFCTime time.Time nonRFCConverter := func(value string) reflect.Value { if v, err := time.Parse("2006-01-02", value); err == nil { return reflect.ValueOf(v) } return reflect.Value{} } nonRFCTime := binder.ParserType{ CustomType: NonRFCTime{}, Converter: nonRFCConverter, } binder.SetParserDecoder(binder.ParserConfig{ IgnoreUnknownKeys: true, ParserType: []binder.ParserType{nonRFCTime}, ZeroEmpty: true, SetAliasTag: "query", }) app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type NonRFCTimeInput struct { Date NonRFCTime `query:"date"` Title string `query:"title"` Body string `query:"body"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") q := new(NonRFCTimeInput) c.Request().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October") require.NoError(t, c.Bind().Query(q)) require.Equal(t, "CustomDateTest", q.Title) date := fmt.Sprintf("%v", q.Date) require.Equal(t, "{0 63753609600 }", date) require.Equal(t, "October", q.Body) c.Request().URI().SetQueryString("date=2021-04-10&title&Body=October") q = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } require.NoError(t, c.Bind().Query(q)) require.Equal(t, "", q.Title) } // go test -run Test_Bind_Query_Schema -v func Test_Bind_Query_Schema(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Query1 struct { Name string `query:"name,required"` Nested struct { Age int `query:"age"` } `query:"nested,required"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("name=tom&nested.age=10") q := new(Query1) require.NoError(t, c.Bind().Query(q)) c.Request().URI().SetQueryString("namex=tom&nested.age=10") q = new(Query1) require.Equal(t, "bind: name is empty", c.Bind().Query(q).Error()) c.Request().URI().SetQueryString("name=tom&nested.agex=10") q = new(Query1) require.NoError(t, c.Bind().Query(q)) c.Request().URI().SetQueryString("name=tom&test.age=10") q = new(Query1) require.Equal(t, "bind: nested is empty", c.Bind().Query(q).Error()) type Query2 struct { Name string `query:"name"` Nested struct { Age int `query:"age,required"` } `query:"nested"` } c.Request().URI().SetQueryString("name=tom&nested.age=10") q2 := new(Query2) require.NoError(t, c.Bind().Query(q2)) c.Request().URI().SetQueryString("nested.age=10") q2 = new(Query2) require.NoError(t, c.Bind().Query(q2)) c.Request().URI().SetQueryString("nested.agex=10") q2 = new(Query2) require.Equal(t, "bind: nested.age is empty", c.Bind().Query(q2).Error()) c.Request().URI().SetQueryString("nested.agex=10") q2 = new(Query2) require.Equal(t, "bind: nested.age is empty", c.Bind().Query(q2).Error()) type Node struct { Next *Node `query:"next,required"` Value int `query:"val,required"` } c.Request().URI().SetQueryString("val=1&next.val=3") n := new(Node) require.NoError(t, c.Bind().Query(n)) require.Equal(t, 1, n.Value) require.Equal(t, 3, n.Next.Value) c.Request().URI().SetQueryString("next.val=2") n = new(Node) require.Equal(t, "bind: val is empty", c.Bind().Query(n).Error()) c.Request().URI().SetQueryString("val=3&next.value=2") n = new(Node) n.Next = new(Node) require.NoError(t, c.Bind().Query(n)) require.Equal(t, 3, n.Value) require.Equal(t, 0, n.Next.Value) type Person struct { Name string `query:"name"` Age int `query:"age"` } type CollectionQuery struct { Data []Person `query:"data"` } c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10&data[1][name]=doe&data[1][age]=12") cq := new(CollectionQuery) require.NoError(t, c.Bind().Query(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, 10, cq.Data[0].Age) require.Equal(t, "doe", cq.Data[1].Name) require.Equal(t, 12, cq.Data[1].Age) c.Request().URI().SetQueryString("data.0.name=john&data.0.age=10&data.1.name=doe&data.1.age=12") cq = new(CollectionQuery) require.NoError(t, c.Bind().Query(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, 10, cq.Data[0].Age) require.Equal(t, "doe", cq.Data[1].Name) require.Equal(t, 12, cq.Data[1].Age) } // go test -run Test_Bind_Header -v func Test_Bind_Header(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Header struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.Add("id", "1") c.Request().Header.Add("Name", "John Doe") c.Request().Header.Add("Hobby", "golang,fiber") q := new(Header) require.NoError(t, c.Bind().Header(q)) require.Len(t, q.Hobby, 1) c.Request().Header.Del("hobby") c.Request().Header.Add("Hobby", "golang,fiber,go") q = new(Header) require.NoError(t, c.Bind().Header(q)) require.Len(t, q.Hobby, 1) empty := new(Header) c.Request().Header.Del("hobby") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) type Header2 struct { Name string Hobby string FavouriteDrinks []string Empty []string Alloc []string No []int64 ID int Bool bool } c.Request().Header.Add("id", "2") c.Request().Header.Add("Name", "Jane Doe") c.Request().Header.Del("hobby") c.Request().Header.Add("Hobby", "go,fiber") c.Request().Header.Add("favouriteDrinks", "milo,coke,pepsi") c.Request().Header.Add("alloc", "") c.Request().Header.Add("no", "1") h2 := new(Header2) h2.Bool = true h2.Name = helloWorld require.NoError(t, c.Bind().Header(h2)) require.Equal(t, "go,fiber", h2.Hobby) require.True(t, h2.Bool) require.Equal(t, "Jane Doe", h2.Name) // check value get overwritten require.Equal(t, []string{"milo,coke,pepsi"}, h2.FavouriteDrinks) var nilSlice []string require.Equal(t, nilSlice, h2.Empty) require.Equal(t, []string{""}, h2.Alloc) require.Equal(t, []int64{1}, h2.No) type RequiredHeader struct { Name string `header:"name,required"` } rh := new(RequiredHeader) c.Request().Header.Del("name") require.Equal(t, "bind: name is empty", c.Bind().Header(rh).Error()) } // go test -run Test_Bind_Header_Map -v func Test_Bind_Header_Map(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.Add("id", "1") c.Request().Header.Add("Name", "John Doe") c.Request().Header.Add("Hobby", "golang,fiber") q := make(map[string][]string, 0) require.NoError(t, c.Bind().Header(&q)) require.Len(t, q["Hobby"], 1) c.Request().Header.Del("hobby") c.Request().Header.Add("Hobby", "golang,fiber,go") q = make(map[string][]string, 0) require.NoError(t, c.Bind().Header(&q)) require.Len(t, q["Hobby"], 1) empty := make(map[string][]string, 0) c.Request().Header.Del("hobby") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["Hobby"]) } // go test -run Test_Bind_Header_WithSetParserDecoder -v func Test_Bind_Header_WithSetParserDecoder(t *testing.T) { type NonRFCTime time.Time nonRFCConverter := func(value string) reflect.Value { if v, err := time.Parse("2006-01-02", value); err == nil { return reflect.ValueOf(v) } return reflect.Value{} } nonRFCTime := binder.ParserType{ CustomType: NonRFCTime{}, Converter: nonRFCConverter, } binder.SetParserDecoder(binder.ParserConfig{ IgnoreUnknownKeys: true, ParserType: []binder.ParserType{nonRFCTime}, ZeroEmpty: true, SetAliasTag: "req", }) app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type NonRFCTimeInput struct { Date NonRFCTime `req:"date"` Title string `req:"title"` Body string `req:"body"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") r := new(NonRFCTimeInput) c.Request().Header.Add("Date", "2021-04-10") c.Request().Header.Add("Title", "CustomDateTest") c.Request().Header.Add("Body", "October") require.NoError(t, c.Bind().Header(r)) require.Equal(t, "CustomDateTest", r.Title) date := fmt.Sprintf("%v", r.Date) require.Equal(t, "{0 63753609600 }", date) require.Equal(t, "October", r.Body) c.Request().Header.Add("Title", "") r = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } require.NoError(t, c.Bind().Header(r)) require.Equal(t, "", r.Title) } // go test -run Test_Bind_Header_Schema -v func Test_Bind_Header_Schema(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Header1 struct { Name string `header:"Name,required"` Nested struct { Age int `header:"Age"` } `header:"Nested,required"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.Add("Name", "tom") c.Request().Header.Add("Nested.Age", "10") q := new(Header1) require.NoError(t, c.Bind().Header(q)) c.Request().Header.Del("Name") q = new(Header1) require.Equal(t, "bind: Name is empty", c.Bind().Header(q).Error()) c.Request().Header.Add("Name", "tom") c.Request().Header.Del("Nested.Age") c.Request().Header.Add("Nested.Agex", "10") q = new(Header1) require.NoError(t, c.Bind().Header(q)) c.Request().Header.Del("Nested.Agex") q = new(Header1) require.Equal(t, "bind: Nested is empty", c.Bind().Header(q).Error()) c.Request().Header.Del("Nested.Agex") c.Request().Header.Del("Name") type Header2 struct { Name string `header:"Name"` Nested struct { Age int `header:"age,required"` } `header:"Nested"` } c.Request().Header.Add("Name", "tom") c.Request().Header.Add("Nested.Age", "10") h2 := new(Header2) require.NoError(t, c.Bind().Header(h2)) c.Request().Header.Del("Name") h2 = new(Header2) require.NoError(t, c.Bind().Header(h2)) c.Request().Header.Del("Name") c.Request().Header.Del("Nested.Age") c.Request().Header.Add("Nested.Agex", "10") h2 = new(Header2) require.Equal(t, "bind: Nested.age is empty", c.Bind().Header(h2).Error()) type Node struct { Next *Node `header:"Next,required"` Value int `header:"Val,required"` } c.Request().Header.Add("Val", "1") c.Request().Header.Add("Next.Val", "3") n := new(Node) require.NoError(t, c.Bind().Header(n)) require.Equal(t, 1, n.Value) require.Equal(t, 3, n.Next.Value) c.Request().Header.Del("Val") n = new(Node) require.Equal(t, "bind: Val is empty", c.Bind().Header(n).Error()) c.Request().Header.Add("Val", "3") c.Request().Header.Del("Next.Val") c.Request().Header.Add("Next.Value", "2") n = new(Node) n.Next = new(Node) require.NoError(t, c.Bind().Header(n)) require.Equal(t, 3, n.Value) require.Equal(t, 0, n.Next.Value) } // go test -run Test_Bind_Resp_Header -v func Test_Bind_RespHeader(t *testing.T) { t.Parallel() app := New(Config{ EnableSplittingOnParsers: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Header struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Response().Header.Add("id", "1") c.Response().Header.Add("Name", "John Doe") c.Response().Header.Add("Hobby", "golang,fiber") q := new(Header) require.NoError(t, c.Bind().RespHeader(q)) require.Len(t, q.Hobby, 2) c.Response().Header.Del("hobby") c.Response().Header.Add("Hobby", "golang,fiber,go") q = new(Header) require.NoError(t, c.Bind().RespHeader(q)) require.Len(t, q.Hobby, 3) empty := new(Header) c.Response().Header.Del("hobby") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) type Header2 struct { Name string Hobby string FavouriteDrinks []string Empty []string Alloc []string No []int64 ID int Bool bool } c.Response().Header.Add("id", "2") c.Response().Header.Add("Name", "Jane Doe") c.Response().Header.Del("hobby") c.Response().Header.Add("Hobby", "go,fiber") c.Response().Header.Add("favouriteDrinks", "milo,coke,pepsi") c.Response().Header.Add("alloc", "") c.Response().Header.Add("no", "1") h2 := new(Header2) h2.Bool = true h2.Name = helloWorld require.NoError(t, c.Bind().RespHeader(h2)) require.Equal(t, "go,fiber", h2.Hobby) require.True(t, h2.Bool) require.Equal(t, "Jane Doe", h2.Name) // check value get overwritten require.Equal(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) var nilSlice []string require.Equal(t, nilSlice, h2.Empty) require.Equal(t, []string{""}, h2.Alloc) require.Equal(t, []int64{1}, h2.No) type RequiredHeader struct { Name string `respHeader:"name,required"` } rh := new(RequiredHeader) c.Response().Header.Del("name") require.Equal(t, "bind: name is empty", c.Bind().RespHeader(rh).Error()) } // go test -run Test_Bind_RespHeader_Map -v func Test_Bind_RespHeader_Map(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Response().Header.Add("id", "1") c.Response().Header.Add("Name", "John Doe") c.Response().Header.Add("Hobby", "golang,fiber") q := make(map[string][]string, 0) require.NoError(t, c.Bind().RespHeader(&q)) require.Len(t, q["Hobby"], 1) c.Response().Header.Del("hobby") c.Response().Header.Add("Hobby", "golang,fiber,go") q = make(map[string][]string, 0) require.NoError(t, c.Bind().RespHeader(&q)) require.Len(t, q["Hobby"], 1) empty := make(map[string][]string, 0) c.Response().Header.Del("hobby") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["Hobby"]) } // go test -v -run=^$ -bench=Benchmark_Bind_Query -benchmem -count=4 func Benchmark_Bind_Query(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Query struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := new(Query) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Query(q) } require.NoError(b, err) require.Equal(b, "tom", q.Name) require.Equal(b, 1, q.ID) require.Len(b, q.Hobby, 2) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_Default -benchmem -count=4 func Benchmark_Bind_Query_Default(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Query struct { Name string `query:"name,default:tom"` Hobby []string `query:"hobby,default:football|basketball"` ID int `query:"id,default:1"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("") q := new(Query) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { *q = Query{} err = c.Bind().Query(q) } require.NoError(b, err) require.Equal(b, "tom", q.Name) require.Equal(b, 1, q.ID) require.Len(b, q.Hobby, 2) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_Map -benchmem -count=4 func Benchmark_Bind_Query_Map(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := make(map[string][]string) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Query(&q) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_WithParseParam -benchmem -count=4 func Benchmark_Bind_Query_WithParseParam(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Person struct { Name string `query:"name"` Age int `query:"age"` } type CollectionQuery struct { Data []Person `query:"data"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10") cq := new(CollectionQuery) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Query(cq) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_Query_Comma -benchmem -count=4 func Benchmark_Bind_Query_Comma(b *testing.B) { var err error app := New(Config{ EnableSplittingOnParsers: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Query struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q := new(Query) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Query(q) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_Header -benchmem -count=4 func Benchmark_Bind_Header(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type ReqHeader struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.Add("id", "1") c.Request().Header.Add("Name", "John Doe") c.Request().Header.Add("Hobby", "golang,fiber") q := new(ReqHeader) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Header(q) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_Header_Map -benchmem -count=4 func Benchmark_Bind_Header_Map(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.Add("id", "1") c.Request().Header.Add("Name", "John Doe") c.Request().Header.Add("Hobby", "golang,fiber") q := make(map[string][]string) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Header(&q) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_RespHeader -benchmem -count=4 func Benchmark_Bind_RespHeader(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type ReqHeader struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Response().Header.Add("id", "1") c.Response().Header.Add("Name", "John Doe") c.Response().Header.Add("Hobby", "golang,fiber") q := new(ReqHeader) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().RespHeader(q) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_RespHeader_Map -benchmem -count=4 func Benchmark_Bind_RespHeader_Map(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Response().Header.Add("id", "1") c.Response().Header.Add("Name", "John Doe") c.Response().Header.Add("Hobby", "golang,fiber") q := make(map[string][]string) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().RespHeader(&q) } require.NoError(b, err) } // go test -run Test_Bind_Body_Compression func Test_Bind_Body(t *testing.T) { t.Parallel() app := New() reqBody := []byte(`{"name":"john"}`) type Demo struct { Name string `json:"name" xml:"name" form:"name" query:"name"` Names []string `json:"names" xml:"names" form:"names" query:"names"` } // Helper function to test compressed bodies testCompressedBody := func(t *testing.T, compressedBody []byte, encoding string) { t.Helper() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetContentType(MIMEApplicationJSON) c.Request().Header.Set(fasthttp.HeaderContentEncoding, encoding) c.Request().SetBody(compressedBody) c.Request().Header.SetContentLength(len(compressedBody)) d := new(Demo) require.NoError(t, c.Bind().Body(d)) require.Equal(t, "john", d.Name) c.Request().Header.Del(fasthttp.HeaderContentEncoding) } t.Run("Gzip", func(t *testing.T) { t.Parallel() compressedBody := fasthttp.AppendGzipBytes(nil, reqBody) require.NotEqual(t, reqBody, compressedBody) testCompressedBody(t, compressedBody, "gzip") }) t.Run("Deflate", func(t *testing.T) { t.Parallel() compressedBody := fasthttp.AppendDeflateBytes(nil, reqBody) require.NotEqual(t, reqBody, compressedBody) testCompressedBody(t, compressedBody, "deflate") }) t.Run("Brotli", func(t *testing.T) { t.Parallel() compressedBody := fasthttp.AppendBrotliBytes(nil, reqBody) require.NotEqual(t, reqBody, compressedBody) testCompressedBody(t, compressedBody, "br") }) t.Run("Zstd", func(t *testing.T) { t.Parallel() compressedBody := fasthttp.AppendZstdBytes(nil, reqBody) require.NotEqual(t, reqBody, compressedBody) testCompressedBody(t, compressedBody, "zstd") }) testDecodeParser := func(t *testing.T, contentType string, body []byte) { t.Helper() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetContentType(contentType) c.Request().SetBody(body) c.Request().Header.SetContentLength(len(body)) d := new(Demo) require.NoError(t, c.Bind().Body(d)) require.Equal(t, "john", d.Name) } t.Run("JSON", func(t *testing.T) { testDecodeParser(t, MIMEApplicationJSON, []byte(`{"name":"john"}`)) }) t.Run("CBOR", func(t *testing.T) { enc, err := cbor.Marshal(&Demo{Name: "john"}) if err != nil { t.Error(err) } testDecodeParser(t, MIMEApplicationCBOR, enc) // Test invalid CBOR data t.Run("Invalid", func(t *testing.T) { invalidData := []byte{0xFF, 0xFF} // Invalid CBOR data c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetContentType(MIMEApplicationCBOR) c.Request().SetBody(invalidData) d := new(Demo) require.Error(t, c.Bind().Body(d)) }) }) t.Run("XML", func(t *testing.T) { testDecodeParser(t, MIMEApplicationXML, []byte(`john`)) }) t.Run("Form", func(t *testing.T) { testDecodeParser(t, MIMEApplicationForm, []byte("name=john")) }) t.Run("MultipartForm", func(t *testing.T) { testDecodeParser(t, MIMEMultipartForm+`;boundary="b"`, []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")) }) testDecodeParserError := func(t *testing.T, contentType, body string) { t.Helper() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetContentType(contentType) c.Request().SetBody([]byte(body)) c.Request().Header.SetContentLength(len(body)) require.Error(t, c.Bind().Body(nil)) } t.Run("ErrorInvalidContentType", func(t *testing.T) { testDecodeParserError(t, "invalid-content-type", "") }) t.Run("ErrorMalformedMultipart", func(t *testing.T) { testDecodeParserError(t, MIMEMultipartForm+`;boundary="b"`, "--b") }) type CollectionQuery struct { Data []Demo `query:"data"` } t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Reset() buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) require.NoError(t, writer.WriteField("data.0.name", "john")) require.NoError(t, writer.WriteField("data.1.name", "doe")) require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) c.Request().Header.SetContentLength(len(c.Body())) cq := new(CollectionQuery) require.NoError(t, c.Bind().Body(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) }) t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Reset() buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) require.NoError(t, writer.WriteField("data[0][name]", "john")) require.NoError(t, writer.WriteField("data[1][name]", "doe")) require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) c.Request().Header.SetContentLength(len(c.Body())) cq := new(CollectionQuery) require.NoError(t, c.Bind().Body(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) }) t.Run("CollectionQuerySquareBrackets", func(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Reset() c.Request().Header.SetContentType(MIMEApplicationForm) c.Request().SetBody([]byte("data[0][name]=john&data[1][name]=doe")) c.Request().Header.SetContentLength(len(c.Body())) cq := new(CollectionQuery) require.NoError(t, c.Bind().Body(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) }) t.Run("CollectionQueryDotNotation", func(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Reset() c.Request().Header.SetContentType(MIMEApplicationForm) c.Request().SetBody([]byte("data.0.name=john&data.1.name=doe")) c.Request().Header.SetContentLength(len(c.Body())) cq := new(CollectionQuery) require.NoError(t, c.Bind().Body(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) }) } // go test -run Test_Bind_Body_WithSetParserDecoder func Test_Bind_Body_WithSetParserDecoder(t *testing.T) { type CustomTime time.Time timeConverter := func(value string) reflect.Value { if v, err := time.Parse("2006-01-02", value); err == nil { return reflect.ValueOf(v) } return reflect.Value{} } customTime := binder.ParserType{ CustomType: CustomTime{}, Converter: timeConverter, } binder.SetParserDecoder(binder.ParserConfig{ IgnoreUnknownKeys: true, ParserType: []binder.ParserType{customTime}, ZeroEmpty: true, SetAliasTag: "form", }) app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Demo struct { Date CustomTime `form:"date"` Title string `form:"title"` Body string `form:"body"` } testDecodeParser := func(contentType, body string) { c.Request().Header.SetContentType(contentType) c.Request().SetBody([]byte(body)) c.Request().Header.SetContentLength(len(body)) d := Demo{ Title: "Existing title", Body: "Existing Body", } require.NoError(t, c.Bind().Body(&d)) date := fmt.Sprintf("%v", d.Date) require.Equal(t, "{0 63743587200 }", date) require.Equal(t, "", d.Title) require.Equal(t, "New Body", d.Body) } testDecodeParser(MIMEApplicationForm, "date=2020-12-15&title=&body=New Body") testDecodeParser(MIMEMultipartForm+`; boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"date\"\r\n\r\n2020-12-15\r\n--b\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n--b\r\nContent-Disposition: form-data; name=\"body\"\r\n\r\nNew Body\r\n--b--") } // go test -v -run=^$ -bench=Benchmark_Bind_Body_JSON -benchmem -count=4 func Benchmark_Bind_Body_JSON(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Demo struct { Name string `json:"name"` } body := []byte(`{"name":"john"}`) c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEApplicationJSON) c.Request().Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } require.NoError(b, err) require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_XML -benchmem -count=4 func Benchmark_Bind_Body_XML(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Demo struct { Name string `xml:"name"` } body := []byte("john") c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEApplicationXML) c.Request().Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } require.NoError(b, err) require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_CBOR -benchmem -count=4 func Benchmark_Bind_Body_CBOR(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Demo struct { Name string `json:"name"` } body, err := cbor.Marshal(&Demo{Name: "john"}) if err != nil { b.Error(err) } c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEApplicationCBOR) c.Request().Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } require.NoError(b, err) require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form -benchmem -count=4 func Benchmark_Bind_Body_Form(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Demo struct { Name string `form:"name"` } body := []byte("name=john") c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEApplicationForm) c.Request().Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } require.NoError(b, err) require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_MultipartForm -benchmem -count=4 func Benchmark_Bind_Body_MultipartForm(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Demo struct { Name string `form:"name"` } buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) require.NoError(b, writer.WriteField("name", "john")) require.NoError(b, writer.Close()) body := buf.Bytes() c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary()) c.Request().Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } require.NoError(b, err) require.Equal(b, "john", d.Name) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_MultipartForm_Nested -benchmem -count=4 func Benchmark_Bind_Body_MultipartForm_Nested(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Person struct { Name string `form:"name"` Age int `form:"age"` } type Demo struct { Name string `form:"name"` Persons []Person `form:"persons"` } buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) require.NoError(b, writer.WriteField("name", "john")) require.NoError(b, writer.WriteField("persons.0.name", "john")) require.NoError(b, writer.WriteField("persons[0][age]", "10")) require.NoError(b, writer.WriteField("persons[1][name]", "doe")) require.NoError(b, writer.WriteField("persons.1.age", "20")) require.NoError(b, writer.Close()) body := buf.Bytes() c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary()) c.Request().Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } require.NoError(b, err) require.Equal(b, "john", d.Name) require.Equal(b, "john", d.Persons[0].Name) require.Equal(b, 10, d.Persons[0].Age) require.Equal(b, "doe", d.Persons[1].Name) require.Equal(b, 20, d.Persons[1].Age) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form_Map -benchmem -count=4 func Benchmark_Bind_Body_Form_Map(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) body := []byte("name=john") c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEApplicationForm) c.Request().Header.SetContentLength(len(body)) d := make(map[string]string) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Body(&d) } require.NoError(b, err) require.Equal(b, "john", d["name"]) } // go test -run Test_Bind_URI func Test_Bind_URI(t *testing.T) { t.Parallel() app := New() app.Get("/test1/userId/role/:roleId", func(c Ctx) error { type Demo struct { UserID uint `uri:"userId"` RoleID uint `uri:"roleId"` } d := new(Demo) if err := c.Bind().URI(d); err != nil { t.Fatal(err) } require.Equal(t, uint(111), d.UserID) require.Equal(t, uint(222), d.RoleID) return nil }) _, err := app.Test(httptest.NewRequest(MethodGet, "/test1/111/role/222", nil)) require.NoError(t, err) _, err = app.Test(httptest.NewRequest(MethodGet, "/test2/111/role/222", nil)) require.NoError(t, err) } // go test -run Test_Bind_URI_Map func Test_Bind_URI_Map(t *testing.T) { t.Parallel() app := New() app.Get("/test1/userId/role/:roleId", func(c Ctx) error { d := make(map[string]string) if err := c.Bind().URI(&d); err != nil { t.Fatal(err) } require.Equal(t, uint(111), d["userId"]) require.Equal(t, uint(222), d["roleId"]) return nil }) _, err := app.Test(httptest.NewRequest(MethodGet, "/test1/111/role/222", nil)) require.NoError(t, err) _, err = app.Test(httptest.NewRequest(MethodGet, "/test2/111/role/222", nil)) require.NoError(t, err) } // go test -v -run=^$ -bench=Benchmark_Bind_URI -benchmem -count=4 func Benchmark_Bind_URI(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed c.route = &Route{ Params: []string{ "param1", "param2", "param3", "param4", }, } c.values = [maxParams]string{ "john", "doe", "is", "awesome", } var res struct { Param1 string `uri:"param1"` Param2 string `uri:"param2"` Param3 string `uri:"param3"` Param4 string `uri:"param4"` } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().URI(&res) } require.NoError(b, err) require.Equal(b, "john", res.Param1) require.Equal(b, "doe", res.Param2) require.Equal(b, "is", res.Param3) require.Equal(b, "awesome", res.Param4) } // go test -v -run=^$ -bench=Benchmark_Bind_URI_Map -benchmem -count=4 func Benchmark_Bind_URI_Map(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed c.route = &Route{ Params: []string{ "param1", "param2", "param3", "param4", }, } c.values = [maxParams]string{ "john", "doe", "is", "awesome", } res := make(map[string]string) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().URI(&res) } require.NoError(b, err) require.Equal(b, "john", res["param1"]) require.Equal(b, "doe", res["param2"]) require.Equal(b, "is", res["param3"]) require.Equal(b, "awesome", res["param4"]) } // go test -run Test_Bind_Cookie -v func Test_Bind_Cookie(t *testing.T) { t.Parallel() app := New(Config{ EnableSplittingOnParsers: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Cookie struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.SetCookie("id", "1") c.Request().Header.SetCookie("Name", "John Doe") c.Request().Header.SetCookie("Hobby", "golang,fiber") q := new(Cookie) require.NoError(t, c.Bind().Cookie(q)) require.Len(t, q.Hobby, 2) c.Request().Header.DelCookie("hobby") c.Request().Header.SetCookie("Hobby", "golang,fiber,go") q = new(Cookie) require.NoError(t, c.Bind().Cookie(q)) require.Len(t, q.Hobby, 3) empty := new(Cookie) c.Request().Header.DelCookie("hobby") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) type Cookie2 struct { Name string Hobby string FavouriteDrinks []string Empty []string Alloc []string No []int64 ID int Bool bool } c.Request().Header.SetCookie("id", "2") c.Request().Header.SetCookie("Name", "Jane Doe") c.Request().Header.DelCookie("hobby") c.Request().Header.SetCookie("Hobby", "go,fiber") c.Request().Header.SetCookie("favouriteDrinks", "milo,coke,pepsi") c.Request().Header.SetCookie("alloc", "") c.Request().Header.SetCookie("no", "1") h2 := new(Cookie2) h2.Bool = true h2.Name = helloWorld require.NoError(t, c.Bind().Cookie(h2)) require.Equal(t, "go,fiber", h2.Hobby) require.True(t, h2.Bool) require.Equal(t, "Jane Doe", h2.Name) // check value get overwritten require.Equal(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks) var nilSlice []string require.Equal(t, nilSlice, h2.Empty) require.Equal(t, []string{""}, h2.Alloc) require.Equal(t, []int64{1}, h2.No) type RequiredCookie struct { Name string `cookie:"name,required"` } rh := new(RequiredCookie) c.Request().Header.DelCookie("name") require.Equal(t, "bind: name is empty", c.Bind().Cookie(rh).Error()) } // go test -run Test_Bind_Cookie_Map -v func Test_Bind_Cookie_Map(t *testing.T) { t.Parallel() app := New(Config{ EnableSplittingOnParsers: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.SetCookie("id", "1") c.Request().Header.SetCookie("Name", "John Doe") c.Request().Header.SetCookie("Hobby", "golang,fiber") q := make(map[string][]string) require.NoError(t, c.Bind().Cookie(&q)) require.Len(t, q["Hobby"], 2) c.Request().Header.DelCookie("hobby") c.Request().Header.SetCookie("Hobby", "golang,fiber,go") q = make(map[string][]string) require.NoError(t, c.Bind().Cookie(&q)) require.Len(t, q["Hobby"], 3) empty := make(map[string][]string) c.Request().Header.DelCookie("hobby") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["Hobby"]) } // go test -run Test_Bind_Cookie_WithSetParserDecoder -v func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) { type NonRFCTime time.Time nonRFCConverter := func(value string) reflect.Value { if v, err := time.Parse("2006-01-02", value); err == nil { return reflect.ValueOf(v) } return reflect.Value{} } nonRFCTime := binder.ParserType{ CustomType: NonRFCTime{}, Converter: nonRFCConverter, } binder.SetParserDecoder(binder.ParserConfig{ IgnoreUnknownKeys: true, ParserType: []binder.ParserType{nonRFCTime}, ZeroEmpty: true, SetAliasTag: "cerez", }) app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type NonRFCTimeInput struct { Date NonRFCTime `cerez:"date"` Title string `cerez:"title"` Body string `cerez:"body"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") r := new(NonRFCTimeInput) c.Request().Header.SetCookie("Date", "2021-04-10") c.Request().Header.SetCookie("Title", "CustomDateTest") c.Request().Header.SetCookie("Body", "October") require.NoError(t, c.Bind().Cookie(r)) require.Equal(t, "CustomDateTest", r.Title) date := fmt.Sprintf("%v", r.Date) require.Equal(t, "{0 63753609600 }", date) require.Equal(t, "October", r.Body) c.Request().Header.SetCookie("Title", "") r = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } require.NoError(t, c.Bind().Cookie(r)) require.Equal(t, "", r.Title) } // go test -run Test_Bind_Cookie_Schema -v func Test_Bind_Cookie_Schema(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Cookie1 struct { Name string `cookie:"Name,required"` Nested struct { Age int `cookie:"Age"` } `cookie:"Nested,required"` } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.SetCookie("Name", "tom") c.Request().Header.SetCookie("Nested.Age", "10") q := new(Cookie1) require.NoError(t, c.Bind().Cookie(q)) c.Request().Header.DelCookie("Name") q = new(Cookie1) require.Equal(t, "bind: Name is empty", c.Bind().Cookie(q).Error()) c.Request().Header.SetCookie("Name", "tom") c.Request().Header.DelCookie("Nested.Age") c.Request().Header.SetCookie("Nested.Agex", "10") q = new(Cookie1) require.NoError(t, c.Bind().Cookie(q)) c.Request().Header.DelCookie("Nested.Agex") q = new(Cookie1) require.Equal(t, "bind: Nested is empty", c.Bind().Cookie(q).Error()) c.Request().Header.DelCookie("Nested.Agex") c.Request().Header.DelCookie("Name") type Cookie2 struct { Name string `cookie:"Name"` Nested struct { Age int `cookie:"Age,required"` } `cookie:"Nested"` } c.Request().Header.SetCookie("Name", "tom") c.Request().Header.SetCookie("Nested.Age", "10") h2 := new(Cookie2) require.NoError(t, c.Bind().Cookie(h2)) c.Request().Header.DelCookie("Name") h2 = new(Cookie2) require.NoError(t, c.Bind().Cookie(h2)) c.Request().Header.DelCookie("Name") c.Request().Header.DelCookie("Nested.Age") c.Request().Header.SetCookie("Nested.Agex", "10") h2 = new(Cookie2) require.Equal(t, "bind: Nested.Age is empty", c.Bind().Cookie(h2).Error()) type Node struct { Next *Node `cookie:"Next,required"` Value int `cookie:"Val,required"` } c.Request().Header.SetCookie("Val", "1") c.Request().Header.SetCookie("Next.Val", "3") n := new(Node) require.NoError(t, c.Bind().Cookie(n)) require.Equal(t, 1, n.Value) require.Equal(t, 3, n.Next.Value) c.Request().Header.DelCookie("Val") n = new(Node) require.Equal(t, "bind: Val is empty", c.Bind().Cookie(n).Error()) c.Request().Header.SetCookie("Val", "3") c.Request().Header.DelCookie("Next.Val") c.Request().Header.SetCookie("Next.Value", "2") n = new(Node) n.Next = new(Node) require.NoError(t, c.Bind().Cookie(n)) require.Equal(t, 3, n.Value) require.Equal(t, 0, n.Next.Value) } // go test -v -run=^$ -bench=Benchmark_Bind_Cookie -benchmem -count=4 func Benchmark_Bind_Cookie(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type Cookie struct { Name string Hobby []string ID int } c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.SetCookie("id", "1") c.Request().Header.SetCookie("Name", "John Doe") c.Request().Header.SetCookie("Hobby", "golang,fiber") q := new(Cookie) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Cookie(q) } require.NoError(b, err) } // go test -v -run=^$ -bench=Benchmark_Bind_Cookie_Map -benchmem -count=4 func Benchmark_Bind_Cookie_Map(b *testing.B) { var err error app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().SetBody([]byte(``)) c.Request().Header.SetContentType("") c.Request().Header.SetCookie("id", "1") c.Request().Header.SetCookie("Name", "John Doe") c.Request().Header.SetCookie("Hobby", "golang,fiber") q := make(map[string][]string) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = c.Bind().Cookie(&q) } require.NoError(b, err) } // custom binder for testing type customBinder struct{} func (*customBinder) Name() string { return "custom" } func (*customBinder) MIMETypes() []string { return []string{"test", "test2"} } func (*customBinder) Parse(c Ctx, out any) error { return json.Unmarshal(c.Body(), out) } // go test -run Test_Bind_CustomBinder func Test_Bind_CustomBinder(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) // Register binder customBinder := &customBinder{} app.RegisterCustomBinder(customBinder) type Demo struct { Name string `json:"name"` } body := []byte(`{"name":"john"}`) c.Request().SetBody(body) c.Request().Header.SetContentType("test") c.Request().Header.SetContentLength(len(body)) d := new(Demo) require.NoError(t, c.Bind().Body(d)) require.NoError(t, c.Bind().Custom("custom", d)) require.Equal(t, ErrCustomBinderNotFound, c.Bind().Custom("not_custom", d)) require.Equal(t, "john", d.Name) } // go test -run Test_Bind_WithAutoHandling func Test_Bind_WithAutoHandling(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) type RequiredQuery struct { Name string `query:"name,required"` } rq := new(RequiredQuery) c.Request().URI().SetQueryString("") err := c.Bind().WithAutoHandling().Query(rq) require.Equal(t, StatusBadRequest, c.Response().StatusCode()) require.Equal(t, "Bad request: bind: name is empty", err.Error()) } // simple struct validator for testing type structValidator struct{} func (*structValidator) Validate(out any) error { out = reflect.ValueOf(out).Elem().Interface() sq, ok := out.(simpleQuery) if !ok { return errors.New("failed to type-assert to simpleQuery") } if sq.Name != "john" { return errors.New("you should have entered right name") } return nil } type simpleQuery struct { Name string `query:"name"` } // go test -run Test_Bind_StructValidator func Test_Bind_StructValidator(t *testing.T) { app := New(Config{StructValidator: &structValidator{}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) rq := new(simpleQuery) c.Request().URI().SetQueryString("name=efe") require.Equal(t, "you should have entered right name", c.Bind().Query(rq).Error()) rq = new(simpleQuery) c.Request().URI().SetQueryString("name=john") require.NoError(t, c.Bind().Query(rq)) } // go test -run Test_Bind_RepeatParserWithSameStruct -v func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) type Request struct { QueryParam string `query:"query_param"` HeaderParam string `header:"header_param"` BodyParam string `json:"body_param" xml:"body_param" form:"body_param"` } r := new(Request) c.Request().URI().SetQueryString("query_param=query_param") require.NoError(t, c.Bind().Query(r)) require.Equal(t, "query_param", r.QueryParam) c.Request().Header.Add("header_param", "header_param") require.NoError(t, c.Bind().Header(r)) require.Equal(t, "header_param", r.HeaderParam) var gzipJSON bytes.Buffer w := gzip.NewWriter(&gzipJSON) _, err := w.Write([]byte(`{"body_param":"body_param"}`)) require.NoError(t, err) err = w.Close() require.NoError(t, err) c.Request().Header.SetContentType(MIMEApplicationJSON) c.Request().Header.Set(HeaderContentEncoding, "gzip") c.Request().SetBody(gzipJSON.Bytes()) c.Request().Header.SetContentLength(len(gzipJSON.Bytes())) require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) c.Request().Header.Del(HeaderContentEncoding) testDecodeParser := func(contentType, body string) { c.Request().Header.SetContentType(contentType) c.Request().SetBody([]byte(body)) c.Request().Header.SetContentLength(len(body)) require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) } cb, err := cbor.Marshal(&Request{BodyParam: "body_param"}) require.NoError(t, err, "Failed to marshal CBOR data") testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`) testDecodeParser(MIMEApplicationXML, `body_param`) testDecodeParser(MIMEApplicationCBOR, string(cb)) testDecodeParser(MIMEApplicationForm, "body_param=body_param") testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"body_param\"\r\n\r\nbody_param\r\n--b--") } type RequestConfig struct { Headers map[string]string Cookies map[string]string ContentType string Query string Body []byte } func (rc *RequestConfig) ApplyTo(ctx Ctx) { if rc.Body != nil { ctx.Request().SetBody(rc.Body) ctx.Request().Header.SetContentLength(len(rc.Body)) } if rc.ContentType != "" { ctx.Request().Header.SetContentType(rc.ContentType) } for k, v := range rc.Headers { ctx.Request().Header.Set(k, v) } for k, v := range rc.Cookies { ctx.Request().Header.SetCookie(k, v) } if rc.Query != "" { ctx.Request().URI().SetQueryString(rc.Query) } } // go test -run Test_Bind_All func Test_Bind_All(t *testing.T) { t.Parallel() type User struct { Avatar *multipart.FileHeader `form:"avatar"` Name string `query:"name" json:"name" form:"name"` Email string `json:"email" form:"email"` Role string `header:"X-User-Role"` SessionID string `json:"session_id" cookie:"session_id"` ID int `uri:"id" query:"id" json:"id" form:"id"` } newBind := func(app *App) *Bind { return &Bind{ ctx: app.AcquireCtx(&fasthttp.RequestCtx{}), } } defaultConfig := func() *RequestConfig { return &RequestConfig{ ContentType: MIMEApplicationJSON, Body: []byte(`{"name":"john", "email": "john@doe.com", "session_id": "abc1234", "id": 1}`), Headers: map[string]string{ "X-User-Role": "admin", }, Cookies: map[string]string{ "session_id": "abc123", }, Query: "id=1&name=john", } } tests := []struct { out any expected *User config *RequestConfig name string wantErr bool }{ { name: "Invalid output type", out: 123, wantErr: true, }, { name: "Successful binding", out: new(User), config: defaultConfig(), expected: &User{ ID: 1, Name: "john", Email: "john@doe.com", Role: "admin", SessionID: "abc1234", }, }, { name: "Missing fields (partial JSON only)", out: new(User), config: &RequestConfig{ ContentType: MIMEApplicationJSON, Body: []byte(`{"name":"partial"}`), }, expected: &User{ Name: "partial", }, }, { name: "Override query with JSON", out: new(User), config: &RequestConfig{ ContentType: MIMEApplicationJSON, Body: []byte(`{"name":"fromjson", "id": 99}`), Query: "id=1&name=queryname", }, expected: &User{ Name: "fromjson", ID: 99, }, }, { name: "Form binding", out: new(User), config: &RequestConfig{ ContentType: MIMEApplicationForm, Body: []byte("id=2&name=formname&email=form@doe.com"), }, expected: &User{ ID: 2, Name: "formname", Email: "form@doe.com", }, }, } app := New() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() bind := newBind(app) if tt.config != nil { tt.config.ApplyTo(bind.ctx) } err := bind.All(tt.out) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) if tt.expected != nil { actual, ok := tt.out.(*User) require.True(t, ok) require.Equal(t, tt.expected.ID, actual.ID) require.Equal(t, tt.expected.Name, actual.Name) require.Equal(t, tt.expected.Email, actual.Email) require.Equal(t, tt.expected.Role, actual.Role) require.Equal(t, tt.expected.SessionID, actual.SessionID) } }) } } // go test -run Test_Bind_All_Uri_Precedence func Test_Bind_All_Uri_Precedence(t *testing.T) { t.Parallel() type User struct { Name string `json:"name"` Email string `json:"email"` ID int `uri:"id" json:"id" query:"id" form:"id"` } app := New() app.Post("/test1/:id", func(c Ctx) error { d := new(User) if err := c.Bind().All(d); err != nil { t.Fatal(err) } require.Equal(t, 111, d.ID) require.Equal(t, "john", d.Name) require.Equal(t, "john@doe.com", d.Email) return nil }) body := strings.NewReader(`{"id": 999, "name": "john", "email": "john@doe.com"}`) req := httptest.NewRequest(MethodPost, "/test1/111?id=888", body) req.Header.Set("Content-Type", "application/json") res, err := app.Test(req) require.NoError(t, err) require.Equal(t, 200, res.StatusCode) } // go test -v -run=^$ -bench=Benchmark_Bind_All -benchmem -count=4 func BenchmarkBind_All(b *testing.B) { type User struct { SessionID string `json:"session_id" cookie:"session_id"` Name string `query:"name" json:"name" form:"name"` Email string `json:"email" form:"email"` Role string `header:"X-User-Role"` ID int `uri:"id" query:"id" json:"id" form:"id"` } app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) config := &RequestConfig{ ContentType: MIMEApplicationJSON, Body: []byte(`{"name":"john", "email": "john@doe.com", "session_id": "abc1234", "id": 1}`), Headers: map[string]string{ "X-User-Role": "admin", }, Cookies: map[string]string{ "session_id": "abc123", }, Query: "id=1&name=john", } bind := &Bind{ ctx: c, } b.ResetTimer() for i := 0; i < b.N; i++ { user := &User{} config.ApplyTo(c) if err := bind.All(user); err != nil { b.Fatalf("unexpected error: %v", err) } } }