Skip to content

Commit e27f8b0

Browse files
wuhongyuwuhongyu
wuhongyu
authored and
wuhongyu
committed
feat add SetFormUnmarshaler to disable form string array of split comma format
1 parent f747585 commit e27f8b0

File tree

4 files changed

+392
-13
lines changed

4 files changed

+392
-13
lines changed

core/mapping/unmarshaler.go

+61-5
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,24 @@ type (
5252
UnmarshalOption func(*unmarshalOptions)
5353

5454
unmarshalOptions struct {
55-
fillDefault bool
56-
fromArray bool
57-
fromString bool
58-
opaqueKeys bool
59-
canonicalKey func(key string) string
55+
fillDefault bool
56+
fromArray bool
57+
fromString bool
58+
opaqueKeys bool
59+
canonicalKey func(key string) string
60+
formCommaArray bool //a new Switch to control the form comma-split array parse,default is true
6061
}
6162
)
6263

6364
// NewUnmarshaler returns an Unmarshaler.
6465
func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
6566
unmarshaler := Unmarshaler{
6667
key: key,
68+
opts: unmarshalOptions{
69+
// The comma split form array mode was enabled in 1.7.5
70+
// so the default value is true in order not to introduce destructiveness
71+
formCommaArray: true,
72+
},
6773
}
6874

6975
for _, opt := range opts {
@@ -152,6 +158,7 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value,
152158
return nil
153159
}
154160

161+
refValue = makeStringSlice(dereffedBaseKind, refValue, u.opts)
155162
var valid bool
156163
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
157164

@@ -1053,6 +1060,21 @@ func WithFromArray() UnmarshalOption {
10531060
}
10541061
}
10551062

1063+
// WithFormCommaArray b is a switch to enable comma-split array values.
1064+
// if b == false will be disable form param comma-split format array values
1065+
// else will be enable above
1066+
//
1067+
// For example, if the field type is []string,
1068+
// GET /a?code=1,2,3,4,5
1069+
//
1070+
// if b==false will be code=[]string{"1,2,3,4,5"}
1071+
// else will be code=[]string{"1","2","3","4","5"}
1072+
func WithFormCommaArray(b bool) UnmarshalOption {
1073+
return func(opt *unmarshalOptions) {
1074+
opt.formCommaArray = b
1075+
}
1076+
}
1077+
10561078
// WithOpaqueKeys customizes an Unmarshaler with opaque keys.
10571079
// Opaque keys are keys that are not processed by the unmarshaler.
10581080
func WithOpaqueKeys() UnmarshalOption {
@@ -1185,6 +1207,40 @@ func join(elem ...string) string {
11851207
return builder.String()
11861208
}
11871209

1210+
func makeStringSlice(dereffedBaseKind reflect.Kind, refValue reflect.Value, opts unmarshalOptions) reflect.Value {
1211+
if !opts.fromArray {
1212+
return refValue
1213+
}
1214+
if refValue.Len() != 1 {
1215+
return refValue
1216+
}
1217+
1218+
element := refValue.Index(0)
1219+
if element.Kind() != reflect.String {
1220+
return refValue
1221+
}
1222+
1223+
val, ok := element.Interface().(string)
1224+
if !ok {
1225+
return refValue
1226+
}
1227+
if dereffedBaseKind != reflect.String || opts.formCommaArray {
1228+
splits := strings.Split(val, comma)
1229+
if len(splits) <= 1 {
1230+
return refValue
1231+
}
1232+
1233+
slice := reflect.MakeSlice(stringSliceType, len(splits), len(splits))
1234+
for i, split := range splits {
1235+
// allow empty strings
1236+
slice.Index(i).Set(reflect.ValueOf(split))
1237+
}
1238+
return slice
1239+
} else {
1240+
return refValue
1241+
}
1242+
}
1243+
11881244
func newInitError(name string) error {
11891245
return fmt.Errorf("field %q is not set", name)
11901246
}

core/mapping/unmarshaler_test.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,9 @@ func TestUnmarshalIntSlice(t *testing.T) {
14621462

14631463
ast := assert.New(t)
14641464
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray())
1465-
ast.Error(unmarshaler.Unmarshal(m, &v))
1465+
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
1466+
ast.ElementsMatch([]int{1, 2}, v.Ages)
1467+
}
14661468
})
14671469
}
14681470

@@ -1544,7 +1546,7 @@ func TestUnmarshalStringSliceFromString(t *testing.T) {
15441546
ast := assert.New(t)
15451547
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray())
15461548
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
1547-
ast.ElementsMatch([]string{","}, v.Names)
1549+
ast.ElementsMatch([]string{"", ""}, v.Names)
15481550
}
15491551
})
15501552

@@ -1558,6 +1560,36 @@ func TestUnmarshalStringSliceFromString(t *testing.T) {
15581560

15591561
ast := assert.New(t)
15601562
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray())
1563+
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
1564+
ast.ElementsMatch([]string{"aa", "bb"}, v.Names)
1565+
}
1566+
})
1567+
1568+
t.Run("slice from empty and valid string", func(t *testing.T) {
1569+
var v struct {
1570+
Names []string `key:"names"`
1571+
}
1572+
m := map[string]any{
1573+
"names": []string{","},
1574+
}
1575+
1576+
ast := assert.New(t)
1577+
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray(), WithFormCommaArray(false))
1578+
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
1579+
ast.ElementsMatch([]string{","}, v.Names)
1580+
}
1581+
})
1582+
1583+
t.Run("slice from valid strings with comma", func(t *testing.T) {
1584+
var v struct {
1585+
Names []string `key:"names"`
1586+
}
1587+
m := map[string]any{
1588+
"names": []string{"aa,bb"},
1589+
}
1590+
1591+
ast := assert.New(t)
1592+
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray(), WithFormCommaArray(false))
15611593
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
15621594
ast.ElementsMatch([]string{"aa,bb"}, v.Names)
15631595
}

rest/httpx/requests.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ const (
2626
var (
2727
formUnmarshaler = mapping.NewUnmarshaler(
2828
formKey,
29-
mapping.WithStringValues(),
30-
mapping.WithOpaqueKeys(),
31-
mapping.WithFromArray())
29+
defaultFormUnmarshalOption()...)
3230
pathUnmarshaler = mapping.NewUnmarshaler(
3331
pathKey,
3432
mapping.WithStringValues(),
@@ -40,6 +38,18 @@ var (
4038
validatorLock sync.RWMutex
4139
)
4240

41+
func defaultFormUnmarshalOption() []mapping.UnmarshalOption {
42+
return []mapping.UnmarshalOption{
43+
mapping.WithStringValues(),
44+
mapping.WithOpaqueKeys(),
45+
mapping.WithFromArray(),
46+
}
47+
}
48+
func SetFormUnmarshaler(customOption ...mapping.UnmarshalOption) {
49+
options := append(defaultFormUnmarshalOption(), customOption...)
50+
formUnmarshaler = mapping.NewUnmarshaler(formKey, options...)
51+
}
52+
4353
// Validator defines the interface for validating the request.
4454
type Validator interface {
4555
// Validate validates the request and parsed data.

0 commit comments

Comments
 (0)