I’ve the following macros (which works fine) which I’d like to enhance so it can be used to bind to form args as well – I’ve tried a bunch of variations but can’t seem to get it right, LLMs haven’t been helpful either (as they seem to produce code that iterates over the TypeNode.named_args which refuses to compile), can someone please help?
macro bind(type)
{{type}}.from_json(context.request.body.not_nil!)
end
macro bind(**args)
NamedTuple({{args.double_splat}}).from_json(context.request.body.not_nil!)
end
Which can be used like so:
alias User = NamedTuple(email: String, password: String, remember_me: Bool?)
#...
payload = bind(User) #works fine for predefined types
# using the second version with **args
payload = bind(email: String, password: String, remember_me: Bool?) #works fine for one off captures too
Assuming request.params exists (which merges query_params, form_params and form data params) how would one go about extending the above two macros so it handles both JSON and form params? I understand binding to form params can be tricky for nested structures but I’m hoping to get to a bare minimum version that can be useful.
Thank you, yes I understand request.params doesn’t exist in the stdlib. Thanks also for the pointer to URI::Params::Serializable, looks interesting, but I’m not sure it would handle what I’m looking to do here as it seems to only deal with url encoded data.
What kind of form data are you wanting to handle? That module handles x-www-form-urlencoded, which is a very common form data content type. If you are wanting to handle multipart/form-data type of form data, then you are correct and it wouldn’t help.
I was trying to get params to transparently handle all three sources of input data – query params, url encoded and form data by merging query_params and form_params then parse form data and merge everything into the resulting params. And while this seems to work great, I was hoping to further improve this by having a generic bind method/macro which can switch based on the request content type and fill in the vars supplied as shown in my example earlier. Hope this makes sense.
This kind of remind me of PHP, which has the magic $_REQUEST variable that combines multipla sources (query parameters, form data and cookies).
However, one drawback of not specifying where the data comes from is that multipla sources can have the same key and then you need to revolver which source that ultimate should be used.
I don’t think the conveniance you’ll gain by combining data from multipla sources is worth it, Haning a single source gets you less confusion.
@lasso Yes indeed, and coming recently from Ruby, params seems ubiquitous. Thank you for the warning however – I understand the drawback on merging these and I’m willing to live with it.
You can do this by making use of URI::Params#merge, so something like params = request.form_params.merge request.query_params. Then make use of URI::Params::Serializable via params.to_s. HTTP::Request#form_params only handles x-www-form-urlencoded anyway so this all should be fine I think.
Thanks all but I should apologize for any confusion here as going by the replies I think my original post perhaps wasn’t super clear on what I’m really after – I’ve already managed to create the params on the request object (by merging the three sources), what I was hoping was to get some help on how to extend the bind macro so that it works with seamlessly with JSON body and regular form post (as right now it only handles JSON bodies). I guess the question I’m asking is how do I iterate the passed in NamedTuple’s fields and set the fields by fetching the value from the params object while being mindful of optionals and arrays.