-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
separate methods to bind only: query params, route params, request body #1681
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1681 +/- ##
==========================================
+ Coverage 84.35% 85.59% +1.24%
==========================================
Files 28 29 +1
Lines 1911 1993 +82
==========================================
+ Hits 1612 1706 +94
+ Misses 189 181 -8
+ Partials 110 106 -4
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a better and more intuitive approach than the one that I proposed
Just a minor thing, Could you please add in the PR message the issues that this PR fixes?
@vishr @lammel or other Echo official maintainers - I think Echo documentation at https://echo.labstack.com/guide/request needs clear declaration for users that to be honest. documentation does not even mention that query and route is binded along with body
|
Why isn't this merged yet? |
Lets give an update on the delays in merging this PR... but it sure is a definite candidate for merging soon. We still need to discuss the best way forward. Either use this workaround as is and put a burden on the developer or accept that we need to introduce a breaking change in the Bind interface to remedy the situation. Feedback is welcome meanwhile. |
About this PR. I should have reversed arguments for these new functions. So Looking at request example https://echo.labstack.com/guide/request I think that original Echo author(s) intentions were that binding for query params should work only then I think documentation/cookbook need proper explanation how binding works.
Maybe in v4 just provide new methods to do binding separately and make sure that route/query params are bind only then their For consideration. These are examples for similar functionality in other popular frameworks
|
Yes, that would be more in line with the current interfaces.
We need to look into that, that may be the simplest solution.
Documentation definitly needs to be updated to describe the correct behaviour then.
This is what I was thinking about.
Thanks for the list, very helpful. |
Lines 43 to 48 in 502cce2
I think that the error here is to assume that because bindData fails, Bind should fail. The calls for bindData are kind of optional or "call it just because this could be a param/query binding". For this reason, bindData should only fail when the intention of the Echo user is to consume that kind of binding and there is an error during that binding. As an example, bindData should fail on line 46 if the type to bind is an struct, contains query tags and the bind of a particular struct field fails. To recap, bindData could be modified to not return an error if the type to bind is not an struct (the check must be kept to not panic when NumField() is called). Also, this change could be beneficial for #1356. This shouldn't be an issue for the Echo users that relies on "param/query" binding, because they are already using structs on their calls to Echo#Bind, so they could safely update to a possible newer version that doesn't fail if the type to bind isn't a struct. |
I agree that sometimes it should not error out or at least let user to have choice. But this is current API behavior/implementation not a bug. It makes sense to return early when some field binding fails due data type problems. This would flush out problems faster for your API clients that have incorrect implementation. If you have worked on rewriting legacy APIs then it is absolute nightmare when percentage of clients are sending some fields as strings instead of number etc and legacy app was not strict about that (ala js/php are pretty relaxed about types). I'll add here that order of binding -> route params before query does not make sense for me. Example so we have these questions/problems for
|
Tiny suggestion that can be added to the PR. Usually when working with echo I have to manually process my comma separated query values. Would be great to abstract that into the basically In order to optionally parse the comma separated strings, we could only process them when a struct is passed into any thoughts? |
Sorry, it seems that I didn't express my idea properly. The only error that could be removed is the struct type check. The rest of the errors should be kept for the reasons that you are mentioning. For me, that case is not an error because calling bindData() on line 43 and 46 it's a "try to guess if the variable to bind is a struct properly tagged". The only caveat to relaxing this error (not a struct) could be that the binded variable will remain as it was before the call to bindData, but probably that couldn't be a big problem because before its use some kind of validation must be performed on it. Here are mi opinions about some of your questions:
The answer to this one is kind of tricky, because to know 100% sure if the calls to bindData for param/query are not optional, the type must be validated and then all the fields needs to be check for the presence of the corresponding tag, and if you find that all this is true, you will do it again the same inside bindData.
I think yes. The autobinding by name could be nice for some users, but in my case I always prefer being explicit. Also I think it could simplify the code, because the intention of the user will be always be clear.
Yes, and could be implemented with the three functions that you are proposing: BindRouteParams/BindQueryParams/BindBody |
@iambenkay I'm don't have a strong position on your suggestion, but maybe what we can discuss if it's worthy to implement ourselves in Echo a more complex DefaultBinder or if we should use a third party Binder as DefaultBinder. Of course, the current implementation of Echo have the mechanisms needed to select/use the Binder that better suit your needs, but it's kind of common that Echo users fills issues because the DefaultBinder doesn't work as they assume/need. |
@pafuent I think the current echo default binder is just fine. Just so far it has delivered well. Just a few hiccups like binding all the data units in a request at once and the comma separated query string for me. This PR obliterates the first issue. maybe when this is merged, I could send in a PR that builds on this to implement the comma separated query values. I understand that there is always the option of using a different binder altogether but if most users are like me, then they don't really like replacing |
Offtopic: this should be discussed in separate issue just finished porting older php app to go and there is one nice feature that PHP has - mapping ala func ToValidInt64(input string, defaultValue int64) (int64, bool) {
if input == "" {
return defaultValue, true
}
n, err := strconv.Atoi(input)
if err != nil {
return 0, false
}
return int64(n), true
} and in handlers length, ok := sanitizer.ToValidInt64(c.QueryParam("length"), 25)
if !ok {
return apperror.NewValidationError("length is not valid int64 value")
} Maybe echo could have special func/structs for handling things like that. ala very short API for usage like that. length := int64(25) // default value
var csv bool
var params []string
b := echo.BindQuery(c) // deals only with query params
if err := b.Int64(&length, "length"); err != nil {
return errors.wrap(err, "length is not valid int64 value")
}
if err := b.Bool(&csv, "csv"); err != nil {
return errors.wrap(err, "csv is not valid bool value")
}
// NB: go URL returns query params as list of values `c.Request().URL.Query()`
// but Echo `c.QueryParam("params[]")` returns single value so it is not possible in Echo
if err := b.SliceStrings(¶ms, "params[]"); err != nil {
return errors.wrap(err, "params contains invalid values")
}
// or name like that
if err := b.Int64s(&ids, "ids[]"); err != nil {
return errors.wrap(err, "ids contains invalid values")
} or even this - fluent APIs are not very idiomatic go but for me I usually have bunch of query params that I need to bind and need to check for error (first) and return some meaningful message // deals only with query params, returns on first error, error struct contains field name as separate member
err := echo.BindQuery(c).
Int64(&length, "length").
Bool(&csv, "csv").
Bind() // and ala BindErrs() that tries to bind all returns all errors
if err != nil {
return errors.Wrapf(err, "%s can not be bind", (err.(echo.BindError)).field)
} I have not used any library for this kind of stuff but that later one is probably already implemented in some binding/validation or something-something library this would be backwards compatible as it would not change existing interfaces (so could be released as 4.x minor version) |
After some discussion with @pafuent on the current situation we think the best way forward is a follows:
The changes above will be done for v4 now. Future changes for binding will be discussed in a seperate issue/PR for v5 (where also the interface can change) We could use this PR to work towards the wanted behaviour above for 1) and 2). @aldas: If you are willing to contribute we can keep this PR and adjust to the behaiour and tests. Feedback very welcome! |
4d59855
to
bd5810f
Compare
NB: I changed initial For points 1,2 this PR would be useful as base as it separates out path and query binding. Also add bunch of tests for use-cases when all 3 sources exist (these need to changed if only body binding remains for Maintainers are allowed to change this PR but maybe its easier to merge it and base next changes on it For 6 I created PR in documentation repo labstack/echox#169 As for point 1
This is first commit of Bind cb75214 (only for binding Form but behavior is similar)
As I mentioned in #1670
this means that b129098 changed how POST/PUT/PATCH interact with query/path parameters.
If path/query params binding is separated from body binding then most of slice binding problems go away. Binding to slice field is somewhat supported already. See Line 154 in 2b36b3d
when destination field is slice and query/path contains multiple values for that parameters then slice of that native type is created and filled |
My suggestion would be:
|
This was probably due to those requests not having a body at all, so only path and query params are available anyway. For this the "AutoBinder" or whatever we call it, that will bind everything could also be used.
Acknowledged! Concerning the new seperate binding ways, just to clarify. |
yep, at least these refactored methods. So user can choose from binding: (all), query, path, body - to struct.
well main thing was that block 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")
} It was pretty much implementation of requirements:
this guaranteed that for POST/PUT request query param from URL will not have conflicts with bindable struct. ala my second example (binding slice) #1670 (comment) so if problem was getting status 400 for empty body during post/put this does not mean that all these requirements should be changed. Maybe just relax req 3 and leave 1,2,4 alone. because touching 2 messes up a lot of things |
We are getting close! Good work.
Reviewing the current changes the rules are different then the 4 stated above. Ad 2) This is still done currently, mixing query and body params Ad 3) A stated in the HTTP1.0/1.1 RFCs a body MAY be empty (usually only useful for PUT to empty a resource) but still that could be considered a valid request. So not sure we should fail binding in that case or just return an empty struct. |
well. my intention was not to change behavior with this PR. idea was to implement changes/"fixes" with further PRs
|
Agreed. Might be better to that separate PRs. So this PR is ready to merge @aldas ? |
yep |
Is there a way now to bind query params for POST? Is there a way to restore previous behavior? This change broke a lot of our APIs developed on 4.1.17. |
@arvenil Did you check https://echo.labstack.com/guide/binding? That helped me to find the right method to restore the correct behavoir in our case. |
To allow user more precise control over where bind takes its source data, bind is refactored to consist of 3 different methods that can be user separately.
DefaultBinder.BindPathParams
- binds only path params to given interfaceDefaultBinder.BindQueryParams
- binds only query params to given interfaceDefaultBinder.BindBody
- binds request body to given interfaceThis is not breaking change as
Binder
interface was not changed.Example usage would be:
Also added bunch of tests for cases when bind target is filled from multiple different sources - to document how binding priority works.