-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
[Feature Request] Nested form attributes #313
Comments
@SergioBenitez what do you think the best option for implementation is? I can try to help flesh this out if you have a good idea of what it should look like and if it should be entirely in rocket or an extension. |
@lancecarlson I haven't had plenty of time to think about this, but this seems like something that would be great to have, and so I'm inclined to accept this. Thus far, I'm thinking that fields can be labeled with In your example, this might look like: #[derive(FromForm]
struct StarterApplication {
#[form(nested)]
user: User,
#[form(nested)]
business: Business,
not_nested: usize,
} You would be able to use the #[derive(FromForm]
struct StarterApplication {
#[form(nested, field = "userSettings")]
user_settings: User,
} |
Implementation wise, a few things would need to happen:
The implementation I site above means that the form string is scanned once per nested form. You might imagine a more optimized implementation that only goes through the form string twice, regardless of the number of nested forms. Though I like the simplicity of the above implementation. |
Just a note: if you implement this, supporting arrays would be great too! #[derive(FromForm]
struct Item {
name: String,
}
#[derive(FromForm]
struct FormData {
#[form(nested)]
items: Vec<Item>,
} <input type="text" name="items.0.name" />
<!-- OR -->
<input type="text" name="items[0][name]" /> |
@theduke I'm not sure about that. It's not clear what the semantics are; are we writing to index The request in #205, which is similar to what you're proposing, is much more conventional and doesn't have any of these drawbacks. It's something I'd eventually like to get in. |
Is this a border line serde thing? |
@lancecarlson Can you expand on that question? I'm not sure what you're asking. |
That's how most of the frameworks do it, since there is not really any other way to express nested arrays which contain structures. I don't really care much about this feature anyway since nowadays I just create some JSON with Javascript and POST it. |
@SergioBenitez I was thinking maybe it's only the serialize part of serde to parse forms, but the more I think about it, it actually makes sense to have it in rocket. |
@SergioBenitez also, to @theduke's point, go does it the way he is describing: |
@SergioBenitez are you thinking that instead of allowing the index to be specified, that if you repeat the same input name (even if it is nested), that that would create an array instead? items[].name Instead of items[0].name I'm completely fine with that I think. The only nice thing with indexes is that it can be easier to fulfill Todo list style use cases where position is important instead of relying on placement in the DOM, you can rely on the index. Also, I assume that one would specify their struct to serialize into with the sizes predefined if they wanted to prevent large index attacks? |
@lancecarlson Can you tell me more about the todo list style use cases you have in mind? I'm imagining that you'd have an "id" field in such a form anyway, so using the index wouldn't matter. I'm not sure what you mean by "I assume that one would specify their struct to serialize into with the sizes predefined if they wanted to prevent large index attacks?". I think as long as the user can specify an index, we lose. Maybe you mean that you should be able to say something like, "ensure that this structure doesn't exceed this much allocated memory during deserialization." If so, well, maybe, but then we'd have to keep track of this during deserialization. That sounds a bit painful and not worthwhile. It also adds another configuration parameter. |
to avoid somebody writing <input type="text" name="items[0][name]" />
<!-- oh noz it's over 9000 -->
<input type="text" name="items[9001][name]" /> to make the system OOM, does rust have an "ordered dict" structure ? in which case you could implement a sparse array that will actually contains only 2 elements |
Not built in, but there is the |
You can get this behavior today with the following (defers form processing to pub struct Qs<D>(pub D);
impl<'f, D> FromForm<'f> for Qs<D> where D: ::serde::de::DeserializeOwned {
type Error = ::serde_qs::Error;
fn from_form(items: &mut FormItems<'f>, _: bool) -> Result<Self, Self::Error> {
items.mark_complete();
Ok(Qs(::serde_qs::from_str(items.inner_str())?))
}
} And then wrapping your form struct in your handler signature like It might be nice if Rocket just deferred form parsing entirely to |
@kardeiz Deferring to |
@SergioBenitez, okay, that makes sense. Sorry, I'm not very familiar with the Rocket ecosystem at the moment. I do appreciate how easy it is to defer to |
Unfortunately, we won't be able to get this in for 0.4: pushing to 0.5. |
There seems to be a commit on this here from 2018, but it was never merged. This is blocking a 0.5 release currently. Is the original author still around and can comment on the code and its state and/or what is missing? |
@TotalKrill It's been so long, I'm sure this code is no longer relevant and I'm missing a lot of context on how to get it up to speed. |
Maybe this feature could be pushed to 0.6, from what I gather, the move to async in 0.5 has made the codebase a lot different and it seems a lot of work for 0.4 is no longer relevant |
@TotalKrill see #106 (comment). It's already been mostly implemented; IIRC there were a few |
All of the issue are now resolved! I should be able to land this very soon! |
Routing: * Unicode characters are accepted anywhere in route paths. (#998) * Dyanmic query values can (and must) be any `FromForm` type. The `Form` type is no longer useable in any query parameter type. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. `DataStream` methods returns `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when the data limit is exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a` when not set. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = expr)]`. Core: * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * All dynamic paramters in a query string must typecheck as `FromForm`. * `FromFormValue` removed in favor of `FromFormField`. * Dyanmic paramters, form values are always percent-decoded. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` as `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by setting overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP: * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `(Status, R)` where `R: Responder` is a responder that overwrites the status of `R` to `Status`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded parts. * The `Segments` iterator is signficantly smarter. Returns `&str`. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`, doesn't consume. * A new `QuerySegments` is the analogous query segment iterator. * Once set, the `expires` field on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen: * Preserve more spans in `uri!` macro. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[form(name = ..)]`. Examples: * `form_validation` and `form_kitchen_sink` removed in favor of `forms` * `rocket_contrib::Json` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json`. * `rocket_contrib::MsgPack` implements `FromForm`. * Added clarifying docs to `StaticFiles`. * The `hello_world` example uses unicode in paths. Internal: * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` are are lowercased. - Stdlib types start with `_` are are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
Routing: * All UTF-8 characters are accepted anywhere in route paths. (#998) * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]` becomes `#[route(GET, uri = "..")]`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = expr)]`. * `FromFormValue` is now `FromFormField`, blanket implements `FromForm`. * Form field values are always percent-decoded apriori. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. `DataStream` methods return `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when data limits are exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a`. Core * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` as `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded versions. * The `Segments` iterator is smarter, returns decoded `&str` items. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`. * A new `QuerySegments` is the analogous query segment iterator. * Once set, `expires` on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen * Preserve more spans in `uri!` macro. * Preserve spans `FromForm` field types. * All dynamic parameters in a query string must typecheck as `FromForm`. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[field(name = ..)]`. Contrib * `Json` implements `FromForm`. * `MsgPack` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json!`. * Added clarifying docs to `StaticFiles`. Examples * `form_validation` and `form_kitchen_sink` removed in favor of `forms`. * The `hello_world` example uses unicode in paths. * The `json` example only allocates as necessary. Internal * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` and are lowercased. - `std` types start with `_` and are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
Routing: * All UTF-8 characters are accepted anywhere in route paths. (#998) * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]` becomes `#[route(GET, uri = "..")]`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = expr)]`. * `FromFormValue` is now `FromFormField`, blanket implements `FromForm`. * Form field values are always percent-decoded apriori. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. `DataStream` methods return `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when data limits are exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a`. Core * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` as `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded versions. * The `Segments` iterator is smarter, returns decoded `&str` items. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`. * A new `QuerySegments` is the analogous query segment iterator. * Once set, `expires` on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen * Preserve more spans in `uri!` macro. * Preserve spans `FromForm` field types. * All dynamic parameters in a query string must typecheck as `FromForm`. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[field(name = ..)]`. Contrib * `Json` implements `FromForm`. * `MsgPack` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json!`. * Added clarifying docs to `StaticFiles`. Examples * `form_validation` and `form_kitchen_sink` removed in favor of `forms`. * The `hello_world` example uses unicode in paths. * The `json` example only allocates as necessary. Internal * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` and are lowercased. - `std` types start with `_` and are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
This is more of POC, but I ran into trouble with recursive dependencies causing compilation failure. Simple Box around the contexts Generic storage allowed them to build the layouts separately during MIR for more flexible inputs. Leaving hear as a future topical note. |
Can you be more specific about what problem you had? This really shouldn't be required unless you have a custom FromForm type in which case you can Box that type's Context instead. |
The important part of the code snippet is the Configuration struct recursion with struct and its' final field.
I suspect the second part is unnecessary, and any structure in a container recursion like this will cause the error. I do not have a minimum reproducible version, and this already has a lot stripped out--hopefully not too much. The error I was getting:
Also, I moved the Box from items to Context in this new commit. The error returned today using the prior one and also seemed cleaner than the last patch. |
Does this change also fix compilation? #[derive(FromForm, JsonSchema, UriDisplayQuery))]
pub struct Configuration {
pub path: String,
pub path_type: PathType,
pub processor: Processor,
pub parameters: HashMap<String, Value>,
pub log: LogConfig,
pub store: Option<Store>,
pub response: Option<ResponseConfig>,
- pub post_activities: Vec<Configuration>,
+ pub post_activities: Vec<Box<Configuration>>,
} |
It probably would, but most macros don't have generic implementation support for Box. In those cases, I would have to patch all other libraries to work around what seems like a limitation in Rocket's internal data structure. Another solution is to support From with duplicate structs fitting each library, ie Adapters--which I sometimes do. The ugly thing about this solution is that is essentially what library Macros are doing. Now there is a bunch of duplicate code to maintain, taking away key advantages of macros. I don't believe it's good to have the object that tightly coupled in memory either. I suspect they are semi-frequently operated on and used in separate functions--so it may reduce overhead in real-world complex structures. Lots of assumptions and guesses in this last paragraph. What downside do you see to this change? |
Feature Requests
Go, Rails, Express all have some way of parsing nested attributes easily from forms.
I think it would be pretty nice to have the ability to able to specify a struct like:
And based on the dot notation or brackets, allow nested attributes in forms to parse automatically:
or
Instead I find myself doing this:
and manually converting it to the individual structs.
That being said, if there was a way to possibly specify multiple structs on FromForm and get access to the form field name and not just the value, that might also be a viable option? I'm not sure what my options are regardless. Perhaps I'm missing something?
It could possibly be an extension of rocket if there was a way to get the values of the fields that come through. I'm not sure that is possible though.
Thanks!!
The text was updated successfully, but these errors were encountered: