diff --git a/bind.go b/bind.go
index 16c3b7adf..08d398916 100644
--- a/bind.go
+++ b/bind.go
@@ -134,6 +134,10 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
// !struct
if typ.Kind() != reflect.Struct {
+ if tag == "param" || tag == "query" {
+ // incompatible type, data is probably to be found in the body
+ return nil
+ }
return errors.New("binding element must be a struct")
}
diff --git a/bind_test.go b/bind_test.go
index e8868b35b..73398034e 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -9,6 +9,7 @@ import (
"mime/multipart"
"net/http"
"net/http/httptest"
+ "net/url"
"reflect"
"strconv"
"strings"
@@ -187,7 +188,10 @@ func TestToMultipleFields(t *testing.T) {
func TestBindJSON(t *testing.T) {
assert := assert.New(t)
- testBindOkay(assert, strings.NewReader(userJSON), MIMEApplicationJSON)
+ testBindOkay(assert, strings.NewReader(userJSON), nil, MIMEApplicationJSON)
+ testBindOkay(assert, strings.NewReader(userJSON), dummyQuery, MIMEApplicationJSON)
+ testBindArrayOkay(assert, strings.NewReader(usersJSON), nil, MIMEApplicationJSON)
+ testBindArrayOkay(assert, strings.NewReader(usersJSON), dummyQuery, MIMEApplicationJSON)
testBindError(assert, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
testBindError(assert, strings.NewReader(userJSONInvalidType), MIMEApplicationJSON, &json.UnmarshalTypeError{})
}
@@ -195,11 +199,15 @@ func TestBindJSON(t *testing.T) {
func TestBindXML(t *testing.T) {
assert := assert.New(t)
- testBindOkay(assert, strings.NewReader(userXML), MIMEApplicationXML)
+ testBindOkay(assert, strings.NewReader(userXML), nil, MIMEApplicationXML)
+ testBindOkay(assert, strings.NewReader(userXML), dummyQuery, MIMEApplicationXML)
+ testBindArrayOkay(assert, strings.NewReader(userXML), nil, MIMEApplicationXML)
+ testBindArrayOkay(assert, strings.NewReader(userXML), dummyQuery, MIMEApplicationXML)
testBindError(assert, strings.NewReader(invalidContent), MIMEApplicationXML, errors.New(""))
testBindError(assert, strings.NewReader(userXMLConvertNumberError), MIMEApplicationXML, &strconv.NumError{})
testBindError(assert, strings.NewReader(userXMLUnsupportedTypeError), MIMEApplicationXML, &xml.SyntaxError{})
- testBindOkay(assert, strings.NewReader(userXML), MIMETextXML)
+ testBindOkay(assert, strings.NewReader(userXML), nil, MIMETextXML)
+ testBindOkay(assert, strings.NewReader(userXML), dummyQuery, MIMETextXML)
testBindError(assert, strings.NewReader(invalidContent), MIMETextXML, errors.New(""))
testBindError(assert, strings.NewReader(userXMLConvertNumberError), MIMETextXML, &strconv.NumError{})
testBindError(assert, strings.NewReader(userXMLUnsupportedTypeError), MIMETextXML, &xml.SyntaxError{})
@@ -208,7 +216,8 @@ func TestBindXML(t *testing.T) {
func TestBindForm(t *testing.T) {
assert := assert.New(t)
- testBindOkay(assert, strings.NewReader(userForm), MIMEApplicationForm)
+ testBindOkay(assert, strings.NewReader(userForm), nil, MIMEApplicationForm)
+ testBindOkay(assert, strings.NewReader(userForm), dummyQuery, MIMEApplicationForm)
e := New()
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userForm))
rec := httptest.NewRecorder()
@@ -336,14 +345,16 @@ func TestBindUnmarshalTextPtr(t *testing.T) {
}
func TestBindMultipartForm(t *testing.T) {
- body := new(bytes.Buffer)
- mw := multipart.NewWriter(body)
+ bodyBuffer := new(bytes.Buffer)
+ mw := multipart.NewWriter(bodyBuffer)
mw.WriteField("id", "1")
mw.WriteField("name", "Jon Snow")
mw.Close()
+ body := bodyBuffer.Bytes()
assert := assert.New(t)
- testBindOkay(assert, body, mw.FormDataContentType())
+ testBindOkay(assert, bytes.NewReader(body), nil, mw.FormDataContentType())
+ testBindOkay(assert, bytes.NewReader(body), dummyQuery, mw.FormDataContentType())
}
func TestBindUnsupportedMediaType(t *testing.T) {
@@ -547,9 +558,13 @@ func assertBindTestStruct(a *assert.Assertions, ts *bindTestStruct) {
a.Equal("", ts.GetCantSet())
}
-func testBindOkay(assert *assert.Assertions, r io.Reader, ctype string) {
+func testBindOkay(assert *assert.Assertions, r io.Reader, query url.Values, ctype string) {
e := New()
- req := httptest.NewRequest(http.MethodPost, "/", r)
+ path := "/"
+ if len(query) > 0 {
+ path += "?" + query.Encode()
+ }
+ req := httptest.NewRequest(http.MethodPost, path, r)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
req.Header.Set(HeaderContentType, ctype)
@@ -561,6 +576,25 @@ func testBindOkay(assert *assert.Assertions, r io.Reader, ctype string) {
}
}
+func testBindArrayOkay(assert *assert.Assertions, r io.Reader, query url.Values, ctype string) {
+ e := New()
+ path := "/"
+ if len(query) > 0 {
+ path += "?" + query.Encode()
+ }
+ req := httptest.NewRequest(http.MethodPost, path, r)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(req, rec)
+ req.Header.Set(HeaderContentType, ctype)
+ u := []user{}
+ err := c.Bind(&u)
+ if assert.NoError(err) {
+ assert.Equal(1, len(u))
+ assert.Equal(1, u[0].ID)
+ assert.Equal("Jon Snow", u[0].Name)
+ }
+}
+
func testBindError(assert *assert.Assertions, r io.Reader, ctype string, expectedInternal error) {
e := New()
req := httptest.NewRequest(http.MethodPost, "/", r)
@@ -679,15 +713,16 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
expect: &Opts{ID: 0, Node: "xxx"}, // query binding has already modified bind target
expectError: "code=400, message=Unmarshal type error: expected=echo.Opts, got=array, field=, offset=1, internal=json: cannot unmarshal array into Go value of type echo.Opts",
},
- { // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
- name: "nok, GET query params bind failure - trying to bind json array to slice",
+ { // query param is ignored as we do not know where exactly to bind it in slice
+ name: "ok, GET bind to struct slice, ignore query param",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`[{"id": 1}]`),
whenNoPathParams: true,
whenBindTarget: &[]Opts{},
- expect: &[]Opts{},
- expectError: "code=400, message=binding element must be a struct, internal=binding element must be a struct",
+ expect: &[]Opts{
+ {ID: 1, Node: ""},
+ },
},
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
name: "ok, POST binding to slice should not be affected query params types",
@@ -699,14 +734,15 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
expect: &[]Opts{{ID: 1}},
expectError: "",
},
- { // binding path params interferes with body. b.BindBody() should be used to bind only body to slice
- name: "nok, GET path params bind failure - trying to bind json array to slice",
+ { // path param is ignored as we do not know where exactly to bind it in slice
+ name: "ok, GET bind to struct slice, ignore path param",
givenMethod: http.MethodGet,
givenURL: "/api/real_node/endpoint?node=xxx",
givenContent: strings.NewReader(`[{"id": 1}]`),
whenBindTarget: &[]Opts{},
- expect: &[]Opts{},
- expectError: "code=400, message=binding element must be a struct, internal=binding element must be a struct",
+ expect: &[]Opts{
+ {ID: 1, Node: ""},
+ },
},
{
name: "ok, GET body bind json array to slice",
diff --git a/echo_test.go b/echo_test.go
index 35c79cbc0..58ecea741 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -10,6 +10,7 @@ import (
"net"
"net/http"
"net/http/httptest"
+ "net/url"
"os"
"reflect"
"strings"
@@ -30,6 +31,7 @@ type (
const (
userJSON = `{"id":1,"name":"Jon Snow"}`
+ usersJSON = `[{"id":1,"name":"Jon Snow"}]`
userXML = `1Jon Snow`
userForm = `id=1&name=Jon Snow`
invalidContent = "invalid content"
@@ -48,6 +50,8 @@ const userXMLPretty = `
Jon Snow
`
+var dummyQuery = url.Values{"dummy": []string{"useless"}}
+
func TestEcho(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/", nil)