/
q.go
129 lines (111 loc) · 3.03 KB
/
q.go
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
}
}