mirror of https://github.com/gin-gonic/gin.git
Allow bind with a map[string]string (#2484)
Co-authored-by: thinkerou <thinkerou@gmail.com>
This commit is contained in:
parent
16cd8cdd4e
commit
65ed60ed13
|
@ -13,6 +13,7 @@ import (
|
|||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -200,6 +201,12 @@ func TestBindingJSONDisallowUnknownFields(t *testing.T) {
|
|||
`{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`)
|
||||
}
|
||||
|
||||
func TestBindingJSONStringMap(t *testing.T) {
|
||||
testBodyBindingStringMap(t, JSON,
|
||||
"/", "/",
|
||||
`{"foo": "bar", "hello": "world"}`, `{"num": 2}`)
|
||||
}
|
||||
|
||||
func TestBindingForm(t *testing.T) {
|
||||
testFormBinding(t, "POST",
|
||||
"/", "/",
|
||||
|
@ -336,6 +343,37 @@ func TestBindingFormForType(t *testing.T) {
|
|||
"", "", "StructPointer")
|
||||
}
|
||||
|
||||
func TestBindingFormStringMap(t *testing.T) {
|
||||
testBodyBindingStringMap(t, Form,
|
||||
"/", "",
|
||||
`foo=bar&hello=world`, "")
|
||||
// Should pick the last value
|
||||
testBodyBindingStringMap(t, Form,
|
||||
"/", "",
|
||||
`foo=something&foo=bar&hello=world`, "")
|
||||
}
|
||||
|
||||
func TestBindingFormStringSliceMap(t *testing.T) {
|
||||
obj := make(map[string][]string)
|
||||
req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world")
|
||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||
err := Form.Bind(req, &obj)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, obj)
|
||||
assert.Len(t, obj, 2)
|
||||
target := map[string][]string{
|
||||
"foo": {"something", "bar"},
|
||||
"hello": {"world"},
|
||||
}
|
||||
assert.True(t, reflect.DeepEqual(obj, target))
|
||||
|
||||
objInvalid := make(map[string][]int)
|
||||
req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world")
|
||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||
err = Form.Bind(req, &objInvalid)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBindingQuery(t *testing.T) {
|
||||
testQueryBinding(t, "POST",
|
||||
"/?foo=bar&bar=foo", "/",
|
||||
|
@ -366,6 +404,28 @@ func TestBindingQueryBoolFail(t *testing.T) {
|
|||
"bool_foo=unused", "")
|
||||
}
|
||||
|
||||
func TestBindingQueryStringMap(t *testing.T) {
|
||||
b := Query
|
||||
|
||||
obj := make(map[string]string)
|
||||
req := requestWithBody("GET", "/?foo=bar&hello=world", "")
|
||||
err := b.Bind(req, &obj)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, obj)
|
||||
assert.Len(t, obj, 2)
|
||||
assert.Equal(t, "bar", obj["foo"])
|
||||
assert.Equal(t, "world", obj["hello"])
|
||||
|
||||
obj = make(map[string]string)
|
||||
req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last
|
||||
err = b.Bind(req, &obj)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, obj)
|
||||
assert.Len(t, obj, 2)
|
||||
assert.Equal(t, "2", obj["foo"])
|
||||
assert.Equal(t, "world", obj["hello"])
|
||||
}
|
||||
|
||||
func TestBindingXML(t *testing.T) {
|
||||
testBodyBinding(t,
|
||||
XML, "xml",
|
||||
|
@ -387,6 +447,13 @@ func TestBindingYAML(t *testing.T) {
|
|||
`foo: bar`, `bar: foo`)
|
||||
}
|
||||
|
||||
func TestBindingYAMLStringMap(t *testing.T) {
|
||||
// YAML is a superset of JSON, so the test below is JSON (to avoid newlines)
|
||||
testBodyBindingStringMap(t, YAML,
|
||||
"/", "/",
|
||||
`{"foo": "bar", "hello": "world"}`, `{"nested": {"foo": "bar"}}`)
|
||||
}
|
||||
|
||||
func TestBindingYAMLFail(t *testing.T) {
|
||||
testBodyBindingFail(t,
|
||||
YAML, "yaml",
|
||||
|
@ -1114,6 +1181,32 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
||||
obj := make(map[string]string)
|
||||
req := requestWithBody("POST", path, body)
|
||||
if b.Name() == "form" {
|
||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||
}
|
||||
err := b.Bind(req, &obj)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, obj)
|
||||
assert.Len(t, obj, 2)
|
||||
assert.Equal(t, "bar", obj["foo"])
|
||||
assert.Equal(t, "world", obj["hello"])
|
||||
|
||||
if badPath != "" && badBody != "" {
|
||||
obj = make(map[string]string)
|
||||
req = requestWithBody("POST", badPath, badBody)
|
||||
err = b.Bind(req, &obj)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
objInt := make(map[string]int)
|
||||
req = requestWithBody("POST", path, body)
|
||||
err = b.Bind(req, &objInt)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||
assert.Equal(t, name, b.Name())
|
||||
|
||||
|
|
|
@ -29,6 +29,21 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||
var emptyField = reflect.StructField{}
|
||||
|
||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||
// Check if ptr is a map
|
||||
ptrVal := reflect.ValueOf(ptr)
|
||||
var pointed interface{}
|
||||
if ptrVal.Kind() == reflect.Ptr {
|
||||
ptrVal = ptrVal.Elem()
|
||||
pointed = ptrVal.Interface()
|
||||
}
|
||||
if ptrVal.Kind() == reflect.Map &&
|
||||
ptrVal.Type().Key().Kind() == reflect.String {
|
||||
if pointed != nil {
|
||||
ptr = pointed
|
||||
}
|
||||
return setFormMap(ptr, form)
|
||||
}
|
||||
|
||||
return mappingByPtr(ptr, formSource(form), tag)
|
||||
}
|
||||
|
||||
|
@ -349,3 +364,29 @@ func head(str, sep string) (head string, tail string) {
|
|||
}
|
||||
return str[:idx], str[idx+len(sep):]
|
||||
}
|
||||
|
||||
func setFormMap(ptr interface{}, form map[string][]string) error {
|
||||
el := reflect.TypeOf(ptr).Elem()
|
||||
|
||||
if el.Kind() == reflect.Slice {
|
||||
ptrMap, ok := ptr.(map[string][]string)
|
||||
if !ok {
|
||||
return errors.New("cannot convert to map slices of strings")
|
||||
}
|
||||
for k, v := range form {
|
||||
ptrMap[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ptrMap, ok := ptr.(map[string]string)
|
||||
if !ok {
|
||||
return errors.New("cannot convert to map of strings")
|
||||
}
|
||||
for k, v := range form {
|
||||
ptrMap[k] = v[len(v)-1] // pick last
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -19,3 +19,12 @@ func TestJSONBindingBindBody(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, "FOO", s.Foo)
|
||||
}
|
||||
|
||||
func TestJSONBindingBindBodyMap(t *testing.T) {
|
||||
s := make(map[string]string)
|
||||
err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO","hello":"world"}`), &s)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, s, 2)
|
||||
assert.Equal(t, "FOO", s["foo"])
|
||||
assert.Equal(t, "world", s["hello"])
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue