From d87065f5f2e81e47642da8bcb4d81ea088ca88d5 Mon Sep 17 00:00:00 2001 From: Iliya Date: Mon, 12 Jun 2023 09:21:57 +0330 Subject: [PATCH] =?UTF-8?q?=20=F0=9F=9A=80=20FEATURE:=20add=20queries=20fu?= =?UTF-8?q?nction=20(#2475)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀 FEATURE: add queries method * 📚 DOCS: add documents for queries method. * 🩹 Fix: fix wrap error returned from Queries function * 🚨 tests: add url encoded tests * 🔥 feature: add url encoded support for queries * 🩹 Fix: fix wrap error returned from Queries function * ♻️ Refactor: change error message of url.QueryUnescape * ♻️ Refactor: refactor of mapping key and value queries * 🚨 Test: Validate to fail parse queries * 🚨 Test: Add benchmark test for Queries * 🩹 Fix: remove parsing for encoded urls * ♻️ Refactor: change string function to c.app.getString fucntion Co-authored-by: cmd777 <83428931+cmd777@users.noreply.github.com> * ♻️ Refactor: change name of benchamark function ctx queries Co-authored-by: leonklingele * ♻️ Refactor: remove empty lines Co-authored-by: leonklingele * Revert "♻️ Refactor: change string function to c.app.getString fucntion" This reverts commit 28febf9e602bb13f0761169c26f264e4687da660. * 📚 Docs: add documents for queries method * 🚨 Tests: add more tests for queries function * ♻️ Refactor: change string function to c.app.getString function * 🚨 Tests: add more test for queries function * 📚 Docs: add more documents to queries function --------- Co-authored-by: cmd777 <83428931+cmd777@users.noreply.github.com> Co-authored-by: leonklingele --- ctx.go | 29 ++++++++++++++++++++ ctx_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/api/ctx.md | 64 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) diff --git a/ctx.go b/ctx.go index 8bb29d3149..9a9694dcf9 100644 --- a/ctx.go +++ b/ctx.go @@ -1054,6 +1054,35 @@ func (c *Ctx) Query(key string, defaultValue ...string) string { return defaultString(c.app.getString(c.fasthttp.QueryArgs().Peek(key)), defaultValue) } +// Queries returns a map of query parameters and their values. +// +// GET /?name=alex&wanna_cake=2&id= +// Queries()["name"] == "alex" +// Queries()["wanna_cake"] == "2" +// Queries()["id"] == "" +// +// GET /?field1=value1&field1=value2&field2=value3 +// Queries()["field1"] == "value2" +// Queries()["field2"] == "value3" +// +// GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 +// Queries()["list_a"] == "3" +// Queries()["list_b[]"] == "3" +// Queries()["list_c"] == "1,2,3" +// +// GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending +// Queries()["filters.author.name"] == "John" +// Queries()["filters.category.name"] == "Technology" +// Queries()["filters[customer][name]"] == "Alice" +// Queries()["filters[status]"] == "pending" +func (c *Ctx) Queries() map[string]string { + m := make(map[string]string, c.Context().QueryArgs().Len()) + c.Context().QueryArgs().VisitAll(func(key, value []byte) { + m[c.app.getString(key)] = c.app.getString(value) + }) + return m +} + // QueryInt returns integer value of key string parameter in the url. // Default to empty or invalid key is 0. // diff --git a/ctx_test.go b/ctx_test.go index c15b6d0892..38e83ca9e2 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3842,6 +3842,78 @@ func Benchmark_Ctx_SendString_B(b *testing.B) { utils.AssertEqual(b, []byte("Hello, world!"), c.Response().Body()) } +// go test -run Test_Ctx_Queries -v +func Test_Ctx_Queries(t *testing.T) { + t.Parallel() + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + + c.Request().SetBody([]byte(``)) + c.Request().Header.SetContentType("") + c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1&field1=value1&field1=value2&field2=value3&list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3") + + queries := c.Queries() + utils.AssertEqual(t, "1", queries["id"]) + utils.AssertEqual(t, "tom", queries["name"]) + utils.AssertEqual(t, "basketball,football", queries["hobby"]) + utils.AssertEqual(t, "milo,coke,pepsi", queries["favouriteDrinks"]) + utils.AssertEqual(t, "", queries["alloc"]) + utils.AssertEqual(t, "1", queries["no"]) + utils.AssertEqual(t, "value2", queries["field1"]) + utils.AssertEqual(t, "value3", queries["field2"]) + utils.AssertEqual(t, "3", queries["list_a"]) + utils.AssertEqual(t, "3", queries["list_b[]"]) + utils.AssertEqual(t, "1,2,3", queries["list_c"]) + + c.Request().URI().SetQueryString("filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending") + + queries = c.Queries() + utils.AssertEqual(t, "John", queries["filters.author.name"]) + utils.AssertEqual(t, "Technology", queries["filters.category.name"]) + utils.AssertEqual(t, "Alice", queries["filters[customer][name]"]) + utils.AssertEqual(t, "pending", queries["filters[status]"]) + + c.Request().URI().SetQueryString("tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits") + + queries = c.Queries() + utils.AssertEqual(t, "apple,orange,banana", queries["tags"]) + utils.AssertEqual(t, "apple,orange,banana", queries["filters[tags]"]) + utils.AssertEqual(t, "fruits", queries["filters[category][name]"]) + utils.AssertEqual(t, "apple,orange,banana", queries["filters.tags"]) + utils.AssertEqual(t, "fruits", queries["filters.category.name"]) + + c.Request().URI().SetQueryString("filters[tags][0]=apple&filters[tags][1]=orange&filters[tags][2]=banana&filters[category][name]=fruits") + + queries = c.Queries() + utils.AssertEqual(t, "apple", queries["filters[tags][0]"]) + utils.AssertEqual(t, "orange", queries["filters[tags][1]"]) + utils.AssertEqual(t, "banana", queries["filters[tags][2]"]) + utils.AssertEqual(t, "fruits", queries["filters[category][name]"]) +} + +// go test -v -run=^$ -bench=Benchmark_Ctx_Queries -benchmem -count=4 +func Benchmark_Ctx_Queries(b *testing.B) { + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + b.ReportAllocs() + b.ResetTimer() + c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") + + var queries map[string]string + for n := 0; n < b.N; n++ { + queries = c.Queries() + } + + utils.AssertEqual(b, "1", queries["id"]) + utils.AssertEqual(b, "tom", queries["name"]) + utils.AssertEqual(b, "basketball,football", queries["hobby"]) + utils.AssertEqual(b, "milo,coke,pepsi", queries["favouriteDrinks"]) + utils.AssertEqual(b, "", queries["alloc"]) + utils.AssertEqual(b, "1", queries["no"]) +} + // go test -run Test_Ctx_QueryParser -v func Test_Ctx_QueryParser(t *testing.T) { t.Parallel() diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 5286a19408..5c58bd92e0 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -1094,6 +1094,70 @@ app.Get("/", func(c *fiber.Ctx) error { > _Returned value is only valid within the handler. Do not store any references. > Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +## Queries + +Queries is a function that returns an object containing a property for each query string parameter in the route. + +```go title="Signature" +func (c *Ctx) Queries() (map[string]string, error) +``` + +```go title="Example" +// GET http://example.com/?name=alex&want_pizza=false&id= + +app.Get("/", func(c *fiber.Ctx) error { +m := c.Queries() +m["name"] // "alex" +m["want_pizza"] // "false" +m["id"] // "" +// ... +}) +``` + +```go title="Example" +// GET http://example.com/?field1=value1&field1=value2&field2=value3 + +app.Get("/", func (c *fiber.Ctx) error { + m := c.Queries() + m["field1"] // "value2" + m["field2"] // value3 +}) +``` + +```go title="Example" +// GET http://example.com/?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 + +app.Get("/", func(c *fiber.Ctx) error { + m := c.Queries() + m["list_a"] // "3" + m["list_b[]"] // "3" + m["list_c"] // "1,2,3" +}) +``` + +```go title="Example" +// GET /api/posts?filters.author.name=John&filters.category.name=Technology + +app.Get("/", func(c *fiber.Ctx) error { + m := c.Queies() + m["filters.author.name"] // John + m["filters.category.name"] // Technology + }) +``` + +```go title="Example" +// GET /api/posts?tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits + +app.Get("/", func(c *fiber.Ctx) error { + m := c.Queries() + m["tags"] // apple,orange,banana + m["filters[tags]"] // apple,orange,banana + m["filters[category][name]"] // fruits + m["filters.tags"] // apple,orange,banana + m["filters.category.name"] // fruits +}) +``` + ## QueryBool This property is an object containing a property for each query boolean parameter in the route, you could pass an optional default value that will be returned if the query key does not exist.