Skip to content

Commit 67f6346

Browse files
authored
Fix Bind() when target is array/slice and path/query params complain target not being struct (#1835)
For path/query params binding we do not try (silently return) to bind when target is not struct. Recreates PR #1574 and fixes #1565
1 parent dec96f0 commit 67f6346

File tree

3 files changed

+61
-17
lines changed

3 files changed

+61
-17
lines changed

bind.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
134134

135135
// !struct
136136
if typ.Kind() != reflect.Struct {
137+
if tag == "param" || tag == "query" {
138+
// incompatible type, data is probably to be found in the body
139+
return nil
140+
}
137141
return errors.New("binding element must be a struct")
138142
}
139143

bind_test.go

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"mime/multipart"
1010
"net/http"
1111
"net/http/httptest"
12+
"net/url"
1213
"reflect"
1314
"strconv"
1415
"strings"
@@ -187,19 +188,26 @@ func TestToMultipleFields(t *testing.T) {
187188

188189
func TestBindJSON(t *testing.T) {
189190
assert := assert.New(t)
190-
testBindOkay(assert, strings.NewReader(userJSON), MIMEApplicationJSON)
191+
testBindOkay(assert, strings.NewReader(userJSON), nil, MIMEApplicationJSON)
192+
testBindOkay(assert, strings.NewReader(userJSON), dummyQuery, MIMEApplicationJSON)
193+
testBindArrayOkay(assert, strings.NewReader(usersJSON), nil, MIMEApplicationJSON)
194+
testBindArrayOkay(assert, strings.NewReader(usersJSON), dummyQuery, MIMEApplicationJSON)
191195
testBindError(assert, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
192196
testBindError(assert, strings.NewReader(userJSONInvalidType), MIMEApplicationJSON, &json.UnmarshalTypeError{})
193197
}
194198

195199
func TestBindXML(t *testing.T) {
196200
assert := assert.New(t)
197201

198-
testBindOkay(assert, strings.NewReader(userXML), MIMEApplicationXML)
202+
testBindOkay(assert, strings.NewReader(userXML), nil, MIMEApplicationXML)
203+
testBindOkay(assert, strings.NewReader(userXML), dummyQuery, MIMEApplicationXML)
204+
testBindArrayOkay(assert, strings.NewReader(userXML), nil, MIMEApplicationXML)
205+
testBindArrayOkay(assert, strings.NewReader(userXML), dummyQuery, MIMEApplicationXML)
199206
testBindError(assert, strings.NewReader(invalidContent), MIMEApplicationXML, errors.New(""))
200207
testBindError(assert, strings.NewReader(userXMLConvertNumberError), MIMEApplicationXML, &strconv.NumError{})
201208
testBindError(assert, strings.NewReader(userXMLUnsupportedTypeError), MIMEApplicationXML, &xml.SyntaxError{})
202-
testBindOkay(assert, strings.NewReader(userXML), MIMETextXML)
209+
testBindOkay(assert, strings.NewReader(userXML), nil, MIMETextXML)
210+
testBindOkay(assert, strings.NewReader(userXML), dummyQuery, MIMETextXML)
203211
testBindError(assert, strings.NewReader(invalidContent), MIMETextXML, errors.New(""))
204212
testBindError(assert, strings.NewReader(userXMLConvertNumberError), MIMETextXML, &strconv.NumError{})
205213
testBindError(assert, strings.NewReader(userXMLUnsupportedTypeError), MIMETextXML, &xml.SyntaxError{})
@@ -208,7 +216,8 @@ func TestBindXML(t *testing.T) {
208216
func TestBindForm(t *testing.T) {
209217
assert := assert.New(t)
210218

211-
testBindOkay(assert, strings.NewReader(userForm), MIMEApplicationForm)
219+
testBindOkay(assert, strings.NewReader(userForm), nil, MIMEApplicationForm)
220+
testBindOkay(assert, strings.NewReader(userForm), dummyQuery, MIMEApplicationForm)
212221
e := New()
213222
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userForm))
214223
rec := httptest.NewRecorder()
@@ -336,14 +345,16 @@ func TestBindUnmarshalTextPtr(t *testing.T) {
336345
}
337346

338347
func TestBindMultipartForm(t *testing.T) {
339-
body := new(bytes.Buffer)
340-
mw := multipart.NewWriter(body)
348+
bodyBuffer := new(bytes.Buffer)
349+
mw := multipart.NewWriter(bodyBuffer)
341350
mw.WriteField("id", "1")
342351
mw.WriteField("name", "Jon Snow")
343352
mw.Close()
353+
body := bodyBuffer.Bytes()
344354

345355
assert := assert.New(t)
346-
testBindOkay(assert, body, mw.FormDataContentType())
356+
testBindOkay(assert, bytes.NewReader(body), nil, mw.FormDataContentType())
357+
testBindOkay(assert, bytes.NewReader(body), dummyQuery, mw.FormDataContentType())
347358
}
348359

349360
func TestBindUnsupportedMediaType(t *testing.T) {
@@ -547,9 +558,13 @@ func assertBindTestStruct(a *assert.Assertions, ts *bindTestStruct) {
547558
a.Equal("", ts.GetCantSet())
548559
}
549560

550-
func testBindOkay(assert *assert.Assertions, r io.Reader, ctype string) {
561+
func testBindOkay(assert *assert.Assertions, r io.Reader, query url.Values, ctype string) {
551562
e := New()
552-
req := httptest.NewRequest(http.MethodPost, "/", r)
563+
path := "/"
564+
if len(query) > 0 {
565+
path += "?" + query.Encode()
566+
}
567+
req := httptest.NewRequest(http.MethodPost, path, r)
553568
rec := httptest.NewRecorder()
554569
c := e.NewContext(req, rec)
555570
req.Header.Set(HeaderContentType, ctype)
@@ -561,6 +576,25 @@ func testBindOkay(assert *assert.Assertions, r io.Reader, ctype string) {
561576
}
562577
}
563578

579+
func testBindArrayOkay(assert *assert.Assertions, r io.Reader, query url.Values, ctype string) {
580+
e := New()
581+
path := "/"
582+
if len(query) > 0 {
583+
path += "?" + query.Encode()
584+
}
585+
req := httptest.NewRequest(http.MethodPost, path, r)
586+
rec := httptest.NewRecorder()
587+
c := e.NewContext(req, rec)
588+
req.Header.Set(HeaderContentType, ctype)
589+
u := []user{}
590+
err := c.Bind(&u)
591+
if assert.NoError(err) {
592+
assert.Equal(1, len(u))
593+
assert.Equal(1, u[0].ID)
594+
assert.Equal("Jon Snow", u[0].Name)
595+
}
596+
}
597+
564598
func testBindError(assert *assert.Assertions, r io.Reader, ctype string, expectedInternal error) {
565599
e := New()
566600
req := httptest.NewRequest(http.MethodPost, "/", r)
@@ -679,15 +713,16 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
679713
expect: &Opts{ID: 0, Node: "xxx"}, // query binding has already modified bind target
680714
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",
681715
},
682-
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
683-
name: "nok, GET query params bind failure - trying to bind json array to slice",
716+
{ // query param is ignored as we do not know where exactly to bind it in slice
717+
name: "ok, GET bind to struct slice, ignore query param",
684718
givenMethod: http.MethodGet,
685719
givenURL: "/api/real_node/endpoint?node=xxx",
686720
givenContent: strings.NewReader(`[{"id": 1}]`),
687721
whenNoPathParams: true,
688722
whenBindTarget: &[]Opts{},
689-
expect: &[]Opts{},
690-
expectError: "code=400, message=binding element must be a struct, internal=binding element must be a struct",
723+
expect: &[]Opts{
724+
{ID: 1, Node: ""},
725+
},
691726
},
692727
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
693728
name: "ok, POST binding to slice should not be affected query params types",
@@ -699,14 +734,15 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
699734
expect: &[]Opts{{ID: 1}},
700735
expectError: "",
701736
},
702-
{ // binding path params interferes with body. b.BindBody() should be used to bind only body to slice
703-
name: "nok, GET path params bind failure - trying to bind json array to slice",
737+
{ // path param is ignored as we do not know where exactly to bind it in slice
738+
name: "ok, GET bind to struct slice, ignore path param",
704739
givenMethod: http.MethodGet,
705740
givenURL: "/api/real_node/endpoint?node=xxx",
706741
givenContent: strings.NewReader(`[{"id": 1}]`),
707742
whenBindTarget: &[]Opts{},
708-
expect: &[]Opts{},
709-
expectError: "code=400, message=binding element must be a struct, internal=binding element must be a struct",
743+
expect: &[]Opts{
744+
{ID: 1, Node: ""},
745+
},
710746
},
711747
{
712748
name: "ok, GET body bind json array to slice",

echo_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net"
1111
"net/http"
1212
"net/http/httptest"
13+
"net/url"
1314
"os"
1415
"reflect"
1516
"strings"
@@ -30,6 +31,7 @@ type (
3031

3132
const (
3233
userJSON = `{"id":1,"name":"Jon Snow"}`
34+
usersJSON = `[{"id":1,"name":"Jon Snow"}]`
3335
userXML = `<user><id>1</id><name>Jon Snow</name></user>`
3436
userForm = `id=1&name=Jon Snow`
3537
invalidContent = "invalid content"
@@ -48,6 +50,8 @@ const userXMLPretty = `<user>
4850
<name>Jon Snow</name>
4951
</user>`
5052

53+
var dummyQuery = url.Values{"dummy": []string{"useless"}}
54+
5155
func TestEcho(t *testing.T) {
5256
e := New()
5357
req := httptest.NewRequest(http.MethodGet, "/", nil)

0 commit comments

Comments
 (0)