Skip to content

Commit b065180

Browse files
authored
Merge pull request #1727 from aldas/bind_query_when_get_delete
Bind query params only for HTTP GET/DELETE methods
2 parents 936c48a + 65ea019 commit b065180

File tree

3 files changed

+97
-19
lines changed

3 files changed

+97
-19
lines changed

Makefile

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
PKG := "github.com/labstack/echo"
2+
PKG_LIST := $(shell go list ${PKG}/...)
3+
14
tag:
25
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
36
@git tag|grep -v ^v
7+
8+
.DEFAULT_GOAL := check
9+
check: lint vet race ## Check project
10+
11+
init:
12+
@go get -u golang.org/x/lint/golint
13+
14+
lint: ## Lint the files
15+
@golint -set_exit_status ${PKG_LIST}
16+
17+
vet: ## Vet the files
18+
@go vet ${PKG_LIST}
19+
20+
test: ## Run tests
21+
@go test -short ${PKG_LIST}
22+
23+
race: ## Run tests with data race detector
24+
@go test -race ${PKG_LIST}
25+
26+
help: ## Display this help screen
27+
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

bind.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,20 @@ func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
9898
}
9999

100100
// Bind implements the `Binder#Bind` function.
101+
// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
102+
// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
101103
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
102104
if err := b.BindPathParams(c, i); err != nil {
103105
return err
104106
}
105-
if err = b.BindQueryParams(c, i); err != nil {
106-
return err
107+
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
108+
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
109+
// i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
110+
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
111+
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
112+
if err = b.BindQueryParams(c, i); err != nil {
113+
return err
114+
}
107115
}
108116
return b.BindBody(c, i)
109117
}

bind_test.go

+63-17
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
559559
// binding is done in steps and one source could overwrite previous source binded data
560560
// these tests are to document this behaviour and detect further possible regressions when bind implementation is changed
561561

562-
type Node struct {
562+
type Opts struct {
563563
ID int `json:"id"`
564564
Node string `json:"node"`
565565
}
@@ -575,59 +575,105 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
575575
expectError string
576576
}{
577577
{
578-
name: "ok, POST bind to struct with: path param + query param + empty body",
578+
name: "ok, POST bind to struct with: path param + query param + body",
579579
givenMethod: http.MethodPost,
580580
givenURL: "/api/real_node/endpoint?node=xxx",
581581
givenContent: strings.NewReader(`{"id": 1}`),
582-
expect: &Node{ID: 1, Node: "xxx"}, // in current implementation query params has higher priority than path params
582+
expect: &Opts{ID: 1, Node: "node_from_path"}, // query params are not used, node is filled from path
583+
},
584+
{
585+
name: "ok, PUT bind to struct with: path param + query param + body",
586+
givenMethod: http.MethodPut,
587+
givenURL: "/api/real_node/endpoint?node=xxx",
588+
givenContent: strings.NewReader(`{"id": 1}`),
589+
expect: &Opts{ID: 1, Node: "node_from_path"}, // query params are not used
590+
},
591+
{
592+
name: "ok, GET bind to struct with: path param + query param + body",
593+
givenMethod: http.MethodGet,
594+
givenURL: "/api/real_node/endpoint?node=xxx",
595+
givenContent: strings.NewReader(`{"id": 1}`),
596+
expect: &Opts{ID: 1, Node: "xxx"}, // query overwrites previous path value
597+
},
598+
{
599+
name: "ok, GET bind to struct with: path param + query param + body",
600+
givenMethod: http.MethodGet,
601+
givenURL: "/api/real_node/endpoint?node=xxx",
602+
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
603+
expect: &Opts{ID: 1, Node: "zzz"}, // body is binded last and overwrites previous (path,query) values
604+
},
605+
{
606+
name: "ok, DELETE bind to struct with: path param + query param + body",
607+
givenMethod: http.MethodDelete,
608+
givenURL: "/api/real_node/endpoint?node=xxx",
609+
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
610+
expect: &Opts{ID: 1, Node: "zzz"}, // for DELETE body is binded after query params
583611
},
584612
{
585-
name: "ok, POST bind to struct with: path param + empty body",
613+
name: "ok, POST bind to struct with: path param + body",
586614
givenMethod: http.MethodPost,
587615
givenURL: "/api/real_node/endpoint",
588616
givenContent: strings.NewReader(`{"id": 1}`),
589-
expect: &Node{ID: 1, Node: "real_node"},
617+
expect: &Opts{ID: 1, Node: "node_from_path"},
590618
},
591619
{
592620
name: "ok, POST bind to struct with path + query + body = body has priority",
593621
givenMethod: http.MethodPost,
594622
givenURL: "/api/real_node/endpoint?node=xxx",
595623
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
596-
expect: &Node{ID: 1, Node: "zzz"}, // field value from content has higher priority
624+
expect: &Opts{ID: 1, Node: "zzz"}, // field value from content has higher priority
597625
},
598626
{
599627
name: "nok, POST body bind failure",
600628
givenMethod: http.MethodPost,
601629
givenURL: "/api/real_node/endpoint?node=xxx",
602630
givenContent: strings.NewReader(`{`),
603-
expect: &Node{ID: 0, Node: "xxx"}, // query binding has already modified bind target
631+
expect: &Opts{ID: 0, Node: "node_from_path"}, // query binding has already modified bind target
604632
expectError: "code=400, message=unexpected EOF, internal=unexpected EOF",
605633
},
634+
{
635+
name: "nok, GET with body bind failure when types are not convertible",
636+
givenMethod: http.MethodGet,
637+
givenURL: "/api/real_node/endpoint?id=nope",
638+
givenContent: strings.NewReader(`{"id": 1, "node": "zzz"}`),
639+
expect: &Opts{ID: 0, Node: "node_from_path"}, // path params binding has already modified bind target
640+
expectError: "code=400, message=strconv.ParseInt: parsing \"nope\": invalid syntax, internal=strconv.ParseInt: parsing \"nope\": invalid syntax",
641+
},
606642
{
607643
name: "nok, GET body bind failure - trying to bind json array to struct",
608644
givenMethod: http.MethodGet,
609645
givenURL: "/api/real_node/endpoint?node=xxx",
610646
givenContent: strings.NewReader(`[{"id": 1}]`),
611-
expect: &Node{ID: 0, Node: "xxx"}, // query binding has already modified bind target
612-
expectError: "code=400, message=Unmarshal type error: expected=echo.Node, got=array, field=, offset=1, internal=json: cannot unmarshal array into Go value of type echo.Node",
647+
expect: &Opts{ID: 0, Node: "xxx"}, // query binding has already modified bind target
648+
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",
613649
},
614650
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
615651
name: "nok, GET query params bind failure - trying to bind json array to slice",
616652
givenMethod: http.MethodGet,
617653
givenURL: "/api/real_node/endpoint?node=xxx",
618654
givenContent: strings.NewReader(`[{"id": 1}]`),
619655
whenNoPathParams: true,
620-
whenBindTarget: &[]Node{},
621-
expect: &[]Node{},
656+
whenBindTarget: &[]Opts{},
657+
expect: &[]Opts{},
622658
expectError: "code=400, message=binding element must be a struct, internal=binding element must be a struct",
623659
},
660+
{ // binding query params interferes with body. b.BindBody() should be used to bind only body to slice
661+
name: "ok, POST binding to slice should not be affected query params types",
662+
givenMethod: http.MethodPost,
663+
givenURL: "/api/real_node/endpoint?id=nope&node=xxx",
664+
givenContent: strings.NewReader(`[{"id": 1}]`),
665+
whenNoPathParams: true,
666+
whenBindTarget: &[]Opts{},
667+
expect: &[]Opts{{ID: 1}},
668+
expectError: "",
669+
},
624670
{ // binding path params interferes with body. b.BindBody() should be used to bind only body to slice
625671
name: "nok, GET path params bind failure - trying to bind json array to slice",
626672
givenMethod: http.MethodGet,
627673
givenURL: "/api/real_node/endpoint?node=xxx",
628674
givenContent: strings.NewReader(`[{"id": 1}]`),
629-
whenBindTarget: &[]Node{},
630-
expect: &[]Node{},
675+
whenBindTarget: &[]Opts{},
676+
expect: &[]Opts{},
631677
expectError: "code=400, message=binding element must be a struct, internal=binding element must be a struct",
632678
},
633679
{
@@ -636,8 +682,8 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
636682
givenURL: "/api/real_node/endpoint",
637683
givenContent: strings.NewReader(`[{"id": 1}]`),
638684
whenNoPathParams: true,
639-
whenBindTarget: &[]Node{},
640-
expect: &[]Node{{ID: 1, Node: ""}},
685+
whenBindTarget: &[]Opts{},
686+
expect: &[]Opts{{ID: 1, Node: ""}},
641687
expectError: "",
642688
},
643689
}
@@ -653,14 +699,14 @@ func TestDefaultBinder_BindToStructFromMixedSources(t *testing.T) {
653699

654700
if !tc.whenNoPathParams {
655701
c.SetParamNames("node")
656-
c.SetParamValues("real_node")
702+
c.SetParamValues("node_from_path")
657703
}
658704

659705
var bindTarget interface{}
660706
if tc.whenBindTarget != nil {
661707
bindTarget = tc.whenBindTarget
662708
} else {
663-
bindTarget = &Node{}
709+
bindTarget = &Opts{}
664710
}
665711
b := new(DefaultBinder)
666712

0 commit comments

Comments
 (0)