Skip to content

Commit

Permalink
binder: make binding to Map work better with string destinations (#2554)
Browse files Browse the repository at this point in the history
  • Loading branch information
aldas committed Dec 20, 2023
1 parent 226e4f0 commit 60fc2fb
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 3 deletions.
22 changes: 19 additions & 3 deletions bind.go
Expand Up @@ -131,10 +131,26 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem()

// Map
if typ.Kind() == reflect.Map {
// Support binding to limited Map destinations:
// - map[string][]string,
// - map[string]string <-- (binds first value from data slice)
// - map[string]interface{}
// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
k := typ.Elem().Kind()
isElemInterface := k == reflect.Interface
isElemString := k == reflect.String
isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
if !(isElemSliceOfStrings || isElemString || isElemInterface) {
return nil
}
for k, v := range data {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
if isElemString {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
} else {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
}
}
return nil
}
Expand Down
56 changes: 56 additions & 0 deletions bind_test.go
Expand Up @@ -429,6 +429,62 @@ func TestBindUnsupportedMediaType(t *testing.T) {
testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
}

func TestDefaultBinder_bindDataToMap(t *testing.T) {
exampleData := map[string][]string{
"multiple": {"1", "2"},
"single": {"3"},
}

t.Run("ok, bind to map[string]string", func(t *testing.T) {
dest := map[string]string{}
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
assert.Equal(t,
map[string]string{
"multiple": "1",
"single": "3",
},
dest,
)
})

t.Run("ok, bind to map[string][]string", func(t *testing.T) {
dest := map[string][]string{}
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
assert.Equal(t,
map[string][]string{
"multiple": {"1", "2"},
"single": {"3"},
},
dest,
)
})

t.Run("ok, bind to map[string]interface", func(t *testing.T) {
dest := map[string]interface{}{}
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
assert.Equal(t,
map[string]interface{}{
"multiple": []string{"1", "2"},
"single": []string{"3"},
},
dest,
)
})

t.Run("ok, bind to map[string]int skips", func(t *testing.T) {
dest := map[string]int{}
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
assert.Equal(t, map[string]int{}, dest)
})

t.Run("ok, bind to map[string][]int skips", func(t *testing.T) {
dest := map[string][]int{}
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
assert.Equal(t, map[string][]int{}, dest)
})

}

func TestBindbindData(t *testing.T) {
ts := new(bindTestStruct)
b := new(DefaultBinder)
Expand Down

0 comments on commit 60fc2fb

Please sign in to comment.