Skip to content

Commit

Permalink
Allow bind with a map[string]string
Browse files Browse the repository at this point in the history
  • Loading branch information
ItalyPaleAle committed Aug 29, 2020
1 parent b94d23d commit 94e50b3
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 0 deletions.
93 changes: 93 additions & 0 deletions binding/binding_test.go
Expand Up @@ -13,6 +13,7 @@ import (
"mime/multipart"
"net/http"
"os"
"reflect"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -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",
"/", "/",
Expand Down Expand Up @@ -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", "/",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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())

Expand Down
37 changes: 37 additions & 0 deletions binding/form_mapping.go
Expand Up @@ -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)
}

Expand Down Expand Up @@ -349,3 +364,25 @@ 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
}
} else {
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
}
9 changes: 9 additions & 0 deletions binding/json_test.go
Expand Up @@ -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"])
}

0 comments on commit 94e50b3

Please sign in to comment.