From b5cc3a610572aa70de93795ca2e872db1cd11def Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 20 Dec 2024 22:19:07 +0300 Subject: [PATCH] improve test coverage --- binder/binder_test.go | 28 ++++++ binder/cbor_test.go | 90 ++++++++++++++++++ binder/cookie.go | 4 +- binder/cookie_test.go | 84 +++++++++++++++++ binder/form.go | 8 +- binder/form_test.go | 182 +++++++++++++++++++++++++++++++++++++ binder/header_test.go | 74 +++++++++++++++ binder/json_test.go | 68 ++++++++++++++ binder/mapping.go | 29 +++--- binder/mapping_test.go | 82 +++++++++++++++++ binder/query.go | 4 +- binder/query_test.go | 73 +++++++++++++++ binder/resp_header_test.go | 76 ++++++++++++++++ binder/uri_test.go | 75 +++++++++++++++ binder/xml_test.go | 134 +++++++++++++++++++++++++++ 15 files changed, 991 insertions(+), 20 deletions(-) create mode 100644 binder/binder_test.go create mode 100644 binder/cbor_test.go create mode 100644 binder/cookie_test.go create mode 100644 binder/form_test.go create mode 100644 binder/header_test.go create mode 100644 binder/json_test.go create mode 100644 binder/query_test.go create mode 100644 binder/resp_header_test.go create mode 100644 binder/uri_test.go create mode 100644 binder/xml_test.go diff --git a/binder/binder_test.go b/binder/binder_test.go new file mode 100644 index 0000000000..d078ed02c6 --- /dev/null +++ b/binder/binder_test.go @@ -0,0 +1,28 @@ +package binder + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_GetAndPutToThePool(t *testing.T) { + t.Parallel() + + // Panics in case we get from another pool + require.Panics(t, func() { + _ = GetFromThePool[*HeaderBinding](&CookieBinderPool) + }) + + // We get from the pool + binder := GetFromThePool[*HeaderBinding](&HeaderBinderPool) + PutToThePool(&HeaderBinderPool, binder) + + _ = GetFromThePool[*RespHeaderBinding](&RespHeaderBinderPool) + _ = GetFromThePool[*QueryBinding](&QueryBinderPool) + _ = GetFromThePool[*FormBinding](&FormBinderPool) + _ = GetFromThePool[*URIBinding](&URIBinderPool) + _ = GetFromThePool[*XMLBinding](&XMLBinderPool) + _ = GetFromThePool[*JSONBinding](&JSONBinderPool) + _ = GetFromThePool[*CBORBinding](&CBORBinderPool) +} diff --git a/binder/cbor_test.go b/binder/cbor_test.go new file mode 100644 index 0000000000..74a94500f7 --- /dev/null +++ b/binder/cbor_test.go @@ -0,0 +1,90 @@ +package binder + +import ( + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/stretchr/testify/require" +) + +func Test_CBORBinder_Bind(t *testing.T) { + t.Parallel() + + b := &CBORBinding{ + CBORDecoder: cbor.Unmarshal, + } + require.Equal(t, "cbor", b.Name()) + + type Post struct { + Title string `cbor:"title"` + } + + type User struct { + Name string `cbor:"name"` + Names []string `cbor:"names"` + Age int `cbor:"age"` + + Posts []Post `cbor:"posts"` + } + var user User + + wantedUser := User{ + Name: "john", + Names: []string{ + "john", + "doe", + }, + Age: 42, + Posts: []Post{ + {Title: "post1"}, + {Title: "post2"}, + {Title: "post3"}, + }, + } + + body, err := cbor.Marshal(wantedUser) + require.NoError(t, err) + + err = b.Bind(body, &user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Len(t, user.Posts, 3) + require.Equal(t, "post1", user.Posts[0].Title) + require.Equal(t, "post2", user.Posts[1].Title) + require.Equal(t, "post3", user.Posts[2].Title) + require.Contains(t, user.Names, "john") + require.Contains(t, user.Names, "doe") +} + +func Benchmark_CBORBinder_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &CBORBinding{ + CBORDecoder: cbor.Unmarshal, + } + + type User struct { + Name string `cbor:"name"` + Age int `cbor:"age"` + } + + var user User + wantedUser := User{ + Name: "john", + Age: 42, + } + + body, err := cbor.Marshal(wantedUser) + require.NoError(b, err) + + for i := 0; i < b.N; i++ { + err = binder.Bind(body, &user) + } + + require.NoError(b, err) + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) +} diff --git a/binder/cookie.go b/binder/cookie.go index 1a9715cf93..00e228ac0d 100644 --- a/binder/cookie.go +++ b/binder/cookie.go @@ -19,11 +19,11 @@ func (*CookieBinding) Name() string { } // Bind parses the request cookie and returns the result. -func (b *CookieBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { +func (b *CookieBinding) Bind(req *fasthttp.Request, out any) error { data := make(map[string][]string) var err error - reqCtx.Request.Header.VisitAllCookie(func(key, val []byte) { + req.Header.VisitAllCookie(func(key, val []byte) { if err != nil { return } diff --git a/binder/cookie_test.go b/binder/cookie_test.go new file mode 100644 index 0000000000..657df24498 --- /dev/null +++ b/binder/cookie_test.go @@ -0,0 +1,84 @@ +package binder + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" +) + +func Test_CookieBinder_Bind(t *testing.T) { + t.Parallel() + + b := &CookieBinding{ + EnableSplitting: true, + } + require.Equal(t, "cookie", b.Name()) + + type Post struct { + Title string `form:"title"` + } + + type User struct { + Name string `form:"name"` + Names []string `form:"names"` + Age int `form:"age"` + + Posts []Post `form:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + + req.Header.SetCookie("name", "john") + req.Header.SetCookie("names", "john,doe") + req.Header.SetCookie("age", "42") + + t.Cleanup(func() { + fasthttp.ReleaseRequest(req) + }) + + err := b.Bind(req, &user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Contains(t, user.Names, "john") + require.Contains(t, user.Names, "doe") +} + +func Benchmark_CookieBinder_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &CookieBinding{ + EnableSplitting: true, + } + + type User struct { + Name string `query:"name"` + Age int `query:"age"` + + Posts []string `query:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + + req.Header.SetCookie("name", "john") + req.Header.SetCookie("age", "42") + req.Header.SetCookie("posts", "post1,post2,post3") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = binder.Bind(req, &user) + } + + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) + require.Len(b, user.Posts, 3) + require.Contains(b, user.Posts, "post1") + require.Contains(b, user.Posts, "post2") + require.Contains(b, user.Posts, "post3") +} diff --git a/binder/form.go b/binder/form.go index 37c9a4e5b6..ea7ec0a40e 100644 --- a/binder/form.go +++ b/binder/form.go @@ -19,11 +19,11 @@ func (*FormBinding) Name() string { } // Bind parses the request body and returns the result. -func (b *FormBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { +func (b *FormBinding) Bind(req *fasthttp.Request, out any) error { data := make(map[string][]string) var err error - reqCtx.PostArgs().VisitAll(func(key, val []byte) { + req.PostArgs().VisitAll(func(key, val []byte) { if err != nil { return } @@ -53,8 +53,8 @@ func (b *FormBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { } // BindMultipart parses the request body and returns the result. -func (b *FormBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error { - data, err := reqCtx.MultipartForm() +func (b *FormBinding) BindMultipart(req *fasthttp.Request, out any) error { + data, err := req.MultipartForm() if err != nil { return err } diff --git a/binder/form_test.go b/binder/form_test.go new file mode 100644 index 0000000000..b6715a435c --- /dev/null +++ b/binder/form_test.go @@ -0,0 +1,182 @@ +package binder + +import ( + "bytes" + "fmt" + "mime/multipart" + "testing" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" +) + +func Test_FormBinder_Bind(t *testing.T) { + t.Parallel() + + b := &FormBinding{ + EnableSplitting: true, + } + require.Equal(t, "form", b.Name()) + + type Post struct { + Title string `form:"title"` + } + + type User struct { + Name string `form:"name"` + Names []string `form:"names"` + Age int `form:"age"` + + Posts []Post `form:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + req.SetBodyString("name=john&names=john,doe&age=42&posts[0][title]=post1&posts[1][title]=post2&posts[2][title]=post3") + req.Header.SetContentType("application/x-www-form-urlencoded") + + t.Cleanup(func() { + fasthttp.ReleaseRequest(req) + }) + + err := b.Bind(req, &user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Len(t, user.Posts, 3) + require.Equal(t, "post1", user.Posts[0].Title) + require.Equal(t, "post2", user.Posts[1].Title) + require.Equal(t, "post3", user.Posts[2].Title) + require.Contains(t, user.Names, "john") + require.Contains(t, user.Names, "doe") +} + +func Benchmark_FormBinder_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &QueryBinding{ + EnableSplitting: true, + } + + type User struct { + Name string `query:"name"` + Age int `query:"age"` + + Posts []string `query:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + req.URI().SetQueryString("name=john&age=42&posts=post1,post2,post3") + req.Header.SetContentType("application/x-www-form-urlencoded") + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = binder.Bind(req, &user) + } + + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) + require.Len(b, user.Posts, 3) +} + +func Test_FormBinder_BindMultipart(t *testing.T) { + t.Parallel() + + b := &FormBinding{ + EnableSplitting: true, + } + require.Equal(t, "form", b.Name()) + + type Post struct { + Title string `form:"title"` + } + + type User struct { + Name string `form:"name"` + Names []string `form:"names"` + Age int `form:"age"` + + Posts []Post `form:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + + buf := &bytes.Buffer{} + mw := multipart.NewWriter(buf) + + require.NoError(t, mw.WriteField("name", "john")) + require.NoError(t, mw.WriteField("names", "john")) + require.NoError(t, mw.WriteField("names", "doe")) + require.NoError(t, mw.WriteField("age", "42")) + // require.NoError(t, mw.WriteField("posts[0][title]", "post1")) + // require.NoError(t, mw.WriteField("posts[1][title]", "post2")) + // require.NoError(t, mw.WriteField("posts[2][title]", "post3")) + require.NoError(t, mw.Close()) + + req.Header.SetContentType(mw.FormDataContentType()) + req.SetBody(buf.Bytes()) + + t.Cleanup(func() { + fasthttp.ReleaseRequest(req) + }) + + err := b.BindMultipart(req, &user) + fmt.Print(user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + // require.Len(t, user.Posts, 3) + // require.Equal(t, "post1", user.Posts[0].Title) + // require.Equal(t, "post2", user.Posts[1].Title) + // require.Equal(t, "post3", user.Posts[2].Title) + require.Contains(t, user.Names, "john") + require.Contains(t, user.Names, "doe") +} + +func Benchmark_FormBinder_BindMultipart(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &FormBinding{ + EnableSplitting: true, + } + + type User struct { + Name string `query:"name"` + Age int `query:"age"` + + Posts []string `query:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + + buf := &bytes.Buffer{} + mw := multipart.NewWriter(buf) + + require.NoError(b, mw.WriteField("name", "john")) + require.NoError(b, mw.WriteField("age", "42")) + require.NoError(b, mw.WriteField("posts", "post1")) + require.NoError(b, mw.WriteField("posts", "post2")) + require.NoError(b, mw.WriteField("posts", "post3")) + require.NoError(b, mw.Close()) + + req.Header.SetContentType(mw.FormDataContentType()) + req.SetBody(buf.Bytes()) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = binder.BindMultipart(req, &user) + } + + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) + require.Len(b, user.Posts, 3) +} diff --git a/binder/header_test.go b/binder/header_test.go new file mode 100644 index 0000000000..195fe6bbf9 --- /dev/null +++ b/binder/header_test.go @@ -0,0 +1,74 @@ +package binder + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" +) + +func Test_HeaderBinder_Bind(t *testing.T) { + t.Parallel() + + b := &HeaderBinding{ + EnableSplitting: true, + } + require.Equal(t, "header", b.Name()) + + type User struct { + Name string `header:"name"` + Names []string `header:"names"` + Age int `header:"age"` + + Posts []string `header:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + req.Header.Set("name", "john") + req.Header.Set("names", "john,doe") + req.Header.Set("age", "42") + req.Header.Set("posts", "post1,post2,post3") + + t.Cleanup(func() { + fasthttp.ReleaseRequest(req) + }) + + err := b.Bind(req, &user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Len(t, user.Posts, 3) + require.Equal(t, "post1", user.Posts[0]) + require.Equal(t, "post2", user.Posts[1]) + require.Equal(t, "post3", user.Posts[2]) + require.Contains(t, user.Names, "john") + require.Contains(t, user.Names, "doe") +} + +func Benchmark_HeaderBinder_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &HeaderBinding{ + EnableSplitting: true, + } + + type User struct { + Name string `query:"name"` + Age int `query:"age"` + + Posts []string `query:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + req.Header.Set("name", "john") + req.Header.Set("age", "42") + req.Header.Set("posts", "post1,post2,post3") + + for i := 0; i < b.N; i++ { + _ = binder.Bind(req, &user) + } +} diff --git a/binder/json_test.go b/binder/json_test.go new file mode 100644 index 0000000000..8c3618ef89 --- /dev/null +++ b/binder/json_test.go @@ -0,0 +1,68 @@ +package binder + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_JSON_Binding_Bind(t *testing.T) { + t.Parallel() + + b := &JSONBinding{ + JSONDecoder: json.Unmarshal, + } + require.Equal(t, "json", b.Name()) + + type Post struct { + Title string `json:"title"` + } + + type User struct { + Name string `json:"name"` + Age int `json:"age"` + + Posts []Post `json:"posts"` + } + var user User + + err := b.Bind([]byte(`{"name":"john","age":42,"posts":[{"title":"post1"},{"title":"post2"},{"title":"post3"}]}`), &user) + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Len(t, user.Posts, 3) + require.Equal(t, "post1", user.Posts[0].Title) + require.Equal(t, "post2", user.Posts[1].Title) + require.Equal(t, "post3", user.Posts[2].Title) +} + +func Benchmark_JSON_Binding_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &JSONBinding{ + JSONDecoder: json.Unmarshal, + } + + type User struct { + Name string `json:"name"` + Age int `json:"age"` + + Posts []string `json:"posts"` + } + + var user User + var err error + for i := 0; i < b.N; i++ { + err = binder.Bind([]byte(`{"name":"john","age":42,"posts":["post1","post2","post3"]}`), &user) + } + + require.NoError(b, err) + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) + require.Len(b, user.Posts, 3) + require.Equal(b, "post1", user.Posts[0]) + require.Equal(b, "post2", user.Posts[1]) + require.Equal(b, "post3", user.Posts[2]) +} diff --git a/binder/mapping.go b/binder/mapping.go index 015c7fd6d2..d9623f8953 100644 --- a/binder/mapping.go +++ b/binder/mapping.go @@ -105,10 +105,11 @@ func parseToStruct(aliasTag string, out any, data map[string][]string) error { // Parse data into the map // thanks to https://github.com/gin-gonic/gin/blob/master/binding/binding.go func parseToMap(ptr any, data map[string][]string) error { + fmt.Print(ptr) elem := reflect.TypeOf(ptr).Elem() - - // map[string][]string - if elem.Kind() == reflect.Slice { + fmt.Print(elem.Kind()) + switch elem.Kind() { + case reflect.Slice: newMap, ok := ptr.(map[string][]string) if !ok { return ErrMapNotConvertable @@ -117,18 +118,22 @@ func parseToMap(ptr any, data map[string][]string) error { for k, v := range data { newMap[k] = v } + case reflect.String, reflect.Interface: + newMap, ok := ptr.(map[string]string) + if !ok { + return ErrMapNotConvertable + } - return nil - } + fmt.Print(newMap) - // map[string]string - newMap, ok := ptr.(map[string]string) - if !ok { - return ErrMapNotConvertable - } + for k, v := range data { + if len(v) == 0 { + newMap[k] = "" + continue + } - for k, v := range data { - newMap[k] = v[len(v)-1] + newMap[k] = v[len(v)-1] + } } return nil diff --git a/binder/mapping_test.go b/binder/mapping_test.go index e6fc8146f7..4abddc8932 100644 --- a/binder/mapping_test.go +++ b/binder/mapping_test.go @@ -29,6 +29,22 @@ func Test_EqualFieldType(t *testing.T) { require.True(t, equalFieldType(&user, reflect.String, "Address")) require.True(t, equalFieldType(&user, reflect.Int, "AGE")) require.True(t, equalFieldType(&user, reflect.Int, "age")) + + var user2 struct { + User struct { + Name string + Address string `query:"address"` + Age int `query:"AGE"` + } `query:"user"` + } + + require.True(t, equalFieldType(&user2, reflect.String, "user.name")) + require.True(t, equalFieldType(&user2, reflect.String, "user.Name")) + require.True(t, equalFieldType(&user2, reflect.String, "user.address")) + require.True(t, equalFieldType(&user2, reflect.String, "user.Address")) + require.True(t, equalFieldType(&user2, reflect.Int, "user.AGE")) + require.True(t, equalFieldType(&user2, reflect.Int, "user.age")) + } func Test_ParseParamSquareBrackets(t *testing.T) { @@ -97,3 +113,69 @@ func Test_ParseParamSquareBrackets(t *testing.T) { }) } } + +func Test_parseToMap(t *testing.T) { + inputMap := map[string][]string{ + "key1": {"value1", "value2"}, + "key2": {"value3"}, + "key3": {"value4"}, + } + + // Test map[string]string + m := make(map[string]string) + err := parseToMap(m, inputMap) + require.NoError(t, err) + + require.Equal(t, "value2", m["key1"]) + require.Equal(t, "value3", m["key2"]) + require.Equal(t, "value4", m["key3"]) + + // Test map[string][]string + m2 := make(map[string][]string) + err = parseToMap(m2, inputMap) + require.NoError(t, err) + + require.Len(t, m2["key1"], 2) + require.Contains(t, m2["key1"], "value1") + require.Contains(t, m2["key1"], "value2") + require.Len(t, m2["key2"], 1) + require.Len(t, m2["key3"], 1) + + // Test map[string]interface{} + m3 := make(map[string]interface{}) + err = parseToMap(m3, inputMap) + require.ErrorIs(t, err, ErrMapNotConvertable) + +} + +func Test_FilterFlags(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + input: "text/javascript; charset=utf-8", + expected: "text/javascript", + }, + { + input: "text/javascript", + expected: "text/javascript", + }, + + { + input: "text/javascript; charset=utf-8; foo=bar", + expected: "text/javascript", + }, + { + input: "text/javascript charset=utf-8", + expected: "text/javascript", + }, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := FilterFlags(tt.input) + require.Equal(t, tt.expected, result) + }) + } +} diff --git a/binder/query.go b/binder/query.go index f693076df5..0811b3d8d2 100644 --- a/binder/query.go +++ b/binder/query.go @@ -19,11 +19,11 @@ func (*QueryBinding) Name() string { } // Bind parses the request query and returns the result. -func (b *QueryBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { +func (b *QueryBinding) Bind(reqCtx *fasthttp.Request, out any) error { data := make(map[string][]string) var err error - reqCtx.QueryArgs().VisitAll(func(key, val []byte) { + reqCtx.URI().QueryArgs().VisitAll(func(key, val []byte) { if err != nil { return } diff --git a/binder/query_test.go b/binder/query_test.go new file mode 100644 index 0000000000..e9183a40c0 --- /dev/null +++ b/binder/query_test.go @@ -0,0 +1,73 @@ +package binder + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" +) + +func Test_QueryBinder_Bind(t *testing.T) { + t.Parallel() + + b := &QueryBinding{ + EnableSplitting: true, + } + require.Equal(t, "query", b.Name()) + + type Post struct { + Title string `query:"title"` + } + + type User struct { + Name string `query:"name"` + Names []string `query:"names"` + Age int `query:"age"` + + Posts []Post `query:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + req.URI().SetQueryString("name=john&names=john,doe&age=42&posts[0][title]=post1&posts[1][title]=post2&posts[2][title]=post3") + + t.Cleanup(func() { + fasthttp.ReleaseRequest(req) + }) + + err := b.Bind(req, &user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Len(t, user.Posts, 3) + require.Equal(t, "post1", user.Posts[0].Title) + require.Equal(t, "post2", user.Posts[1].Title) + require.Equal(t, "post3", user.Posts[2].Title) + require.Contains(t, user.Names, "john") + require.Contains(t, user.Names, "doe") +} + +func Benchmark_QueryBinder_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &QueryBinding{ + EnableSplitting: true, + } + + type User struct { + Name string `query:"name"` + Age int `query:"age"` + + Posts []string `query:"posts"` + } + var user User + + req := fasthttp.AcquireRequest() + req.URI().SetQueryString("name=john&age=42&posts=post1,post2,post3") + + for i := 0; i < b.N; i++ { + _ = binder.Bind(req, &user) + } +} diff --git a/binder/resp_header_test.go b/binder/resp_header_test.go new file mode 100644 index 0000000000..2805a83caf --- /dev/null +++ b/binder/resp_header_test.go @@ -0,0 +1,76 @@ +package binder + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/valyala/fasthttp" +) + +func Test_RespHeaderBinder_Bind(t *testing.T) { + t.Parallel() + + b := &RespHeaderBinding{ + EnableSplitting: true, + } + require.Equal(t, "respHeader", b.Name()) + + type User struct { + Name string `respHeader:"name"` + Age int `respHeader:"age"` + + Posts []string `respHeader:"posts"` + } + var user User + + resp := fasthttp.AcquireResponse() + resp.Header.Set("name", "john") + resp.Header.Set("age", "42") + resp.Header.Set("posts", "post1,post2,post3") + + t.Cleanup(func() { + fasthttp.ReleaseResponse(resp) + }) + + err := b.Bind(resp, &user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Equal(t, []string{"post1", "post2", "post3"}, user.Posts) +} + +func Benchmark_RespHeaderBinder_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &RespHeaderBinding{ + EnableSplitting: true, + } + + type User struct { + Name string `respHeader:"name"` + Age int `respHeader:"age"` + + Posts []string `respHeader:"posts"` + } + var user User + + resp := fasthttp.AcquireResponse() + resp.Header.Set("name", "john") + resp.Header.Set("age", "42") + resp.Header.Set("posts", "post1,post2,post3") + + b.Cleanup(func() { + fasthttp.ReleaseResponse(resp) + }) + + b.StartTimer() + for i := 0; i < b.N; i++ { + _ = binder.Bind(resp, &user) + } + + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) + require.Equal(b, []string{"post1", "post2", "post3"}, user.Posts) +} diff --git a/binder/uri_test.go b/binder/uri_test.go new file mode 100644 index 0000000000..e5308d5ce7 --- /dev/null +++ b/binder/uri_test.go @@ -0,0 +1,75 @@ +package binder + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_URIBinding_Bind(t *testing.T) { + t.Parallel() + + b := &URIBinding{} + require.Equal(t, "uri", b.Name()) + + type User struct { + Name string `uri:"name"` + Age int `uri:"age"` + + Posts []string `uri:"posts"` + } + var user User + + paramsKey := []string{"name", "age", "posts"} + paramsVals := []string{"john", "42", "post1,post2,post3"} + paramsFunc := func(key string, defaultValue ...string) string { + for i, k := range paramsKey { + if k == key { + return paramsVals[i] + } + } + + return "" + } + + err := b.Bind(paramsKey, paramsFunc, &user) + + fmt.Println(user) + + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Equal(t, []string{"post1,post2,post3"}, user.Posts) +} + +func Benchmark_URIBinding_Bind(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + binder := &URIBinding{} + + type User struct { + Name string `uri:"name"` + Age int `uri:"age"` + + Posts []string `uri:"posts"` + } + var user User + + paramsKey := []string{"name", "age", "posts"} + paramsVals := []string{"john", "42", "post1,post2,post3"} + paramsFunc := func(key string, defaultValue ...string) string { + for i, k := range paramsKey { + if k == key { + return paramsVals[i] + } + } + + return "" + } + + for i := 0; i < b.N; i++ { + _ = binder.Bind(paramsKey, paramsFunc, &user) + } +} diff --git a/binder/xml_test.go b/binder/xml_test.go new file mode 100644 index 0000000000..33ba65a67e --- /dev/null +++ b/binder/xml_test.go @@ -0,0 +1,134 @@ +package binder + +import ( + "encoding/xml" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_XMLBinding_Bind(t *testing.T) { + t.Parallel() + + b := &XMLBinding{ + XMLDecoder: xml.Unmarshal, + } + require.Equal(t, "xml", b.Name()) + + type Posts struct { + XMLName xml.Name `xml:"post"` + Title string `xml:"title"` + } + + type User struct { + Name string `xml:"name"` + Age int `xml:"age"` + + Posts []Posts `xml:"posts>post"` + + // This field should be ignored + Ignore string `xml:"-"` + } + + user := new(User) + err := b.Bind([]byte(` + + john + 42 + ignore + + + post1 + + + post2 + + + + `), user) + require.NoError(t, err) + require.Equal(t, "john", user.Name) + require.Equal(t, 42, user.Age) + require.Empty(t, user.Ignore) + + require.Len(t, user.Posts, 2) + require.Equal(t, "post1", user.Posts[0].Title) + require.Equal(t, "post2", user.Posts[1].Title) +} + +func Test_XMLBinding_Bind_error(t *testing.T) { + t.Parallel() + b := &XMLBinding{ + XMLDecoder: xml.Unmarshal, + } + + type User struct { + Name string `xml:"name"` + Age int `xml:"age"` + } + + user := new(User) + err := b.Bind([]byte(` + + john + 42 + unknown + post"` + } + + user := new(User) + data := []byte(` + + john + 42 + ignore + + + post1 + + + post2 + + + + `) + + b.StartTimer() + + for i := 0; i < b.N; i++ { + _ = binder.Bind(data, user) + } + + user = new(User) + err := binder.Bind(data, user) + require.NoError(b, err) + + require.Equal(b, "john", user.Name) + require.Equal(b, 42, user.Age) + + require.Len(b, user.Posts, 2) + require.Equal(b, "post1", user.Posts[0].Title) + require.Equal(b, "post2", user.Posts[1].Title) +}