diff --git a/tonic/handler.go b/tonic/handler.go index bcb99e8..f21a9d5 100644 --- a/tonic/handler.go +++ b/tonic/handler.go @@ -2,6 +2,7 @@ package tonic import ( "fmt" + "net" "reflect" "runtime" "strconv" @@ -252,36 +253,41 @@ func bind(c *gin.Context, v reflect.Value, tag string, extract extractor) error if len(fieldValues) > 1 && (kind != reflect.Slice && kind != reflect.Array) { return BindError{field: ft.Name, typ: t, message: "multiple values not supported"} } - // Ensure that the number of values to fill does - // not exceed the length of a field of type Array. - if kind == reflect.Array { - if field.Len() != len(fieldValues) { - return BindError{field: ft.Name, typ: t, message: fmt.Sprintf( - "parameter expect %d values, got %d", field.Len(), len(fieldValues)), + + // Don't consider net.IP as an array. + if field.Type() != reflect.TypeOf(new(net.IP)).Elem() { + // Ensure that the number of values to fill does + // not exceed the length of a field of type Array. + if kind == reflect.Array { + if field.Len() != len(fieldValues) { + return BindError{field: ft.Name, typ: t, message: fmt.Sprintf( + "parameter expect %d values, got %d", field.Len(), len(fieldValues)), + } } } - } - if kind == reflect.Slice || kind == reflect.Array { - // Create a new slice with an adequate - // length to set all the values. - if kind == reflect.Slice { - field.Set(reflect.MakeSlice(field.Type(), 0, len(fieldValues))) - } - for i, val := range fieldValues { - v := reflect.New(field.Type().Elem()).Elem() - err = bindStringValue(val, v) - if err != nil { - return BindError{field: ft.Name, typ: t, message: err.Error()} - } + if kind == reflect.Slice || kind == reflect.Array { + // Create a new slice with an adequate + // length to set all the values. if kind == reflect.Slice { - field.Set(reflect.Append(field, v)) + field.Set(reflect.MakeSlice(field.Type(), 0, len(fieldValues))) } - if kind == reflect.Array { - field.Index(i).Set(v) + for i, val := range fieldValues { + v := reflect.New(field.Type().Elem()).Elem() + err = bindStringValue(val, v) + if err != nil { + return BindError{field: ft.Name, typ: t, message: err.Error()} + } + if kind == reflect.Slice { + field.Set(reflect.Append(field, v)) + } + if kind == reflect.Array { + field.Index(i).Set(v) + } } + continue } - continue } + // Handle enum values. enum := ft.Tag.Get(EnumTag) if enum != "" { diff --git a/tonic/tonic_test.go b/tonic/tonic_test.go index 694c159..bf2aa2e 100644 --- a/tonic/tonic_test.go +++ b/tonic/tonic_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "net" "net/http" "strings" "testing" @@ -78,6 +79,9 @@ func TestPathQuery(t *testing.T) { tester.AddCall("query-optional", "GET", "/query?param=foo¶m-optional=bar", "").Checkers(iffy.ExpectStatus(200), expectString("param-optional", "bar")) tester.AddCall("query-int", "GET", "/query?param=foo¶m-int=42", "").Checkers(iffy.ExpectStatus(200), expectInt("param-int", 42)) tester.AddCall("query-multiple", "GET", "/query?param=foo¶ms=foo¶ms=bar", "").Checkers(iffy.ExpectStatus(200), expectStringArr("params", "foo", "bar")) + tester.AddCall("query-array-ok", "GET", "/query?param=foo¶m-array=foo¶m-array=bar", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-array", "foo", "bar")) + tester.AddCall("query-array-error-1", "GET", "/query?param=foo¶m-array=foo", "").Checkers(iffy.ExpectStatus(400)) + tester.AddCall("query-array-error-2", "GET", "/query?param=foo¶m-array=foo¶m-array=bar¶m-array=overflow", "").Checkers(iffy.ExpectStatus(400)) tester.AddCall("query-bool", "GET", "/query?param=foo¶m-bool=true", "").Checkers(iffy.ExpectStatus(200), expectBool("param-bool", true)) tester.AddCall("query-override-default", "GET", "/query?param=foo¶m-default=bla", "").Checkers(iffy.ExpectStatus(200), expectString("param-default", "bla")) tester.AddCall("query-ptr", "GET", "/query?param=foo¶m-ptr=bar", "").Checkers(iffy.ExpectStatus(200), expectString("param-ptr", "bar")) @@ -87,6 +91,15 @@ func TestPathQuery(t *testing.T) { tester.AddCall("query-complex", "GET", fmt.Sprintf("/query?param=foo¶m-complex=%s", now), "").Checkers(iffy.ExpectStatus(200), expectString("param-complex", string(now))) + // IP. + tester.AddCall("query-ip-ok", "GET", "/query?param=foo¶m-ip=127.0.0.1", "").Checkers(iffy.ExpectStatus(200), expectString("param-ip", "127.0.0.1")) + tester.AddCall("query-ip-error", "GET", "/query?param=foo¶m-ip=foo", "").Checkers(iffy.ExpectStatus(400)) + tester.AddCall("query-ip-array-ok", "GET", "/query?param=foo¶m-ip-array=1.1.1.1¶m-ip-array=2.2.2.2", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-ip-array", "1.1.1.1", "2.2.2.2")) + tester.AddCall("query-ip-array-error-1", "GET", "/query?param=foo¶m-ip-array=1.1.1.1¶m-ip-array=2.2.2.2¶m-ip-array=3.3.3.3", "").Checkers(iffy.ExpectStatus(400)) + tester.AddCall("query-ip-array-error-2", "GET", "/query?param=foo¶m-ip-array=1.1.1.1¶m-ip-array=foo", "").Checkers(iffy.ExpectStatus(400)) + tester.AddCall("query-ip-slice-ok", "GET", "/query?param=foo¶m-ip-slice=1.1.1.1¶m-ip-slice=2.2.2.2", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-ip-slice", "1.1.1.1", "2.2.2.2")) + tester.AddCall("query-ip-slice-error", "GET", "/query?param=foo¶m-ip-slice=1.1.1.1¶m-ip-slice=foo", "").Checkers(iffy.ExpectStatus(400)) + // Explode. tester.AddCall("query-explode", "GET", "/query?param=foo¶m-explode=a¶m-explode=b¶m-explode=c", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-explode", "a", "b", "c")) tester.AddCall("query-explode-disabled-ok", "GET", "/query?param=foo¶m-explode-disabled=x,y,z", "").Checkers(iffy.ExpectStatus(200), expectStringArr("param-explode-disabled", "x", "y", "z")) @@ -154,6 +167,7 @@ type queryIn struct { Param string `query:"param" json:"param" validate:"required"` ParamOptional string `query:"param-optional" json:"param-optional"` Params []string `query:"params" json:"params"` + ParamArray [2]string `query:"param-array" json:"param-array"` ParamInt int `query:"param-int" json:"param-int"` ParamBool bool `query:"param-bool" json:"param-bool"` ParamDefault string `query:"param-default" json:"param-default" default:"default" validate:"required"` @@ -164,6 +178,9 @@ type queryIn struct { ParamExplodeString string `query:"param-explode-string" json:"param-explode-string" explode:"true"` ParamExplodeDefault []string `query:"param-explode-default" json:"param-explode-default" default:"1,2,3" explode:"true"` ParamExplodeDefaultDisabled []string `query:"param-explode-disabled-default" json:"param-explode-disabled-default" default:"1,2,3" explode:"false"` + ParamIP net.IP `query:"param-ip" json:"param-ip"` + ParamIPArray [2]net.IP `query:"param-ip-array" json:"param-ip-array"` + ParamIPSlice []net.IP `query:"param-ip-slice" json:"param-ip-slice"` *DoubleEmbedded }