Skip to content

Commit

Permalink
binder: support for maps
Browse files Browse the repository at this point in the history
  • Loading branch information
robfig committed Jul 29, 2013
1 parent de4ff0c commit 4d9cd2f
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 9 deletions.
44 changes: 35 additions & 9 deletions binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ type Binder struct {
//
// Example
//
// Request:
// Request:
// url?id=123&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=rob
//
// Action:
// Action:
// Example.Action(id int, ol []int, ul []string, user User)
//
// Calls:
// Calls:
// Bind(params, "id", int): 123
// Bind(params, "ol", []int): {1, 2}
// Bind(params, "ul", []string): {"str", "array"}
Expand Down Expand Up @@ -183,6 +183,11 @@ var (
output[name] = t.Format(format)
},
}

MapBinder = Binder{
Bind: bindMap,
Unbind: unbindMap,
}
)

// Sadly, the binder lookups can not be declared initialized -- that results in
Expand All @@ -208,6 +213,7 @@ func init() {
KindBinders[reflect.Slice] = Binder{bindSlice, unbindSlice}
KindBinders[reflect.Struct] = Binder{bindStruct, unbindStruct}
KindBinders[reflect.Ptr] = PointerBinder
KindBinders[reflect.Map] = MapBinder

TypeBinders[reflect.TypeOf(time.Time{})] = TimeBinder

Expand Down Expand Up @@ -427,18 +433,38 @@ func bindByteArray(params *Params, name string, typ reflect.Type) reflect.Value
return reflect.Zero(typ)
}

func bindReader(params *Params, name string, typ reflect.Type) reflect.Value {
func bindReadSeeker(params *Params, name string, typ reflect.Type) reflect.Value {
if reader := getMultipartFile(params, name); reader != nil {
return reflect.ValueOf(reader.(io.Reader))
return reflect.ValueOf(reader.(io.ReadSeeker))
}
return reflect.Zero(typ)
}

func bindReadSeeker(params *Params, name string, typ reflect.Type) reflect.Value {
if reader := getMultipartFile(params, name); reader != nil {
return reflect.ValueOf(reader.(io.ReadSeeker))
// bindMap converts parameters using map syntax into the corresponding map. e.g.:
// params["a[5]"]=foo, name="a", typ=map[int]string => map[int]string{5: "foo"}
func bindMap(params *Params, name string, typ reflect.Type) reflect.Value {
var (
result = reflect.MakeMap(typ)
keyType = typ.Key()
valueType = typ.Elem()
)
for paramName, values := range params.Values {
if !strings.HasPrefix(paramName, name+"[") || paramName[len(paramName)-1] != ']' {
continue
}

key := paramName[len(name)+1 : len(paramName)-1]
result.SetMapIndex(BindValue(key, keyType), BindValue(values[0], valueType))
}
return result
}

func unbindMap(output map[string]string, name string, iface interface{}) {
mapValue := reflect.ValueOf(iface)
for _, key := range mapValue.MapKeys() {
Unbind(output, name+"["+fmt.Sprintf("%v", key.Interface())+"]",
mapValue.MapIndex(key).Interface())
}
return reflect.Zero(typ)
}

// Bind takes the name and type of the desired parameter and constructs it
Expand Down
28 changes: 28 additions & 0 deletions binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ var (
"arrC[0].B.Extra": {"foo"},
"arrC[1].Id": {"8"},
"arrC[1].Name": {"bill"},
"m[a]": {"foo"},
"m[b]": {"bar"},
"m2[1]": {"foo"},
"m2[2]": {"bar"},
"m3[a]": {"1"},
"m3[b]": {"2"},
"invalidInt": {"xyz"},
"invalidInt2": {""},
"invalidBool": {"xyz"},
Expand Down Expand Up @@ -124,6 +130,9 @@ var binderTestCases = map[string]interface{}{
Name: "bill",
},
},
"m": map[string]string{"a": "foo", "b": "bar"},
"m2": map[int]string{1: "foo", 2: "bar"},
"m3": map[string]int{"a": 1, "b": 2},

// TODO: Tests that use TypeBinders

Expand Down Expand Up @@ -257,6 +266,9 @@ var unbinderTestCases = map[string]interface{}{
Name: "bill",
},
},
"m": map[string]string{"a": "foo", "b": "bar"},
"m2": map[int]string{1: "foo", 2: "bar"},
"m3": map[string]int{"a": 1, "b": 2},
}

// Some of the unbinding results are not exactly what is in PARAMS, since it
Expand All @@ -281,6 +293,9 @@ var unbinderOverrideAnswers = map[string]map[string]string{
"arrC[1].Name": "bill",
"arrC[1].B.Extra": "",
},
"m": map[string]string{"m[a]": "foo", "m[b]": "bar"},
"m2": map[string]string{"m2[1]": "foo", "m2[2]": "bar"},
"m3": map[string]string{"m3[a]": "1", "m3[b]": "2"},
}

func TestUnbinder(t *testing.T) {
Expand Down Expand Up @@ -332,6 +347,19 @@ func valEq(t *testing.T, name string, actual, expected reflect.Value) {
case reflect.Ptr:
// Check equality on the element type.
valEq(t, name, actual.Elem(), expected.Elem())
case reflect.Map:
if !eq(t, name+" (len)", actual.Len(), expected.Len()) {
return
}
for _, key := range expected.MapKeys() {
expectedValue := expected.MapIndex(key)
actualValue := actual.MapIndex(key)
if actualValue.IsValid() {
valEq(t, fmt.Sprintf("%s[%s]", name, key), actualValue, expectedValue)
} else {
t.Errorf("Expected key %s not found", key)
}
}
default:
eq(t, name, actual.Interface(), expected.Interface())
}
Expand Down

0 comments on commit 4d9cd2f

Please sign in to comment.