Skip to content

Commit 30f7a39

Browse files
committed
Add Encoder type for custom encoding of values
1 parent ec0a78e commit 30f7a39

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

query/encode.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ import (
3333

3434
var timeType = reflect.TypeOf(time.Time{})
3535

36+
var encoderType = reflect.TypeOf(new(Encoder)).Elem()
37+
38+
// Encoder is an interface implemented by any type that wishes to encode
39+
// itself into URL values in a non-standard way.
40+
type Encoder interface {
41+
EncodeValues(v *url.Values) error
42+
}
43+
3644
// Values returns the url.Values encoding of v.
3745
//
3846
// Values expects to be passed a struct, and traverses it recursively using the
@@ -107,14 +115,14 @@ func Values(v interface{}) (url.Values, error) {
107115
}
108116

109117
values := make(url.Values)
110-
reflectValue(values, val)
111-
return values, nil
118+
err := reflectValue(values, val)
119+
return values, err
112120
}
113121

114122
// reflectValue populates the values parameter from the struct fields in val.
115123
// Embedded structs are followed recursively (using the rules defined in the
116124
// Values function documentation) breadth-first.
117-
func reflectValue(values url.Values, val reflect.Value) {
125+
func reflectValue(values url.Values, val reflect.Value) error {
118126
var embedded []reflect.Value
119127

120128
typ := val.Type()
@@ -144,6 +152,14 @@ func reflectValue(values url.Values, val reflect.Value) {
144152
continue
145153
}
146154

155+
if sv.Type().Implements(encoderType) {
156+
m := sv.Interface().(Encoder)
157+
if err := m.EncodeValues(&values); err != nil {
158+
return err
159+
}
160+
continue
161+
}
162+
147163
if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
148164
var del byte
149165
if opts.Contains("comma") {
@@ -176,8 +192,12 @@ func reflectValue(values url.Values, val reflect.Value) {
176192
}
177193

178194
for _, f := range embedded {
179-
reflectValue(values, f)
195+
if err := reflectValue(values, f); err != nil {
196+
return err
197+
}
180198
}
199+
200+
return nil
181201
}
182202

183203
// valueString returns the string representation of a value.

query/encode_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package query
66

77
import (
8+
"fmt"
89
"net/url"
910
"reflect"
1011
"testing"
@@ -188,6 +189,34 @@ func TestValues_invalidInput(t *testing.T) {
188189
}
189190
}
190191

192+
type EncodedArgs []string
193+
194+
func (m EncodedArgs) EncodeValues(v *url.Values) error {
195+
for i, arg := range m {
196+
v.Set(fmt.Sprintf("arg.%d", i), arg)
197+
}
198+
return nil
199+
}
200+
201+
func TestValues_Marshaler(t *testing.T) {
202+
s := struct {
203+
Args EncodedArgs `url:"args"`
204+
}{[]string{"a", "b", "c"}}
205+
v, err := Values(s)
206+
if err != nil {
207+
t.Errorf("Values(%q) returned error: %v", s, err)
208+
}
209+
210+
want := url.Values{
211+
"arg.0": {"a"},
212+
"arg.1": {"b"},
213+
"arg.2": {"c"},
214+
}
215+
if !reflect.DeepEqual(want, v) {
216+
t.Errorf("Values(%q) returned %v, want %v", s, v, want)
217+
}
218+
}
219+
191220
func TestTagParsing(t *testing.T) {
192221
name, opts := parseTag("field,foobar,foo")
193222
if name != "field" {

0 commit comments

Comments
 (0)