Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat add SetFormUnmarshaler to disable form string array of split co… #4638

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 61 additions & 5 deletions core/mapping/unmarshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,24 @@
UnmarshalOption func(*unmarshalOptions)

unmarshalOptions struct {
fillDefault bool
fromArray bool
fromString bool
opaqueKeys bool
canonicalKey func(key string) string
fillDefault bool
fromArray bool
fromString bool
opaqueKeys bool
canonicalKey func(key string) string
formCommaArray bool //a new Switch to control the form comma-split array parse,default is true
}
)

// NewUnmarshaler returns an Unmarshaler.
func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
unmarshaler := Unmarshaler{
key: key,
opts: unmarshalOptions{
// The comma split form array mode was enabled in 1.7.5
// so the default value is true in order not to introduce destructiveness
formCommaArray: true,
},
}

for _, opt := range opts {
Expand Down Expand Up @@ -150,6 +156,7 @@
return nil
}

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

Expand Down Expand Up @@ -1051,6 +1058,21 @@
}
}

// WithFormCommaArray b is a switch to enable comma-split array values.
// if b == false will be disable form param comma-split format array values
// else will be enable above
//
// For example, if the field type is []string,
// GET /a?code=1,2,3,4,5
//
// if b==false will be code=[]string{"1,2,3,4,5"}
// else will be code=[]string{"1","2","3","4","5"}
func WithFormCommaArray(b bool) UnmarshalOption {
return func(opt *unmarshalOptions) {
opt.formCommaArray = b
}
}

// WithOpaqueKeys customizes an Unmarshaler with opaque keys.
// Opaque keys are keys that are not processed by the unmarshaler.
func WithOpaqueKeys() UnmarshalOption {
Expand Down Expand Up @@ -1183,6 +1205,40 @@
return builder.String()
}

func makeStringSlice(dereffedBaseKind reflect.Kind, refValue reflect.Value, opts unmarshalOptions) reflect.Value {
if !opts.fromArray {
return refValue
}
if refValue.Len() != 1 {
return refValue
}

element := refValue.Index(0)
if element.Kind() != reflect.String {
return refValue
}

val, ok := element.Interface().(string)
if !ok {
return refValue
}
if dereffedBaseKind != reflect.String || opts.formCommaArray {
splits := strings.Split(val, comma)

Check failure on line 1226 in core/mapping/unmarshaler.go

View workflow job for this annotation

GitHub Actions / Windows

undefined: comma

Check failure on line 1226 in core/mapping/unmarshaler.go

View workflow job for this annotation

GitHub Actions / Linux

undefined: comma
if len(splits) <= 1 {
return refValue
}

slice := reflect.MakeSlice(stringSliceType, len(splits), len(splits))

Check failure on line 1231 in core/mapping/unmarshaler.go

View workflow job for this annotation

GitHub Actions / Windows

undefined: stringSliceType

Check failure on line 1231 in core/mapping/unmarshaler.go

View workflow job for this annotation

GitHub Actions / Linux

undefined: stringSliceType
for i, split := range splits {
// allow empty strings
slice.Index(i).Set(reflect.ValueOf(split))
}
return slice
} else {
return refValue
}
}

func newInitError(name string) error {
return fmt.Errorf("field %q is not set", name)
}
Expand Down
36 changes: 34 additions & 2 deletions core/mapping/unmarshaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,9 @@ func TestUnmarshalIntSlice(t *testing.T) {

ast := assert.New(t)
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray())
ast.Error(unmarshaler.Unmarshal(m, &v))
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
ast.ElementsMatch([]int{1, 2}, v.Ages)
}
})
}

Expand Down Expand Up @@ -1544,7 +1546,7 @@ func TestUnmarshalStringSliceFromString(t *testing.T) {
ast := assert.New(t)
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray())
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
ast.ElementsMatch([]string{","}, v.Names)
ast.ElementsMatch([]string{"", ""}, v.Names)
}
})

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

ast := assert.New(t)
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray())
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
ast.ElementsMatch([]string{"aa", "bb"}, v.Names)
}
})

t.Run("slice from empty and valid string", func(t *testing.T) {
var v struct {
Names []string `key:"names"`
}
m := map[string]any{
"names": []string{","},
}

ast := assert.New(t)
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray(), WithFormCommaArray(false))
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
ast.ElementsMatch([]string{","}, v.Names)
}
})

t.Run("slice from valid strings with comma", func(t *testing.T) {
var v struct {
Names []string `key:"names"`
}
m := map[string]any{
"names": []string{"aa,bb"},
}

ast := assert.New(t)
unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray(), WithFormCommaArray(false))
if ast.NoError(unmarshaler.Unmarshal(m, &v)) {
ast.ElementsMatch([]string{"aa,bb"}, v.Names)
}
Expand Down
16 changes: 13 additions & 3 deletions rest/httpx/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ const (
var (
formUnmarshaler = mapping.NewUnmarshaler(
formKey,
mapping.WithStringValues(),
mapping.WithOpaqueKeys(),
mapping.WithFromArray())
defaultFormUnmarshalOption()...)
pathUnmarshaler = mapping.NewUnmarshaler(
pathKey,
mapping.WithStringValues(),
Expand All @@ -40,6 +38,18 @@ var (
validatorLock sync.RWMutex
)

func defaultFormUnmarshalOption() []mapping.UnmarshalOption {
return []mapping.UnmarshalOption{
mapping.WithStringValues(),
mapping.WithOpaqueKeys(),
mapping.WithFromArray(),
}
}
func SetFormUnmarshaler(customOption ...mapping.UnmarshalOption) {
options := append(defaultFormUnmarshalOption(), customOption...)
formUnmarshaler = mapping.NewUnmarshaler(formKey, options...)
}

// Validator defines the interface for validating the request.
type Validator interface {
// Validate validates the request and parsed data.
Expand Down
Loading
Loading