From 596c7776a0c69c4ee9033f59e0a4c2a7cd951b3b Mon Sep 17 00:00:00 2001 From: rock Date: Sun, 3 Jan 2021 13:23:18 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=94=A5=20add=20function=20to=20overid?= =?UTF-8?q?e=20form=20decoder=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔥 Feature : SetBodyParserDecoder map all option to form decoder, use with BodyParserConfig, BodyParserType 🚨 Test : Test_Ctx_BodyParser_WithSetBodyParserDecoder --- ctx.go | 31 ++++++++++++++++++++++++++++++ ctx_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/ctx.go b/ctx.go index 961e0b7efc..1721affcac 100644 --- a/ctx.go +++ b/ctx.go @@ -77,6 +77,21 @@ type Views interface { Render(io.Writer, string, interface{}, ...string) error } +// BodyParserType require two element, type and converter for register. +// Use BodyParserType with BodyParser for parsing custom type in form data. +type BodyParserType struct { + Customtype interface{} + Converter func(string) reflect.Value +} + +// BodyParserConfig form decoder config for SetBodyParserDecoder +type BodyParserConfig struct { + IgnoreUnknownKeys bool + SetAliasTag string + BodyParserType []BodyParserType + ZeroEmpty bool +} + // AcquireCtx retrieves a new Ctx from the pool. func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx { c := app.pool.Get().(*Ctx) @@ -244,6 +259,22 @@ var decoderPool = &sync.Pool{New: func() interface{} { return decoder }} +// SetBodyParserDecoder allow globally change the option of form decoder, update decoderPool +func SetBodyParserDecoder(bodyParserConfig BodyParserConfig) { + decoderPool = &sync.Pool{New: func() interface{} { + var decoder = schema.NewDecoder() + decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) + if bodyParserConfig.SetAliasTag != "" { + decoder.SetAliasTag(bodyParserConfig.SetAliasTag) + } + for _, v := range bodyParserConfig.BodyParserType { + decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) + } + decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) + return decoder + }} +} + // BodyParser binds the request body to a struct. // It supports decoding the following content types based on the Content-Type header: // application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data diff --git a/ctx_test.go b/ctx_test.go index cd22f51b4c..f37175f9b6 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -339,6 +339,60 @@ func Test_Ctx_BodyParser(t *testing.T) { testDecodeParserError(MIMEMultipartForm+`;boundary="b"`, "--b") } +// go test -run Test_Ctx_BodyParser_WithSetBodyParserDecoder +func Test_Ctx_BodyParser_WithSetBodyParserDecoder(t *testing.T) { + type CustomTime time.Time + + var 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 := BodyParserType{ + Customtype: CustomTime{}, + Converter: timeConverter, + } + + SetBodyParserDecoder(BodyParserConfig{ + IgnoreUnknownKeys: true, + BodyParserType: []BodyParserType{customTime}, + ZeroEmpty: true, + SetAliasTag: "form", + }) + + t.Parallel() + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + + 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", + } + utils.AssertEqual(t, nil, c.BodyParser(&d)) + date := fmt.Sprintf("%v", d.Date) + fmt.Println(date, d.Title, d.Body) + utils.AssertEqual(t, "{0 63743587200 }", date) + utils.AssertEqual(t, "", d.Title) + utils.AssertEqual(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_Ctx_BodyParser_JSON -benchmem -count=4 func Benchmark_Ctx_BodyParser_JSON(b *testing.B) { app := New() From e7e9dca1c6d28e76b7d5c994b4c7d9b5975621f4 Mon Sep 17 00:00:00 2001 From: Rock Date: Thu, 30 Sep 2021 23:58:12 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=94=A5=20Use=20decoder=20builder=20fu?= =?UTF-8?q?nction=20with=20default=20setting=20on=20init=20decoderPool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ctx.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ctx.go b/ctx.go index 1721affcac..2900135324 100644 --- a/ctx.go +++ b/ctx.go @@ -254,27 +254,32 @@ func (c *Ctx) Body() []byte { // decoderPool helps to improve BodyParser's and QueryParser's performance var decoderPool = &sync.Pool{New: func() interface{} { - var decoder = schema.NewDecoder() - decoder.IgnoreUnknownKeys(true) - return decoder + return decoderBuilder(BodyParserConfig{ + IgnoreUnknownKeys: true, + ZeroEmpty: true, + }) }} // SetBodyParserDecoder allow globally change the option of form decoder, update decoderPool func SetBodyParserDecoder(bodyParserConfig BodyParserConfig) { decoderPool = &sync.Pool{New: func() interface{} { - var decoder = schema.NewDecoder() - decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) - if bodyParserConfig.SetAliasTag != "" { - decoder.SetAliasTag(bodyParserConfig.SetAliasTag) - } - for _, v := range bodyParserConfig.BodyParserType { - decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) - } - decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) - return decoder + return decoderBuilder(bodyParserConfig) }} } +func decoderBuilder(bodyParserConfig BodyParserConfig) interface{} { + var decoder = schema.NewDecoder() + decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) + if bodyParserConfig.SetAliasTag != "" { + decoder.SetAliasTag(bodyParserConfig.SetAliasTag) + } + for _, v := range bodyParserConfig.BodyParserType { + decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) + } + decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) + return decoder +} + // BodyParser binds the request body to a struct. // It supports decoding the following content types based on the Content-Type header: // application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data From ab01a1cc15c9b4e1e80cdb110b1dcd982506cd80 Mon Sep 17 00:00:00 2001 From: Rock Date: Fri, 1 Oct 2021 19:01:14 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20rename=20SetBodyParser?= =?UTF-8?q?Decoder=20to=20SetParserDecoder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BodyParserType > ParserType bodyParserConfig > parserConfig BodyParserConfig > ParserConfig --- ctx.go | 32 ++++++++++++++++---------------- ctx_test.go | 10 +++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ctx.go b/ctx.go index 7e2b23d68b..5c85b95c26 100644 --- a/ctx.go +++ b/ctx.go @@ -86,18 +86,18 @@ type Views interface { Render(io.Writer, string, interface{}, ...string) error } -// BodyParserType require two element, type and converter for register. -// Use BodyParserType with BodyParser for parsing custom type in form data. -type BodyParserType struct { +// ParserType require two element, type and converter for register. +// Use ParserType with BodyParser for parsing custom type in form data. +type ParserType struct { Customtype interface{} Converter func(string) reflect.Value } -// BodyParserConfig form decoder config for SetBodyParserDecoder -type BodyParserConfig struct { +// ParserConfig form decoder config for SetParserDecoder +type ParserConfig struct { IgnoreUnknownKeys bool SetAliasTag string - BodyParserType []BodyParserType + ParserType []ParserType ZeroEmpty bool } @@ -287,29 +287,29 @@ func (c *Ctx) Body() []byte { // decoderPool helps to improve BodyParser's and QueryParser's performance var decoderPool = &sync.Pool{New: func() interface{} { - return decoderBuilder(BodyParserConfig{ + return decoderBuilder(ParserConfig{ IgnoreUnknownKeys: true, ZeroEmpty: true, }) }} -// SetBodyParserDecoder allow globally change the option of form decoder, update decoderPool -func SetBodyParserDecoder(bodyParserConfig BodyParserConfig) { +// SetParserDecoder allow globally change the option of form decoder, update decoderPool +func SetParserDecoder(parserConfig ParserConfig) { decoderPool = &sync.Pool{New: func() interface{} { - return decoderBuilder(bodyParserConfig) + return decoderBuilder(parserConfig) }} } -func decoderBuilder(bodyParserConfig BodyParserConfig) interface{} { +func decoderBuilder(parserConfig ParserConfig) interface{} { var decoder = schema.NewDecoder() - decoder.IgnoreUnknownKeys(bodyParserConfig.IgnoreUnknownKeys) - if bodyParserConfig.SetAliasTag != "" { - decoder.SetAliasTag(bodyParserConfig.SetAliasTag) + decoder.IgnoreUnknownKeys(parserConfig.IgnoreUnknownKeys) + if parserConfig.SetAliasTag != "" { + decoder.SetAliasTag(parserConfig.SetAliasTag) } - for _, v := range bodyParserConfig.BodyParserType { + for _, v := range parserConfig.ParserType { decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) } - decoder.ZeroEmpty(bodyParserConfig.ZeroEmpty) + decoder.ZeroEmpty(parserConfig.ZeroEmpty) return decoder } diff --git a/ctx_test.go b/ctx_test.go index d90eb63f60..78a9caf5a7 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -399,8 +399,8 @@ func Test_Ctx_BodyParser(t *testing.T) { testDecodeParserError(MIMEMultipartForm+`;boundary="b"`, "--b") } -// go test -run Test_Ctx_BodyParser_WithSetBodyParserDecoder -func Test_Ctx_BodyParser_WithSetBodyParserDecoder(t *testing.T) { +// go test -run Test_Ctx_BodyParser_WithSetParserDecoder +func Test_Ctx_BodyParser_WithSetParserDecoder(t *testing.T) { type CustomTime time.Time var timeConverter = func(value string) reflect.Value { @@ -410,14 +410,14 @@ func Test_Ctx_BodyParser_WithSetBodyParserDecoder(t *testing.T) { return reflect.Value{} } - customTime := BodyParserType{ + customTime := ParserType{ Customtype: CustomTime{}, Converter: timeConverter, } - SetBodyParserDecoder(BodyParserConfig{ + SetParserDecoder(ParserConfig{ IgnoreUnknownKeys: true, - BodyParserType: []BodyParserType{customTime}, + ParserType: []ParserType{customTime}, ZeroEmpty: true, SetAliasTag: "form", }) From 14fae6a473245ec3f62a7c152d0efb07070a2817 Mon Sep 17 00:00:00 2001 From: Rock Date: Mon, 4 Oct 2021 14:38:47 +0800 Subject: [PATCH 4/7] Add Test case for QueryParser WithSetParserDecoder --- ctx_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/ctx_test.go b/ctx_test.go index 8e9caa53f0..b206528222 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -410,13 +410,11 @@ func Test_Ctx_BodyParser_WithSetParserDecoder(t *testing.T) { return reflect.Value{} } - customTime := ParserType{ Customtype: CustomTime{}, Converter: timeConverter, } - SetParserDecoder(ParserConfig{ IgnoreUnknownKeys: true, ParserType: []ParserType{customTime}, @@ -445,7 +443,6 @@ func Test_Ctx_BodyParser_WithSetParserDecoder(t *testing.T) { } utils.AssertEqual(t, nil, c.BodyParser(&d)) date := fmt.Sprintf("%v", d.Date) - fmt.Println(date, d.Title, d.Body) utils.AssertEqual(t, "{0 63743587200 }", date) utils.AssertEqual(t, "", d.Title) utils.AssertEqual(t, "New Body", d.Body) @@ -2384,6 +2381,61 @@ func Test_Ctx_QueryParser(t *testing.T) { utils.AssertEqual(t, "name is empty", c.QueryParser(rq).Error()) } +// go test -run Test_Ctx_QueryParser_WithSetParserDecoder -v +func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) { + type CustomTime time.Time + + var 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 := ParserType{ + Customtype: CustomTime{}, + Converter: timeConverter, + } + + SetParserDecoder(ParserConfig{ + IgnoreUnknownKeys: true, + ParserType: []ParserType{customTime}, + ZeroEmpty: true, + SetAliasTag: "query", + }) + + t.Parallel() + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + + type Demo struct { + Date CustomTime `query:"date"` + Title string `query:"title"` + Body string `query:"body"` + } + + c.Request().SetBody([]byte(``)) + c.Request().Header.SetContentType("") + q := new(Demo) + + c.Request().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October") + utils.AssertEqual(t, nil, c.QueryParser(q)) + fmt.Println(q.Date, "q.Date") + utils.AssertEqual(t, "CustomDateTest", q.Title) + date := fmt.Sprintf("%v", q.Date) + utils.AssertEqual(t, "{0 63753609600 }", date) + utils.AssertEqual(t, "October", q.Body) + + c.Request().URI().SetQueryString("date=2021-04-10&title&Body=October") + q = &Demo{ + Title: "Existing title", + Body: "Existing Body", + } + utils.AssertEqual(t, nil, c.QueryParser(q)) + utils.AssertEqual(t, "", q.Title) +} + // go test -run Test_Ctx_QueryParser_Schema -v func Test_Ctx_QueryParser_Schema(t *testing.T) { t.Parallel() From 5e0adde6d58cd20c08cfa31a2c3e91cc98619ef1 Mon Sep 17 00:00:00 2001 From: Rock Date: Mon, 4 Oct 2021 21:23:49 +0800 Subject: [PATCH 5/7] Update for Test_Ctx_QueryParser_WithSetParserDecoder --- ctx_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ctx_test.go b/ctx_test.go index b206528222..59b14b15b1 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -2383,23 +2383,23 @@ func Test_Ctx_QueryParser(t *testing.T) { // go test -run Test_Ctx_QueryParser_WithSetParserDecoder -v func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) { - type CustomTime time.Time + type NonRFCTime time.Time - var timeConverter = func(value string) reflect.Value { + var NonRFCConverter = func(value string) reflect.Value { if v, err := time.Parse("2006-01-02", value); err == nil { return reflect.ValueOf(v) } return reflect.Value{} } - customTime := ParserType{ - Customtype: CustomTime{}, - Converter: timeConverter, + nonRFCTime := ParserType{ + Customtype: NonRFCTime{}, + Converter: NonRFCConverter, } SetParserDecoder(ParserConfig{ IgnoreUnknownKeys: true, - ParserType: []ParserType{customTime}, + ParserType: []ParserType{nonRFCTime}, ZeroEmpty: true, SetAliasTag: "query", }) @@ -2409,15 +2409,15 @@ func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - type Demo struct { - Date CustomTime `query:"date"` + 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(Demo) + q := new(NonRFCTimeInput) c.Request().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October") utils.AssertEqual(t, nil, c.QueryParser(q)) @@ -2428,7 +2428,7 @@ func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) { utils.AssertEqual(t, "October", q.Body) c.Request().URI().SetQueryString("date=2021-04-10&title&Body=October") - q = &Demo{ + q = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", } From 5eef329cd4b93ab79b48b00b22b44e332e3fdffd Mon Sep 17 00:00:00 2001 From: Rock Date: Mon, 4 Oct 2021 23:58:35 +0800 Subject: [PATCH 6/7] Update Test_Ctx_QueryParser_WithSetParserDecoder remove t.Parallel() debug unit test - may close this pull request for cleaner commit --- ctx_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ctx_test.go b/ctx_test.go index 59b14b15b1..fec50cf3fa 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -422,7 +422,7 @@ func Test_Ctx_BodyParser_WithSetParserDecoder(t *testing.T) { SetAliasTag: "form", }) - t.Parallel() + // t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) @@ -2404,7 +2404,7 @@ func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) { SetAliasTag: "query", }) - t.Parallel() + // t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) From 8fd826e4910f54058319d4b0f241f927babd6fe0 Mon Sep 17 00:00:00 2001 From: Rock Date: Tue, 5 Oct 2021 00:04:23 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=A7=B9=20Clean=20Up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ctx_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/ctx_test.go b/ctx_test.go index fec50cf3fa..78c1df464e 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -422,7 +422,6 @@ func Test_Ctx_BodyParser_WithSetParserDecoder(t *testing.T) { SetAliasTag: "form", }) - // t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) @@ -2404,7 +2403,6 @@ func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) { SetAliasTag: "query", }) - // t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c)