-
Notifications
You must be signed in to change notification settings - Fork 52
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
New "binding" package replaces "Form" middleware #34
Conversation
XML would probably be a piece of cake…
…s (related to #32)
Stoked! I will review this after work |
This will need a readme and godoc documentation before it lands. Looks pretty good so far. The only thing I am hung up on design-wise is the validation. Validation currently requires the struct to be passed in, which means we need a reference to it as a pointer. This situation opens the door for some major race conditions. To get around it we can copy the struct. but for that to happen we would need some way for the validation middleware to get that copied struct, so here is what I propose. I think the Form and JSON middlewares should bind the struct and validate/map errors. The validation middleware can still exists, but it's only purpose is to throw a bad request error if there are validation errors. The validation middleware should only be a convenience if you don't want to be checking for validation errors all over the place. So I don't know, should it still be called validation? Or something different? |
It's looking pretty good so far @mholt. really excited to pull the json bindings into my projects |
I appreciate your enthusiasm. You have a great way of motivating contributors. 👍 Okay, so basically, these lines should be moved into the Validate middleware, and the existing code in the Validate function goes into a new, un-exported func, that is called by the Json and Form middlewares (they will no longer execute the Validate middleware: only the Bind middleware will do that). I may be misunderstanding you... if so, make sure I'm clear on things! As I understand it now, the Json and Form middlewares would still allow the application logic (the final handler) to take the request even if the validation fails: in other words, you'd have to check for errors yourself if you didn't use the Bind middleware. Also, could you point out the race condition? Middleware is called sequentially, with one handler running at a time, isn't it? When Validate is invoked as sort-of middle-middleware like it is now, is that what would cause the race condition? |
@mholt You are pretty much reading my mind. I think validation enforcement should be optional. Either by allowing the application code to take care of it or for the validation handler to be added to the stack, it is up to the developer. m.Post("/form", binding.Form(BlogPost{}), binding.Validate(), func(bp BlogPost) {
// ...
}) I was getting ahead of myself with the race condition. In this more modular model that we are talking about right now (which I think is pretty good) we could run into race conditions. When you were invoking validate yourself there was no race condition unless you referenced the struct as a pointer. m.Post("/form", binding.Form(&BlogPost{}), ...) // the *BlogPost will be pointing to the same struct on every request! big ohnoes That issue might be worth outlining in the documentation as it will still be possible to do. |
Oh! I see your point about the pointer to the struct. The handler is only set up once (m.Post, or whatever, is only actually executed once...) thus the instantiation of a new struct only happens once and the pointer is to the same one for each request. I guess doing an in-memory copy is OK; a request wouldn't be too huge, right? Yes, that should be documented. I also see now what you mean about adding the Validate middleware to the stack manually instead of building it in. If we do this refactor, then yes, let's rename Validate to... uh, I dunno... letsee, it responds with a certain status code and payload and then stops the request in its tracks... what do you call something like that? 😕 I'm just trying to bury as much boilerplate as possible away from the actual, final handler. I'd personally love not to have to specify that so-called-"Validate" middleware for each What if the "Validate" middleware executed a (optional) user-defined function for dealing with requests that don't validate (have errors)? That was kind of the idea behind providing the Validator interface, but maybe it could also provide a ErrorResponder interface or something where the user-defined ErrorResponse func is called by default, if one exists, if there are errors... follow? |
I see where you are coming from. I think the main hangup for me is the error handling automatically being done for me. If he response is written in the bind middleware then there is no way for me to specify my own custom response body. I think it is good to remove the boilerplate with having a good default error handler but I have a spidey sense that it should be optional. What if it was named binding.ErrorHandler() ? Sent from my iPhone
|
My thought is that all the Validator would do is get the binding.Errors type injected and write a response error if any exist. Simple? Yes, but it is effective and consistent. And it also is easily swappable. Sent from my iPhone
|
Now thinking about it. binding.Bind can still write an error. binding.Bind is really a composition of the smaller pieces of the package. I just wouldn't want response control flow restricting me from using binding.JSON or binding.Form Thanks for discussing this. I know we are both wanting to nail this functinality. Sent from my iPhone
|
Ah, yes, I keep forgetting that middleware is swappable. (Duh.) And if Thanks for your patience as we iterate on these ideas. I'll try to work on this more tomorrow. |
Woah, okay, I just looked at the whole thing from a fresh set of eyes this morning. I wonder if both our desires are already being satisfied, and I think some errors in my description from my original post are root of the confusion:
m.Post("/form", binding.Form(BlogPost{}), binding.ErrorHandler(), func(bp BlogPost) {
// ...
}) Or you could write your own middleware handler to respond to errors, or you could do it in your application, or whatever you want. Using Bind, which kind of wraps up all the others, you'd do only this (but you're right, you have less control): m.Post("/form", binding.Bind(BlogPost{}), func(bp BlogPost) {
// ...
}) Does that clear things up? |
Hah wow. That sounds great! So just to clarify, everything is staying the Sounds great to me! On Tue, Dec 3, 2013 at 9:16 AM, Matt notifications@github.com wrote:
|
Yup! I'll do that and add godocs. And if anything needs to change after the PR is merged, that's fine too. |
@codegangsta Bam. See what you think. There's still no README, but I can throw that together soon enough if you'd like me to do that. |
Yeah. throw together a readme. People seem to like them for getting On Tue, Dec 3, 2013 at 11:59 AM, Matt notifications@github.com wrote:
|
aaaand merged. Seriously awesome work! @mholt @mdwhatcott I'm adding you two as collaborators so you can update binding without PRs |
Hi guys. I just wanted to update the vendors I use and wait what ? the form middleware disappear in favor of this one. The fact is, if you red the go guidelines carefully, it says "never break backward compatibility". If you want to do it, create a new package. So thanks for this middleware which support more functionality than the form one. But for the future, please never breack BC... |
martini and martini-contrib are less than a month old. The plan is to use Truth is, stuff is going to break before we stamp it as 1.0. We are all in That being said, I am conscious of API compatibility and will not break On Thu, Dec 5, 2013 at 6:42 AM, Florent VIEL notifications@github.comwrote:
|
I understant martini is a new project so that things are not fixed. But how would you use martini and martini-contrib with semantic versioning because the |
There is tooling built around managing dependencies with specific versions. Godep comes to mind. Thankfully for the majority of use cases we shouldn't run into any broken promises with the api until Martini hits 2.0. Right now it's the pre 1.0, Wild West era of martini. Once we hit 1.0 we will be making compatibility promises until 2.0 in which I am banking some hope that dependency management in Go will be a lot better by that time. Sent from my iPhone
|
With go you don't have to care about dependency management because you don't have to break BC. Further reading here http://www.stovepipestudios.com/blog/2013/02/go-dependency-management.html |
Yup. Either a martini2 repository or a separate branch. I personally like what mgo does. They have their own endpoint for different labix.org/v2/mgo On Thu, Dec 5, 2013 at 8:03 AM, Florent VIEL notifications@github.comwrote:
|
My point is not to be angry against you. |
No offense taken. :) Martini may never have a version 2. I also have no On Thu, Dec 5, 2013 at 1:20 PM, Florent VIEL notifications@github.comwrote:
|
Package "binding" combines the Form middleware with new Json, Bind, and Validate middleware.
Bind
Bind
is the highest-level middleware. It takes a request with a Form or JSON payload and converts it to a new struct you pass in. If a Content-Type is specified, it immediately deserializes the request into that struct according to that content type. Otherwise, it guesses until it finds one that works. (Right now, it only supports Form and JSON; XML could be added later if desired.) Bind also performs validation on the request so the application only sees the request, with the populated struct, if deserialization and validation succeeded. It works like shown here. It wraps up the functionality of the rest of the package so you have very little boilerplate to worry about.Form
Form
middleware works like it did before, deserializing form-urlencoded payload or query string into a struct, except it does not validate that the "required" attribute is satisfied. It only populates a struct andperforms no validation. (Validation is now performed with the Validate middleware.) performs validation by invoking the Validate middleware, but it does not write a 400 response if there are errors: you have to do that yourself. You get more control this way if there are errors in the request.JSON
Json
middleware works likeForm
except it deserializes a JSON payload in the request body into the struct.It performs no validation.It performs validation by invoking the Validate middleware, and as Form, does not write a 400 response if there are errors: you'd have to do that. You get more control this way for error handling.Validate
Validate
middleware only performs validation. It does not write a response if there are errors. The struct passed in is presumed to be populated from the request already. It first checks each field for the "required" attribute, and if it is required, ensures that it is a non-zero value for that field's type. (That means if you want to accept 0 inint
fields, or false inbool
fields, etc, don't mark it as required.)After the required property is validated, it checks to see if the struct implements the Validator interface:
If so, the
Validate()
method is called on that struct. This comment on issue 5 also describes how that works.This PR should effectively fix #5. It's a massive change so feel free to make tweaks, and feedback is welcome. I strongly suspect we'll be using this internally at work so it's in our best interest to make this good, too.