From c46fc838a9320adfc6532b1b543e903c48b3b4cb Mon Sep 17 00:00:00 2001 From: Ujjwal Goyal Date: Sun, 7 Mar 2021 00:08:10 +0530 Subject: [PATCH] tpl: Allow 'Querify' to take lone slice/interface argument Querify can now take a lone string/interface slice (with string keys) as a parameter, or multiple string parameters, to build URL queries. Querify earlier used 'Dictionary' to add key/value pairs to a map to build URL queries. Changed to dynamically generate ordered key/value pairs. Cannot take string slice as key (earlier possible due to Dictionary). Added tests and benchmarks for querify. Closes #6735 --- tpl/collections/collections.go | 36 +++++++++++++++++++++++++---- tpl/collections/collections_test.go | 33 ++++++++++++++++++++++++++ tpl/collections/init.go | 4 ++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 3521f9cd5..669e386f8 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -424,13 +424,39 @@ func (ns *Namespace) Last(limit interface{}, seq interface{}) (interface{}, erro // Querify encodes the given parameters in URL-encoded form ("bar=baz&foo=quux") sorted by key. func (ns *Namespace) Querify(params ...interface{}) (string, error) { qs := url.Values{} - vals, err := ns.Dictionary(params...) - if err != nil { - return "", errors.New("querify keys must be strings") + + if len(params) == 1 { + switch v := params[0].(type) { + case []string: + if len(v)%2 != 0 { + return "", errors.New("invalid query") + } + + for i := 0; i < len(v); i += 2 { + qs.Add(v[i], v[i+1]) + } + + return qs.Encode(), nil + + case []interface{}: + params = v + + default: + return "", errors.New("query keys must be strings") + } } - for name, value := range vals { - qs.Add(name, fmt.Sprintf("%v", value)) + if len(params)%2 != 0 { + return "", errors.New("invalid query") + } + + for i := 0; i < len(params); i += 2 { + switch v := params[i].(type) { + case string: + qs.Add(v, fmt.Sprintf("%v", params[i+1])) + default: + return "", errors.New("query keys must be strings") + } } return qs.Encode(), nil diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index a9bf9a09b..1e0569751 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -564,9 +564,16 @@ func TestQuerify(t *testing.T) { }{ {[]interface{}{"a", "b"}, "a=b"}, {[]interface{}{"a", "b", "c", "d", "f", " &"}, `a=b&c=d&f=+%26`}, + {[]interface{}{[]string{"a", "b"}}, "a=b"}, + {[]interface{}{[]string{"a", "b", "c", "d", "f", " &"}}, `a=b&c=d&f=+%26`}, + {[]interface{}{[]interface{}{"x", "y"}}, `x=y`}, + {[]interface{}{[]interface{}{"x", 5}}, `x=5`}, // errors {[]interface{}{5, "b"}, false}, {[]interface{}{"a", "b", "c"}, false}, + {[]interface{}{[]string{"a", "b", "c"}}, false}, + {[]interface{}{[]string{"a", "b"}, "c"}, false}, + {[]interface{}{[]interface{}{"c", "d", "e"}}, false}, } { errMsg := qt.Commentf("[%d] %v", i, test.params) @@ -582,6 +589,32 @@ func TestQuerify(t *testing.T) { } } +func BenchmarkQuerify(b *testing.B) { + ns := New(&deps.Deps{}) + params := []interface{}{"a", "b", "c", "d", "f", " &"} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ns.Querify(params...) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkQuerifySlice(b *testing.B) { + ns := New(&deps.Deps{}) + params := []string{"a", "b", "c", "d", "f", " &"} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ns.Querify(params) + if err != nil { + b.Fatal(err) + } + } +} + func TestSeq(t *testing.T) { t.Parallel() c := qt.New(t) diff --git a/tpl/collections/init.go b/tpl/collections/init.go index 1e6412610..4126b4410 100644 --- a/tpl/collections/init.go +++ b/tpl/collections/init.go @@ -122,6 +122,10 @@ func init() { `Search`, `Search`, }, + { + `{{ slice "foo" 1 "bar" 2 | querify | safeHTML }}`, + `bar=2&foo=1`, + }, }, )