Repository: labstack/echo
File: bind.go (Go)

Code before:

type (
	// Binder is the interface that wraps the Bind method.
	Binder interface {
		Bind(i interface{}, c Context) error
	}

	// DefaultBinder is the default implementation of the Binder interface.
	DefaultBinder struct{}

	// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
	BindUnmarshaler interface {
		// UnmarshalParam decodes and assigns a value from an form or query param.
		UnmarshalParam(param string) error
	}
)

// Bind implements the `Binder#Bind` function.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
	req := c.Request()

	paramNames := c.ParamNames()
	paramValues := c.ParamValues()
	params := make(map[string][]string)
	for i, name := range paramNames {
		// Fix for an echo bug where a param name would show up which dont exist if its name contains "id" e.g. "userid"
		names := strings.Split(name, ",")
		for _, n := range names {
			params[n] = append(params[name], paramValues[i])
		}
	}
	b.bindData(i, params, "param")

	if req.ContentLength == 0 {
		if req.Method == GET || req.Method == DELETE {
			if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
				return NewHTTPError(http.StatusBadRequest, err.Error())
			}
			return
		}
		return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
	}
	ctype := req.Header.Get(HeaderContentType)
	switch {
	case strings.HasPrefix(ctype, MIMEApplicationJSON):
		if err = json.NewDecoder(req.Body).Decode(i); err != nil {
			if ute, ok := err.(*json.UnmarshalTypeError); ok {
				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset))
			} else if se, ok := err.(*json.SyntaxError); ok {
				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
			} else {

Review comment (bug):
```go
  // id-named fields
  type testUser struct {
		ID      int `param:"userid"`
		NameID  int `param:"nameid"`
		GroupID int `param:"groupid"`
		SameID  int `param:"sameid"`
	}

...

	t.Run("should be ok for id-named fields", func(t *testing.T) {
		req := httptest.NewRequest(echo.GET, "/1/2/3/4", nil)
		testHandler := func(ctx echo.Context) error {
			u := new(testUser)
			err := ctx.Bind(u)
			if assert.NoError(t, err) {
				assert.Equal(t, 1, u.ID)
				assert.Equal(t, 2, u.NameID)
				assert.Equal(t, 3, u.GroupID)
				assert.Equal(t, 4, u.SameID)
			}

			return nil
		}
		e.GET("/:userid/:nameid/:groupid/:sameid", testHandler)
		e.ServeHTTP(rec, req)
	})
```

can't reproduce this bug

Code after:
)

type (
	// Binder is the interface that wraps the Bind method.
	Binder interface {
		Bind(i interface{}, c Context) error
	}

	// DefaultBinder is the default implementation of the Binder interface.
	DefaultBinder struct{}

	// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
	// Types that don't implement this, but do implement encoding.TextUnmarshaler
	// will use that interface instead.
	BindUnmarshaler interface {
		// UnmarshalParam decodes and assigns a value from an form or query param.
		UnmarshalParam(param string) error
	}
)

// Bind implements the `Binder#Bind` function.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
	req := c.Request()

	paramNames := c.ParamNames()
	paramValues := c.ParamValues()
	params := make(map[string][]string)
	for i, name := range paramNames {
		params[name] = []string{paramValues[i]}
	}
	if err := b.bindData(i, params, "param"); err != nil {
		return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
	}

	if req.ContentLength == 0 {
		if req.Method == http.MethodGet || req.Method == http.MethodDelete {
			if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
				return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
			}
			return
		}
		return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
	}
	ctype := req.Header.Get(HeaderContentType)
	switch {
	case strings.HasPrefix(ctype, MIMEApplicationJSON):
		if err = json.NewDecoder(req.Body).Decode(i); err != nil {
			if ute, ok := err.(*json.UnmarshalTypeError); ok {
				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
			} else if se, ok := err.(*json.SyntaxError); ok {
				return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)