Skip to content

Commit

Permalink
🔥 add config to enable splitting by comma in parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
efectn committed Aug 6, 2023
1 parent e91b02b commit 9d9c62e
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 11 deletions.
9 changes: 9 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,15 @@ type Config struct {
//
// Optional. Default: DefaultMethods
RequestMethods []string

// EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.
// For example, you can use it to parse multiple values from a query parameter like this:
// /api?foo=bar,baz == foo[]=bar&foo[]=baz
// If there is an escape character before the comma, parsers won't split it like the example below:
// /api?foo=bar\,baz == foo=bar,baz
//
// Optional. Default: false
EnableSplittingOnParsers bool `json:"enable_splitting_on_parsers"`
}

// Static defines configuration options when defining static assets.
Expand Down
14 changes: 7 additions & 7 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ func (c *Ctx) BodyParser(out interface{}) error {
k, err = parseParamSquareBrackets(k)
}

if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
values := strings.Split(v, ",")
if findNextNonEscapedCharsetPosition(v, []byte(",")) != -1 && equalFieldType(out, reflect.Slice, k) && c.app.config.EnableSplittingOnParsers {
values := splitNonEscaped(v, ",")
for i := 0; i < len(values); i++ {
data[k] = append(data[k], values[i])
}
Expand Down Expand Up @@ -1159,10 +1159,10 @@ func (c *Ctx) QueryParser(out interface{}) error {
k, err = parseParamSquareBrackets(k)
}

if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
values := strings.Split(v, ",")
if findNextNonEscapedCharsetPosition(v, []byte(",")) != -1 && equalFieldType(out, reflect.Slice, k) && c.app.config.EnableSplittingOnParsers {
values := splitNonEscaped(v, ",")
for i := 0; i < len(values); i++ {
data[k] = append(data[k], values[i])
data[k] = append(data[k], RemoveEscapeChar(values[i]))
}
} else {
data[k] = append(data[k], v)
Expand Down Expand Up @@ -1208,8 +1208,8 @@ func (c *Ctx) ReqHeaderParser(out interface{}) error {
k := c.app.getString(key)
v := c.app.getString(val)

if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) {
values := strings.Split(v, ",")
if findNextNonEscapedCharsetPosition(v, []byte(",")) != -1 && equalFieldType(out, reflect.Slice, k) && c.app.config.EnableSplittingOnParsers {
values := splitNonEscaped(v, ",")
for i := 0; i < len(values); i++ {
data[k] = append(data[k], values[i])
}
Expand Down
71 changes: 67 additions & 4 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3917,7 +3917,7 @@ func Benchmark_Ctx_Queries(b *testing.B) {
// go test -run Test_Ctx_QueryParser -v
func Test_Ctx_QueryParser(t *testing.T) {
t.Parallel()
app := New()
app := New(Config{EnableSplittingOnParsers: true})
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Query struct {
Expand All @@ -3937,6 +3937,11 @@ func Test_Ctx_QueryParser(t *testing.T) {
utils.AssertEqual(t, nil, c.QueryParser(q))
utils.AssertEqual(t, 2, len(q.Hobby))

c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball\\,football")
q = new(Query)
utils.AssertEqual(t, nil, c.QueryParser(q))
utils.AssertEqual(t, 1, len(q.Hobby))

c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
q = new(Query)
utils.AssertEqual(t, nil, c.QueryParser(q))
Expand Down Expand Up @@ -3988,6 +3993,29 @@ func Test_Ctx_QueryParser(t *testing.T) {
utils.AssertEqual(t, 2, len(aq.Data))
}

// go test -run Test_Ctx_QueryParser -v
func Test_Ctx_QueryParser_WithoutSplitting(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Query struct {
ID int
Name string
Hobby []string
}

c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
q := new(Query)
utils.AssertEqual(t, nil, c.QueryParser(q))
utils.AssertEqual(t, 1, len(q.Hobby))

c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
q = new(Query)
utils.AssertEqual(t, nil, c.QueryParser(q))
utils.AssertEqual(t, 2, len(q.Hobby))
}

// go test -run Test_Ctx_QueryParser_WithSetParserDecoder -v
func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -4146,7 +4174,7 @@ func Test_Ctx_QueryParser_Schema(t *testing.T) {
// go test -run Test_Ctx_ReqHeaderParser -v
func Test_Ctx_ReqHeaderParser(t *testing.T) {
t.Parallel()
app := New()
app := New(Config{EnableSplittingOnParsers: true})
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Header struct {
Expand All @@ -4170,6 +4198,13 @@ func Test_Ctx_ReqHeaderParser(t *testing.T) {
utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
utils.AssertEqual(t, 3, len(q.Hobby))

c.Request().Header.Del("hobby")
c.Request().Header.Add("Hobby", "golang\\,fiber\\,go,fiber")
q = new(Header)
utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
fmt.Println(q.Hobby)

Check failure on line 4205 in ctx_test.go

View workflow job for this annotation

GitHub Actions / lint

use of `fmt.Println` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)
utils.AssertEqual(t, 2, len(q.Hobby))

empty := new(Header)
c.Request().Header.Del("hobby")
utils.AssertEqual(t, nil, c.QueryParser(empty))
Expand Down Expand Up @@ -4215,6 +4250,34 @@ func Test_Ctx_ReqHeaderParser(t *testing.T) {
utils.AssertEqual(t, "failed to decode: name is empty", c.ReqHeaderParser(rh).Error())
}

// go test -run Test_Ctx_ReqHeaderParser -v
func Test_Ctx_ReqHeaderParser_WithoutSplitting(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Header struct {
ID int
Name string
Hobby []string
}
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)
utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
utils.AssertEqual(t, 1, len(q.Hobby))

c.Request().Header.Del("hobby")
c.Request().Header.Add("Hobby", "golang,fiber,go")
q = new(Header)
utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
utils.AssertEqual(t, 1, len(q.Hobby))
}

// go test -run Test_Ctx_ReqHeaderParser_WithSetParserDecoder -v
func Test_Ctx_ReqHeaderParser_WithSetParserDecoder(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -4439,7 +4502,7 @@ func Benchmark_Ctx_parseQuery(b *testing.B) {

// go test -v -run=^$ -bench=Benchmark_Ctx_QueryParser_Comma -benchmem -count=4
func Benchmark_Ctx_QueryParser_Comma(b *testing.B) {
app := New()
app := New(Config{EnableSplittingOnParsers: true})
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Query struct {
Expand All @@ -4465,7 +4528,7 @@ func Benchmark_Ctx_QueryParser_Comma(b *testing.B) {

// go test -v -run=^$ -bench=Benchmark_Ctx_ReqHeaderParser -benchmem -count=4
func Benchmark_Ctx_ReqHeaderParser(b *testing.B) {
app := New()
app := New(Config{EnableSplittingOnParsers: true})
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type ReqHeader struct {
Expand Down
11 changes: 11 additions & 0 deletions path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ func Test_Utils_RemoveEscapeChar(t *testing.T) {
utils.AssertEqual(t, "noEscapeChar", res)
}

func Benchmark_Utils_RemoveEscapeChar(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
var res string
for n := 0; n < b.N; n++ {
res = RemoveEscapeChar(":test\\:bla")
}

utils.AssertEqual(b, ":test:bla", res)
}

// go test -race -run Test_Path_matchParams
func Benchmark_Path_matchParams(t *testing.B) {
var ctxParams [maxParams]string
Expand Down

0 comments on commit 9d9c62e

Please sign in to comment.