diff options
Diffstat (limited to 'tpl/collections/querify.go')
-rw-r--r-- | tpl/collections/querify.go | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/tpl/collections/querify.go b/tpl/collections/querify.go new file mode 100644 index 000000000..19e6d8afe --- /dev/null +++ b/tpl/collections/querify.go @@ -0,0 +1,125 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package collections + +import ( + "errors" + "net/url" + + "github.com/gohugoio/hugo/common/maps" + "github.com/spf13/cast" +) + +var ( + errWrongArgStructure = errors.New("expected a map, a slice with an even number of elements, or an even number of scalar values, and each key must be a string") + errKeyIsEmptyString = errors.New("one of the keys is an empty string") +) + +// Querify returns a URL query string composed of the given key-value pairs, +// encoded and sorted by key. +func (ns *Namespace) Querify(params ...any) (string, error) { + if len(params) == 0 { + return "", nil + } + + if len(params) == 1 { + switch v := params[0].(type) { + case map[string]any: // created with collections.Dictionary + return mapToQueryString(v) + case maps.Params: // site configuration or page parameters + return mapToQueryString(v) + case []string: + return stringSliceToQueryString(v) + case []any: + s, err := interfaceSliceToStringSlice(v) + if err != nil { + return "", err + } + return stringSliceToQueryString(s) + default: + return "", errWrongArgStructure + } + } + + if len(params)%2 != 0 { + return "", errWrongArgStructure + } + + s, err := interfaceSliceToStringSlice(params) + if err != nil { + return "", err + } + return stringSliceToQueryString(s) +} + +// mapToQueryString returns a URL query string derived from the given string +// map, encoded and sorted by key. The function returns an error if it cannot +// convert an element value to a string. +func mapToQueryString[T map[string]any | maps.Params](m T) (string, error) { + if len(m) == 0 { + return "", nil + } + + qs := url.Values{} + for k, v := range m { + if len(k) == 0 { + return "", errKeyIsEmptyString + } + vs, err := cast.ToStringE(v) + if err != nil { + return "", err + } + qs.Add(k, vs) + } + return qs.Encode(), nil +} + +// sliceToQueryString returns a URL query string derived from the given slice +// of strings, encoded and sorted by key. The function returns an error if +// there are an odd number of elements. +func stringSliceToQueryString(s []string) (string, error) { + if len(s) == 0 { + return "", nil + } + if len(s)%2 != 0 { + return "", errWrongArgStructure + } + + qs := url.Values{} + for i := 0; i < len(s); i += 2 { + if len(s[i]) == 0 { + return "", errKeyIsEmptyString + } + qs.Add(s[i], s[i+1]) + } + return qs.Encode(), nil +} + +// interfaceSliceToStringSlice converts a slice of interfaces to a slice of +// strings, returning an error if it cannot convert an element to a string. +func interfaceSliceToStringSlice(s []any) ([]string, error) { + if len(s) == 0 { + return []string{}, nil + } + + ss := make([]string, 0, len(s)) + for _, v := range s { + vs, err := cast.ToStringE(v) + if err != nil { + return []string{}, err + } + ss = append(ss, vs) + } + return ss, nil +} |