1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
// Copyright (c) 2021, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.
package tdhttp
import (
"errors"
"fmt"
"net/url"
"reflect"
"strconv"
"github.com/maxatome/go-testdeep/internal/color"
)
// Q allows to easily declare query parameters for use in [NewRequest]
// and related [http.Request] builders, as [Get] for example:
//
// req := tdhttp.Get("/path", tdhttp.Q{
// "id": []int64{1234, 4567},
// "dryrun": true,
// })
//
// See [NewRequest] for several examples of use.
//
// Accepted types as values are:
// - [fmt.Stringer]
// - string
// - int, int8, int16, int32, int64
// - uint, uint8, uint16, uint32, uint64
// - float32, float64
// - bool
// - slice or array of any type above, plus any
// - pointer on any type above, plus any or any other pointer
type Q map[string]any
var _ URLValuesEncoder = Q(nil)
// AddTo adds the q contents to qp.
func (q Q) AddTo(qp url.Values) error {
for param, value := range q {
// Ignore nil values
if value == nil {
continue
}
err := q.addParamTo(param, reflect.ValueOf(value), true, qp)
if err != nil {
return err
}
}
return nil
}
// Values returns a [url.Values] instance corresponding to q. It panics
// if a value cannot be converted.
func (q Q) Values() url.Values {
qp := make(url.Values, len(q))
err := q.AddTo(qp)
if err != nil {
panic(errors.New(color.Bad(err.Error())))
}
return qp
}
// Encode does the same as [url.Values.Encode] does. So quoting its doc,
// it encodes the values into “URL encoded” form ("bar=baz&foo=quux")
// sorted by key.
//
// It panics if a value cannot be converted.
func (q Q) Encode() string {
return q.Values().Encode()
}
func (q Q) addParamTo(param string, v reflect.Value, allowArray bool, qp url.Values) error {
var str string
for {
if s, ok := v.Interface().(fmt.Stringer); ok {
qp.Add(param, s.String())
return nil
}
switch v.Kind() {
case reflect.Slice, reflect.Array:
if !allowArray {
return fmt.Errorf("%s is only allowed at the root level for param %q",
v.Kind(), param)
}
for i, l := 0, v.Len(); i < l; i++ {
err := q.addParamTo(param, v.Index(i), false, qp)
if err != nil {
return err
}
}
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
str = strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
str = strconv.FormatUint(v.Uint(), 10)
case reflect.Float32, reflect.Float64:
str = strconv.FormatFloat(v.Float(), 'g', -1, 64)
case reflect.String:
str = v.String()
case reflect.Bool:
str = strconv.FormatBool(v.Bool())
case reflect.Ptr, reflect.Interface:
if !v.IsNil() {
v = v.Elem()
continue
}
return nil // mimic url.Values behavior ⇒ ignore
default:
return fmt.Errorf("don't know how to add type %s (%s) to param %q",
v.Type(), v.Kind(), param)
}
qp.Add(param, str)
return nil
}
}
|