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

[Feature Request] Nested form attributes #313

Closed
lancecarlson opened this issue Jun 8, 2017 · 28 comments
Closed

[Feature Request] Nested form attributes #313

lancecarlson opened this issue Jun 8, 2017 · 28 comments
Assignees
Labels
accepted An accepted request or suggestion request Request for new functionality
Milestone

Comments

@lancecarlson
Copy link

lancecarlson commented Jun 8, 2017

Feature Requests

  1. Why you believe this feature is necessary.

Go, Rails, Express all have some way of parsing nested attributes easily from forms.

  1. A convincing use-case for this feature.

I think it would be pretty nice to have the ability to able to specify a struct like:

#[derive(FromForm]
struct StarterApplication {
  user: User,
  business: Business,
}

#[post("/apply", data="<form>")]
fn post_apply(context: Context, form: StartedApplication) -> Flash<Redirect> {
  //..
}

And based on the dot notation or brackets, allow nested attributes in forms to parse automatically:

<input type="email" name="user[email]" />
<input type="email" name="business[name]" />

or

<input type="email" name="user.email" />
<input type="email" name="business.name" />

Instead I find myself doing this:

#[derive(FromForm, Serialize)]
struct StarterApplication {
    user_email: String,
    user_full_name: String,
//... user fields ...
    business_name: String,
//... business fields ...
}

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?

  1. Why this feature can't or shouldn't exist outside of Rocket.

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!!

@SergioBenitez SergioBenitez added the request Request for new functionality label Jun 9, 2017
@lancecarlson
Copy link
Author

lancecarlson commented Jun 9, 2017

@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.

@SergioBenitez
Copy link
Member

@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 #[form(nested)] to indicate that the field's type actually corresponds to a nested form. The FromForm derived code can then do the right thing, assuming something like what I've proposed in #242 (comment) is merged.

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 nested and field inner-attributes simultaneously:

#[derive(FromForm]
struct StarterApplication {
  #[form(nested, field = "userSettings")]
  user_settings: User,
}

@SergioBenitez
Copy link
Member

Implementation wise, a few things would need to happen:

  1. The new attribute would need to be recognized in the derive(FromForm) plugin.

  2. FormItems would need to be extended so that it knows about nested forms.

    I imagine this to mean that we add a .filtered(&self, name: &str) -> FormItems method to FormItems that returns a new iterator where only fields that start with {name}. are emitted from the iterator and so that the {name}. prefix is removed from keys. This enables infinite nesting as well, so forms can include something like user.account.id.

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.

@theduke
Copy link

theduke commented Jun 11, 2017

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]" />

@SergioBenitez
Copy link
Member

@theduke I'm not sure about that. It's not clear what the semantics are; are we writing to index 0 of the items array? If so, how is the array getting sized? If we allow the user to control the index, which is what you proposed, and automatically size the array depending on the index, then this can trivially lead to a DOS attack via OOM. Further, what happens if index i and j are provided but aren't contiguous? I suppose we could force Item to be Default, but that seems error-prone.

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.

@SergioBenitez SergioBenitez added the accepted An accepted request or suggestion label Jun 17, 2017
@SergioBenitez SergioBenitez added this to the 0.4.0 milestone Jun 17, 2017
@lancecarlson
Copy link
Author

Is this a border line serde thing?

@SergioBenitez
Copy link
Member

@lancecarlson Can you expand on that question? I'm not sure what you're asking.

@theduke
Copy link

theduke commented Jun 18, 2017

That's how most of the frameworks do it, since there is not really any other way to express nested arrays which contain structures.
The index could probably just be ignored on the Rust side and only used for detecting arrays.

I don't really care much about this feature anyway since nowadays I just create some JSON with Javascript and POST it.

@lancecarlson
Copy link
Author

@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.

@lancecarlson
Copy link
Author

@SergioBenitez also, to @theduke's point, go does it the way he is describing:

http://www.gorillatoolkit.org/pkg/schema

@lancecarlson
Copy link
Author

lancecarlson commented Jun 18, 2017

@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
Items[].name

Instead of

items[0].name
items[1].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?

@SergioBenitez
Copy link
Member

SergioBenitez commented Jun 20, 2017

@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.

@allan-simon
Copy link

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

@shssoichiro
Copy link
Contributor

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 ordermap crate.

@kardeiz
Copy link

kardeiz commented Jul 17, 2017

You can get this behavior today with the following (defers form processing to serde_qs):

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 form: Qs<MyForm>.

It might be nice if Rocket just deferred form parsing entirely to serde_qs (or serde_urlencoded, but that one doesn't support nesting)...

@SergioBenitez
Copy link
Member

@kardeiz Deferring to serde simply isn't an option. We want to have custom validation per field which means calling from_form_value for every field in the structure; this isn't something serde can do, of course. We also want it to be simple to implement the equivalent of FromFormValue; implementing Deserialize is much more complicated.

@kardeiz
Copy link

kardeiz commented Jul 21, 2017

@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 serde with FromForm when it is necessary, though!

@SergioBenitez
Copy link
Member

Unfortunately, we won't be able to get this in for 0.4: pushing to 0.5.

@SergioBenitez SergioBenitez modified the milestones: 0.4.0, 0.5.0 Jul 23, 2018
@TotalKrill
Copy link
Contributor

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?

@lancecarlson
Copy link
Author

@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.

@TotalKrill
Copy link
Contributor

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

@jebrosen
Copy link
Collaborator

jebrosen commented Jan 9, 2021

@TotalKrill see #106 (comment). It's already been mostly implemented; IIRC there were a few rustc bugs (which were hopefully fixed in the most recent stable) and a few bugs in dependencies.

@SergioBenitez SergioBenitez self-assigned this Jan 12, 2021
@SergioBenitez
Copy link
Member

All of the issue are now resolved! I should be able to land this very soon!

SergioBenitez added a commit that referenced this issue Feb 26, 2021
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.
SergioBenitez added a commit that referenced this issue Mar 2, 2021
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.
SergioBenitez added a commit that referenced this issue Mar 4, 2021
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.
@ittorchbearer
Copy link

ittorchbearer commented Apr 21, 2022

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.
see comment below for corrected link
Plato-solutions@b13a664

@SergioBenitez
Copy link
Member

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.

Plato-solutions@b13a664

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.

@ittorchbearer
Copy link

ittorchbearer commented Apr 21, 2022

The important part of the code snippet is the Configuration struct recursion with struct and its' final field.

#[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>,
}

#[derive(FromForm, JsonSchema, UriDisplayQuery))]
pub struct ExecutionDb {
    pub configuration: Json<Configuration>,
}

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:

error[E0391]: cycle detected when computing layout of `rocket::form::from_form::VecContext<configuration::Configuration>`
   |
   = note: ...which requires computing layout of `core::option::Option<<configuration::Configuration as rocket::form::from_form::FromForm>::Context>`...
   = note: ...which requires computing layout of `core::option::Option<configuration::_::FromFormGeneratedContext>`...
   = note: ...which requires computing layout of `configuration::_::FromFormGeneratedContext`...
   = note: ...which requires computing layout of `core::option::Option<<alloc::vec::Vec<configuration::Configuration> as rocket::form::from_form::FromForm>::Context>`...
   = note: ...which requires computing layout of `core::option::Option<rocket::form::from_form::VecContext<configuration::Configuration>>`...
   = note: ...which again requires computing layout of `rocket::form::from_form::VecContext<configuration::Configuration>`, completing the cycle
note: cycle used when optimizing MIR for `configuration::_::<impl at src/configuration.rs:30:45: 30:53>::push_value::{closure#7}`
  --> src/configuration.rs:39:26
   |
39 |     pub post_activities: Vec<Configuration>,

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.
Plato-solutions@2cdd5c1

@SergioBenitez
Copy link
Member

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>>,
}

@ittorchbearer
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted An accepted request or suggestion request Request for new functionality
Projects
None yet
Development

No branches or pull requests

9 participants