Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"binding element must be a struct" introduced with path params binding #1356

Closed
3 tasks done
andreiavrammsd opened this issue Jun 27, 2019 · 39 comments
Closed
3 tasks done
Assignees
Labels

Comments

@andreiavrammsd
Copy link
Contributor

Issue Description

This is simillar to #988 and introduced by 858270f in a specific situation: taking the address of your struct twice and sending it to bind.

u := &user{}
c.Bind(&u)

Because of the newly introduced params binding, when the struct's type is checked, it fails here:

func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
	typ := reflect.TypeOf(ptr).Elem()
	val := reflect.ValueOf(ptr).Elem()

	if typ.Kind() != reflect.Struct {
		return errors.New("binding element must be a struct")
	}

Before the params binding was introduced, bindData was not called for POST/PUT/PATCH, because for these methods you directly decode the body into the given struct.

And a mention: If you have id both as path param and json property in the request body, the value from the body will overwrite the one from the path. This could lead to confusion and bugs in projects.

Checklist

  • Dependencies installed
  • No typos
  • Searched existing issues and docs

Expected behaviour

If this is known and planned for next major version, than it's OK, else compatibility should not be broken.

Actual behaviour

See Expected behaviour

Steps to reproduce

Take the address of the struct sent at bind twice and make a request to a server (see Working code to debug).

curl -X PUT -H "Content-Type: application/json" -d '{"name":"John"}' localhost:8811/users/1

Working code to debug

package main

import (
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
)

type user struct {
	ID int `json:"id" param:"id"`
	Name string `json:"name"`
}

func main() {
	s := echo.New()
	s.Use(middleware.Logger())

	s.PUT("/users/:id", func(c echo.Context) error {
		u := &user{}
		if err := c.Bind(&u); err != nil {
			log.Fatal(err)
		}
		log.Print(u)
		return nil
	})

	s.Start(":8811")
}

Version/commit

858270f

@andreiavrammsd
Copy link
Contributor Author

Also, binding to slices is returning the same error.

var items []uuid.UUID
c.Bind(&items)

@xXLokerXx
Copy link

For me this causes to all my inner structures in the field id change to that value and this really made a mess in my projects because this updates all inner structures to zero values (i'm working with gorm and i'm using the save function)

example:

package main

import (
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
)

type user struct {
	ID int `json:"id" param:"id"`
	Name string `json:"name"`
	BranchID uint
	Branch Branch
}

type branch struct {
	ID int
	Name string
}

func main() {
	s := echo.New()
	s.Use(middleware.Logger())

	s.PUT("/users/:id", func(c echo.Context) error {
		u := &user{}
		if err := c.Bind(&u); err != nil {
			log.Fatal(err)
		}
		log.Print(u)
		return nil
	})

	s.Start(":7070")
}

This set the ID in branch relation equals to the ID of user

@xXLokerXx
Copy link

To me, as a quick fix, just change the name of the parameter to pk insted of id.

I hope this will fix in a couple of days this will helpfull for me.

@radaiming
Copy link

Also, binding to slices is returning the same error.

var items []uuid.UUID
c.Bind(&items)

Breakage on existing feature really hurts, if couldn't be fixed quickly, maybe a temporary revert and a new version is a good idea?

@cdukes
Copy link

cdukes commented Oct 10, 2019

+1 On this issue. This is a bug that breaks existing functionality and has been an issue since early August. A patch release would be appreciated, so that I don't have to pin at v4.1.6 indefinitely.

Thanks!

@ryanking8215
Copy link

ryanking8215 commented Nov 12, 2019

This bug exists in 4.1.11 which is the latest release.
And it really stops the general restful api usage, i.e.

package main

import (
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "github.com/labstack/gommon/log"
)

func main() {
    s := echo.New()
    s.Use(middleware.Logger())

    s.PUT("/users/:id", func(c echo.Context) error {
        var data interface{}
        if err := c.Bind(&data); err != nil {
            log.Fatal(err)
        }
        log.Print(data)
        return nil
    })

    s.Start(":8811")
}

request:

curl -X PUT -H "Content-Type: application/json" -d '{"name":"John"}' localhost:8811/users/1

I have read the Bind source code.

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

        names := c.ParamNames()
        values := c.ParamValues()
        params := map[string][]string{}
        for i, name := range names {
                params[name] = []string{values[i]}
        }
        if err := b.bindData(i, params, "param"); err != nil {
                return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
        }
        if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
                return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
        }
        if req.ContentLength == 0 {
                return
        }
        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)
                        }
                        return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
                }    
         ....                     

if the request has url param and content both, it must be got error because it binds param first.

@andreiavrammsd
Copy link
Contributor Author

@vishr, param binding is a feature that I wanted, but I think it's better if it gets reverted to fix breaking change it introduced. And then planned again for another release.

@ryanking8215
Copy link

Is it possible that add a flag to enable or disable param binding feature and disable by default?

@EndlessCheng
Copy link

EndlessCheng commented Jan 10, 2020

I did some tricks to temporary solve the problem by adding c.SetParamNames() before bind.

@bxcodec
Copy link

bxcodec commented Mar 3, 2020

Any updates on this? I see that @vishr is assigned on this issue?

@stale
Copy link

stale bot commented May 2, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label May 2, 2020
@cdukes
Copy link

cdukes commented May 2, 2020

Please don't close this! I'm stuck back at version 4.1.6, which was the last release without this bug.

@stale stale bot removed the wontfix label May 2, 2020
benoitmasson added a commit to benoitmasson/echo that referenced this issue May 19, 2020
Do not throw an error when binding to a slice, if binding occurs
on path- or query-parameters
=> data is very likely to be found in the body, which will be bound later

Should fix labstack#1356, labstack#1448, labstack#1495 (second case), labstack#1565
@arigon
Copy link

arigon commented Jun 2, 2020

I did some tricks to temporary solve the problem by adding c.SetParamNames() before bind.

This workaround is causing interesting problems! If the c.bind() afterwards fails, other routes using params will suddenly fail with "Not found". Only restarting the echo service resolves this.

I am now using ioutil.ReadAll(c.Request().Body) and Unmarshal it later into the struct.

@leefernandes
Copy link

Composable Bind implementations would be nice, in the meantime, roll your own Binder I think.

I did not want Bind to involve query or path params.

package util

import (
	"encoding"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"

	"github.com/labstack/echo/v4"
)

type (
	// Binder is the interface that wraps the Bind method.
	Binder interface {
		Bind(i interface{}, c echo.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 echo.Context) (err error) {
	req := c.Request()

	ctype := req.Header.Get(echo.HeaderContentType)
	switch {
	case strings.HasPrefix(ctype, echo.MIMEApplicationJSON):
		if err = json.NewDecoder(req.Body).Decode(i); err != nil {
			if ute, ok := err.(*json.UnmarshalTypeError); ok {
				return echo.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 echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
			}
			return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
		}
	case strings.HasPrefix(ctype, echo.MIMEApplicationXML), strings.HasPrefix(ctype, echo.MIMETextXML):
		if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
			if ute, ok := err.(*xml.UnsupportedTypeError); ok {
				return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
			} else if se, ok := err.(*xml.SyntaxError); ok {
				return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
			}
			return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
		}
	case strings.HasPrefix(ctype, echo.MIMEApplicationForm), strings.HasPrefix(ctype, echo.MIMEMultipartForm):
		params, err := c.FormParams()
		if err != nil {
			return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
		}
		if err = b.bindData(i, params, "form"); err != nil {
			return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
		}
	default:
		return echo.ErrUnsupportedMediaType
	}
	return
}

func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
	if ptr == nil || len(data) == 0 {
		return nil
	}
	typ := reflect.TypeOf(ptr).Elem()
	val := reflect.ValueOf(ptr).Elem()

	// Map
	if typ.Kind() == reflect.Map {
		for k, v := range data {
			val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
		}
		return nil
	}

	// !struct
	if typ.Kind() != reflect.Struct {
		return errors.New("binding element must be a struct")
	}

	for i := 0; i < typ.NumField(); i++ {
		typeField := typ.Field(i)
		structField := val.Field(i)
		if !structField.CanSet() {
			continue
		}
		structFieldKind := structField.Kind()
		inputFieldName := typeField.Tag.Get(tag)

		if inputFieldName == "" {
			inputFieldName = typeField.Name
			// If tag is nil, we inspect if the field is a struct.
			if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
				if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
					return err
				}
				continue
			}
		}

		inputValue, exists := data[inputFieldName]
		if !exists {
			// Go json.Unmarshal supports case insensitive binding.  However the
			// url params are bound case sensitive which is inconsistent.  To
			// fix this we must check all of the map values in a
			// case-insensitive search.
			for k, v := range data {
				if strings.EqualFold(k, inputFieldName) {
					inputValue = v
					exists = true
					break
				}
			}
		}

		if !exists {
			continue
		}

		// Call this first, in case we're dealing with an alias to an array type
		if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
			if err != nil {
				return err
			}
			continue
		}

		numElems := len(inputValue)
		if structFieldKind == reflect.Slice && numElems > 0 {
			sliceOf := structField.Type().Elem().Kind()
			slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
			for j := 0; j < numElems; j++ {
				if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
					return err
				}
			}
			val.Field(i).Set(slice)
		} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
			return err

		}
	}
	return nil
}

func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
	// But also call it here, in case we're dealing with an array of BindUnmarshalers
	if ok, err := unmarshalField(valueKind, val, structField); ok {
		return err
	}

	switch valueKind {
	case reflect.Ptr:
		return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
	case reflect.Int:
		return setIntField(val, 0, structField)
	case reflect.Int8:
		return setIntField(val, 8, structField)
	case reflect.Int16:
		return setIntField(val, 16, structField)
	case reflect.Int32:
		return setIntField(val, 32, structField)
	case reflect.Int64:
		return setIntField(val, 64, structField)
	case reflect.Uint:
		return setUintField(val, 0, structField)
	case reflect.Uint8:
		return setUintField(val, 8, structField)
	case reflect.Uint16:
		return setUintField(val, 16, structField)
	case reflect.Uint32:
		return setUintField(val, 32, structField)
	case reflect.Uint64:
		return setUintField(val, 64, structField)
	case reflect.Bool:
		return setBoolField(val, structField)
	case reflect.Float32:
		return setFloatField(val, 32, structField)
	case reflect.Float64:
		return setFloatField(val, 64, structField)
	case reflect.String:
		structField.SetString(val)
	default:
		return errors.New("unknown type")
	}
	return nil
}

func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
	switch valueKind {
	case reflect.Ptr:
		return unmarshalFieldPtr(val, field)
	default:
		return unmarshalFieldNonPtr(val, field)
	}
}

func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
	fieldIValue := field.Addr().Interface()
	if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
		return true, unmarshaler.UnmarshalParam(value)
	}
	if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
		return true, unmarshaler.UnmarshalText([]byte(value))
	}

	return false, nil
}

func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
	if field.IsNil() {
		// Initialize the pointer to a nil value
		field.Set(reflect.New(field.Type().Elem()))
	}
	return unmarshalFieldNonPtr(value, field.Elem())
}

func setIntField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0"
	}
	intVal, err := strconv.ParseInt(value, 10, bitSize)
	if err == nil {
		field.SetInt(intVal)
	}
	return err
}

func setUintField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0"
	}
	uintVal, err := strconv.ParseUint(value, 10, bitSize)
	if err == nil {
		field.SetUint(uintVal)
	}
	return err
}

func setBoolField(value string, field reflect.Value) error {
	if value == "" {
		value = "false"
	}
	boolVal, err := strconv.ParseBool(value)
	if err == nil {
		field.SetBool(boolVal)
	}
	return err
}

func setFloatField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0.0"
	}
	floatVal, err := strconv.ParseFloat(value, bitSize)
	if err == nil {
		field.SetFloat(floatVal)
	}
	return err
}

@cdukes
Copy link

cdukes commented Jun 4, 2020

I'm confused about this ticket. The documentation doesn't say that the binding element must be a struct.

On v4.1.6 and before, this worked fine. A change was introduced post-v4.1.6 that broke existing functionality. If this was intentional, then shouldn't the next release have been 5.0.0, following semver? If it wasn't intentional, then shouldn't the change be reverted?

Beyond that, I'm having trouble figuring out why this change happened in the first place. Looking at the v4.1.7 release, I think issue #988 is the only one relevant here, but that ticket also has the binding element issue

@benoitmasson
Copy link

I'm having trouble figuring out why this change happened in the first place.

The bug comes from this commit (from this PR), to solve this proposal.

What is done is done, but I agree with you, breaking compatibility on such critical functions is not something we expect… unit tests were not complete enough.

PR #1574 includes a quick fix which works in most, common, cases (by ignoring the binding error on the parameter). Let's hope it gets merged soon… If you have any idea or comments to improve it, feel free to tell me about it.

@cdukes
Copy link

cdukes commented Jun 4, 2020

@benoitmasson

Thanks for the reply! Looking at the Bind() code, it seems to:

  1. Try to bind params
  2. Then try to bind query params
  3. Then try to bind JSON/XML/form data, depending on the content type

And an error with any of these exits early, correct? That's a lot, and I think the function is too complicated for its own good. What we're seeing here is an issue with 1 breaking 3, even though param binding wasn't intended for the situation this ticket.

For a 4.1.x/4.2.0 fix, how do you feel about adding BindBody(), BindParams(), and BindQueryParams() methods, so that developers can explicitly state what they're trying to bind? (I also can't imagine a case where you wouldn't know what you're trying to bind.)

That doesn't fix the issue with Bind() overall, but it's a workaround that will only require minor code changes. I'm not sure what the best way to unravel Bind() would be. To be a 4.x.x release, I think it would have to:

  1. Not break any existing implementations
  2. Correctly bind params, query params, and JSON/XML/form data
  3. Handle a map for params/query params and any interface for JSON/XML/form data (that's valid for Unmarshal/Decode)

I'm not sure all 3 are doable. Maybe the function parameters should change in 5.0.0?

@benoitmasson
Copy link

I think the function is too complicated for its own good.

I totally agree, and also think your suggestion of separate BindBody(), … functions is the most appropriate (along with a generic Bind() if needed, but then this one should allow only struct or map[string]string bindings).

This has already been suggested somewhere (another issue maybe?), but not implemented yet, although it would not be too hard to code… maybe the maintainer could tell us about it, as for me, I don't know what the plans are for the next versions and I don't want to waste time on something which has no chance of getting merged (my own PR is enough for me right now).

@arigon
Copy link

arigon commented Jun 5, 2020

The description of bind() clearly states:

// Bind binds the request body into provided type `i`. The default binder
// does it based on Content-Type header.

Why is it also parsing Param and QueryParam which are part of the header? Adding the Param and QueryParam parser into the binder was a breaking change and should not have happened. Developers like me expected this function only parsing the body as the description states.

I'm with @cdukes and his suggestion of adding BindParams() and BindQueryParams() but the Bind() should only parse the body like it always did (if added to 4.x).

@nxadm
Copy link

nxadm commented Jul 6, 2020

I did some tricks to temporary solve the problem by adding c.SetParamNames() before bind.

This workaround is causing interesting problems! If the c.bind() afterwards fails, other routes using params will suddenly fail with "Not found". Only restarting the echo service resolves this.

I am now using ioutil.ReadAll(c.Request().Body) and Unmarshal it later into the struct.

Until, the new behaviour is fixed or reverted, I agree that this is the easiest workaround when you only need to read a JSON Body with a non-struct like an array. A more verbose code example:

	b, err := ioutil.ReadAll(c.Request().Body)
	if err != nil {
		return c.NoContent(http.StatusNotAcceptable)
	}

	var req []MyArray // the non-struct body
	if b != nil {
		err := json.Unmarshal(b, &req)
		if err != nil {
			return c.NoContent(http.StatusNotAcceptable)
		}
	} else {
		return c.NoContent(http.StatusNotAcceptable)
	}

      // Do something with req

benoitmasson added a commit to benoitmasson/echo that referenced this issue Aug 17, 2020
Do not throw an error when binding to a slice, if binding occurs
on path- or query-parameters
=> data is very likely to be found in the body, which will be bound later

Should fix labstack#1356, labstack#1448, labstack#1495 (second case), labstack#1565
@stale
Copy link

stale bot commented Sep 5, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Sep 5, 2020
@nxadm
Copy link

nxadm commented Sep 8, 2020

I wouldn't like to see this issue automatically close. It's a revision version that broke compatibility.

@stale stale bot removed the wontfix label Sep 8, 2020
@Julio-Guerra
Copy link

Same - I upgraded to v4 yesterday and this broke (Bind on a json array now returning an error).

@ajanata
Copy link

ajanata commented Oct 7, 2020

This is also biting my team.

@adrianlungu
Copy link

I'm not sure if this is the bug I ran into as well, or it's something similar, but we had unit tests that were complaining about suddenly receiving the wrong status code and apparently reverting to 4.1.6 fixed the issue.

It happened mostly on routes that have a path param and also receive a JSON body.

Is there anything we can help with regarding this ? Quite some time seems to have passed without any update on this.

@benoitmasson
Copy link

Hi all,

My PR was closed due to inactivity, but if you really need this to work, apply this quick patch and things should work as expected.

@adrianlungu
Copy link

@benoitmasson yep, I saw it, thanks! At this point it seems as if it would be better to fork the repo and try to keep an up to date version based on this one.

@jherman
Copy link

jherman commented Oct 23, 2020

This is almost a year and half old and it hasn't been fixed? Why would anyone want to rely on a framework where these basic functionality type of issues aren't resolved?

I also want to note that even using a struct with a PATCH method seems broken.

@yves-bourgeois
Copy link

Hi,

is there a timeline to get the different solutions offered in different merge requests actually on master?

This is currently quite breaking for us, and many others.

Can this be picked up again?

Thanks!

@lammel
Copy link
Contributor

lammel commented Oct 29, 2020

Will try to look into this in the next days. Never touched the bind code before though.
This might be similar to #1638.

@adrianlungu
Copy link

@lammel there was a previous PR by @benoitmasson that was closed due to inactivity, maybe that one fixes the issue ?

@nbourdeau
Copy link

Same problem here.
For those of you like me that just want to use the binding for a restful API usage with mainly json request body parsing, I created a simple binder like this:

type RestBinder struct {}

func (b* RestBinder) Bind(i interface{}, c echo.Context) (err error) {
	req := c.Request()
	if req.ContentLength == 0 {
		return
	}
	if err = json.NewDecoder(req.Body).Decode(i); err != nil {
		if ute, ok := err.(*json.UnmarshalTypeError); ok {
			return echo.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 echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
		}
		return echo.NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
	}
	return
}

e = echo.New()
e.Binder = &RestBinder{}

Can be used to bypass all other fancy logic regarding form binding and such ...

@diegoclair
Copy link

Hi, an alternative to this problem would be this: https://www.buggger.com/en/rqt/5ecb9ceacb08b

@lammel
Copy link
Contributor

lammel commented Mar 9, 2021

Issues with binding should be resolved with echo v4.2 (see release notes) with various PRs merged that are referenced here.

So feedback is welcome, if there are open issues left... Please let us know.

@jherman
Copy link

jherman commented Mar 10, 2021

Issues with binding should be resolved with echo v4.2 (see release notes) with various PRs merged that are referenced here.

So feedback is welcome, if there are open issues left... Please let us know.

Still getting this error with PATCH method:

image

github.com/labstack/echo/v4 v4.2.1

@dahu33
Copy link
Contributor

dahu33 commented Apr 19, 2021

This issue seems fixed by #1835 and was released in v4.2.2.

@jherman
Copy link

jherman commented Apr 19, 2021

Can confirm this is now working for me in v4.2.2.

@lammel
Copy link
Contributor

lammel commented Apr 19, 2021

Thanks for verifying @jherman , @dahu33 .
Closing!

@lammel lammel closed this as completed Apr 19, 2021
@jherman
Copy link

jherman commented Apr 30, 2021

@lammel Sorry - I think this may have to be re-opened. I noticed this works fine when using a struct, but if you bind to a map map[string]interface{} and you have parameters in your route, then it will also bind those parameters in your map. For example if I make a webrequest with PATCH, to a route with the following path /tags/:tagKey and the object is { 'hello': 'world' }, it will bind like: `{ hello: 'world', tagKey: 'myUrlTagName' }. I don't believe that is expected, right?

EDIT:
Maybe that is expected. Perhaps I'm suppose to code it like so:

if err = (&echo.DefaultBinder{}).BindBody(c, &tagValue); err != nil {
  return fmt.Errorf("could not parse update: %v", err)
}

This appears do what I expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.