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"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -200,6 +201,12 @@ func TestBindingJSONDisallowUnknownFields(t *testing.T) {
|
||||||
`{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`)
|
`{"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) {
|
func TestBindingForm(t *testing.T) {
|
||||||
testFormBinding(t, "POST",
|
testFormBinding(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
|
@ -336,6 +343,37 @@ func TestBindingFormForType(t *testing.T) {
|
||||||
"", "", "StructPointer")
|
"", "", "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) {
|
func TestBindingQuery(t *testing.T) {
|
||||||
testQueryBinding(t, "POST",
|
testQueryBinding(t, "POST",
|
||||||
"/?foo=bar&bar=foo", "/",
|
"/?foo=bar&bar=foo", "/",
|
||||||
|
@ -366,6 +404,28 @@ func TestBindingQueryBoolFail(t *testing.T) {
|
||||||
"bool_foo=unused", "")
|
"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) {
|
func TestBindingXML(t *testing.T) {
|
||||||
testBodyBinding(t,
|
testBodyBinding(t,
|
||||||
XML, "xml",
|
XML, "xml",
|
||||||
|
@ -387,6 +447,13 @@ func TestBindingYAML(t *testing.T) {
|
||||||
`foo: bar`, `bar: foo`)
|
`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) {
|
func TestBindingYAMLFail(t *testing.T) {
|
||||||
testBodyBindingFail(t,
|
testBodyBindingFail(t,
|
||||||
YAML, "yaml",
|
YAML, "yaml",
|
||||||
|
@ -1114,6 +1181,32 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
|
||||||
assert.Error(t, err)
|
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) {
|
func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,21 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
||||||
var emptyField = reflect.StructField{}
|
var emptyField = reflect.StructField{}
|
||||||
|
|
||||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
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)
|
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):]
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "FOO", s.Foo)
|
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