File: q.go

package info (click to toggle)
golang-github-maxatome-go-testdeep 1.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,416 kB
  • sloc: perl: 1,012; yacc: 130; makefile: 2
file content (129 lines) | stat: -rw-r--r-- 3,101 bytes parent folder | download | duplicates (2)
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
	}
}