Skip to content

Commit a52cebd

Browse files
fix(client): support multipart encoding array formats (#342)
1 parent 1bef69c commit a52cebd

File tree

2 files changed

+82
-8
lines changed

2 files changed

+82
-8
lines changed

Diff for: internal/apiform/encoder.go

+47-6
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,32 @@ import (
2020
var encoders sync.Map // map[encoderEntry]encoderFunc
2121

2222
func Marshal(value interface{}, writer *multipart.Writer) error {
23-
e := &encoder{dateFormat: time.RFC3339}
23+
e := &encoder{
24+
dateFormat: time.RFC3339,
25+
arrayFmt: "brackets",
26+
}
2427
return e.marshal(value, writer)
2528
}
2629

2730
func MarshalRoot(value interface{}, writer *multipart.Writer) error {
28-
e := &encoder{root: true, dateFormat: time.RFC3339}
31+
e := &encoder{
32+
root: true,
33+
dateFormat: time.RFC3339,
34+
arrayFmt: "brackets",
35+
}
36+
return e.marshal(value, writer)
37+
}
38+
39+
func MarshalWithSettings(value interface{}, writer *multipart.Writer, arrayFormat string) error {
40+
e := &encoder{
41+
arrayFmt: arrayFormat,
42+
dateFormat: time.RFC3339,
43+
}
2944
return e.marshal(value, writer)
3045
}
3146

3247
type encoder struct {
48+
arrayFmt string
3349
dateFormat string
3450
root bool
3551
}
@@ -163,15 +179,40 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
163179
}
164180
}
165181

182+
func arrayKeyEncoder(arrayFmt string) func(string, int) string {
183+
var keyFn func(string, int) string
184+
switch arrayFmt {
185+
case "comma", "repeat":
186+
keyFn = func(k string, _ int) string { return k }
187+
case "brackets":
188+
keyFn = func(key string, _ int) string { return key + "[]" }
189+
case "indices:dots":
190+
keyFn = func(k string, i int) string {
191+
if k == "" {
192+
return strconv.Itoa(i)
193+
}
194+
return k + "." + strconv.Itoa(i)
195+
}
196+
case "indices:brackets":
197+
keyFn = func(k string, i int) string {
198+
if k == "" {
199+
return strconv.Itoa(i)
200+
}
201+
return k + "[" + strconv.Itoa(i) + "]"
202+
}
203+
}
204+
return keyFn
205+
}
206+
166207
func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
167208
itemEncoder := e.typeEncoder(t.Elem())
168-
209+
keyFn := arrayKeyEncoder(e.arrayFmt)
169210
return func(key string, v reflect.Value, writer *multipart.Writer) error {
170-
if key != "" {
171-
key = key + "."
211+
if keyFn == nil {
212+
return fmt.Errorf("apiform: unsupported array format")
172213
}
173214
for i := 0; i < v.Len(); i++ {
174-
err := itemEncoder(key+strconv.Itoa(i), v.Index(i), writer)
215+
err := itemEncoder(keyFn(key, i), v.Index(i), writer)
175216
if err != nil {
176217
return err
177218
}

Diff for: internal/apiform/form_test.go

+35-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ type Primitives struct {
1919
F []int `form:"f"`
2020
}
2121

22+
// These aliases are necessary to bypass the cache.
23+
// This only relevant during testing.
24+
type int_ int
25+
type PrimitivesBrackets struct {
26+
F []int_ `form:"f"`
27+
}
28+
2229
type PrimitivePointers struct {
2330
A *bool `form:"a"`
2431
B *int `form:"b"`
@@ -169,6 +176,27 @@ Content-Disposition: form-data; name="f.3"
169176
`,
170177
Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
171178
},
179+
"primitive_struct,brackets": {
180+
`--xxx
181+
Content-Disposition: form-data; name="f[]"
182+
183+
1
184+
--xxx
185+
Content-Disposition: form-data; name="f[]"
186+
187+
2
188+
--xxx
189+
Content-Disposition: form-data; name="f[]"
190+
191+
3
192+
--xxx
193+
Content-Disposition: form-data; name="f[]"
194+
195+
4
196+
--xxx--
197+
`,
198+
PrimitivesBrackets{F: []int_{1, 2, 3, 4}},
199+
},
172200

173201
"slices": {
174202
`--xxx
@@ -213,7 +241,6 @@ Content-Disposition: form-data; name="slices.0.f.3"
213241
Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}},
214242
},
215243
},
216-
217244
"primitive_pointer_struct": {
218245
`--xxx
219246
Content-Disposition: form-data; name="a"
@@ -423,7 +450,13 @@ func TestEncode(t *testing.T) {
423450
buf := bytes.NewBuffer(nil)
424451
writer := multipart.NewWriter(buf)
425452
writer.SetBoundary("xxx")
426-
err := Marshal(test.val, writer)
453+
454+
var arrayFmt string = "indices:dots"
455+
if tags := strings.Split(name, ","); len(tags) > 1 {
456+
arrayFmt = tags[1]
457+
}
458+
459+
err := MarshalWithSettings(test.val, writer, arrayFmt)
427460
if err != nil {
428461
t.Errorf("serialization of %v failed with error %v", test.val, err)
429462
}

0 commit comments

Comments
 (0)