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

Support for multi-valued select elements #205

Closed
trombonehero opened this issue Feb 22, 2017 · 14 comments
Closed

Support for multi-valued select elements #205

trombonehero opened this issue Feb 22, 2017 · 14 comments
Labels
accepted An accepted request or suggestion request Request for new functionality
Milestone

Comments

@trombonehero
Copy link

Thanks for Rocket's request guard mechanism: it's shockingly elegant. However, when inputting form data, there doesn't seem to be any support for <select multiple>...</select> elements. I'd like to be able to write something like:

#[derive(FromForm)]
struct Foo {
    names: Vec<String>
}

so that the form data foo=a&foo=b&foo=c turns into a Vec of "a", "b" and "c". Currently, if I try to use the above struct, I see:

the trait `rocket::request::FromFormValue<'_>` is not implemented for `std::vec::Vec<std::string::String>`

and if I change Vec to Option, I only see the last value of foo (in this case, "c"): the other values are silently dropped.

@SergioBenitez SergioBenitez added the request Request for new functionality label Feb 24, 2017
@MrWinstead
Copy link

MrWinstead commented Feb 25, 2017

Trombonehero (or whomever is interested),

Do you see yourself needing any other collection type other than just a vector as a container in your structure for the first implementation? If std::Vec is good enough, would the following behavior make sense?

#[derive(FromForm)]
struct Foo {
    names: Vec<String>
}

Non-rust-like Pseudocode:

method parse_vector 
    returns Vector of type T
    input form_variable_name of type String, raw_form of type String
    encounters error condition T::CannotParseFromString
  Vector to_return;
  MultiKeyHashMap form_keys_values = form_split(raw_form);

  foreach formitem in form_keys_values.get_all_values(form_variable_name)
    to_return.push(T::fromstring(formitem));
  end foreach

  return to_return;
end method

Which means that if there were no elements specified or if the specified elements were all empty, we'd give the user an empty vector. If any of the elements can't be converted into the specified type, we'll report a parsing error.

@trombonehero
Copy link
Author

Hi,

That does, indeed, sound like what I'm thinking of. I have been using HashSet as well, for situations in which I don't care about ordering but do want to compute differences (e.g., "roles that we should add to a user" and "roles that we should remove from a user"), but that should be straightforward enough to get from a Vec.

I think the difficulty with this issue is that it would require a generalization of FromFormValue that accumulates values rather than mapping them. Perhaps "difficulty" isn't the right word, but it would be an API change.

For reference, my current approach (which I'd like to replace with #[derive(FromForm)]) is:

    fn from_form_items(form_items: &mut request::FormItems<'f>) -> Result<Self, Self::Error> {
        let mut update = UserUpdate {
            name: String::new(),
            email: String::new(),
            phone: None,
            roles: HashSet::new(),
        };

        for (k, v) in form_items {
            let key: &str = &*k;
            let value = v.into();

            match key {
                "name" => update.name = value,
                "email" => update.email = value,
                "phone" => update.phone = Some(value),
                "roles" => { update.roles.insert(value); },
                _ => {
                    return Err(Error::InvalidData(format!["invalid form data name: '{}'", key]));
                },
            }
        }

        Ok(update)
    }

@mehcode
Copy link
Contributor

mehcode commented Feb 25, 2017

I think it'd be important to not constrain this to Vec and allow anything that implements FromIterator.

@MrWinstead
Copy link

MrWinstead commented Feb 25, 2017

@trombonehero, That makes sense.

@mehcode, I was wondering what the generic trait was for Rust in this case. I think there's a question then for whether we should support deeper nested types than a two-level generic. I'm not good enough at rust to design some sort of recursive generic unroller where each step of the generic has either a FromString or FromIterator trait, but I could see a future implementation of that.

Oh, and perhaps for the maintainers, the current API for this doesn't allow a struct implementing the FromFormValue to store state between multiple calls to from_form_value. This would be super-easy to implement if I could change that API to allow for a reference to self. Thus, I'm going to add that in for the first iteration; just let me know if you disagree and I'll change it.

@MrWinstead
Copy link

Branch 205 in my fork of the project has an initial implementation for vectors of items which have the FromStr trait.

In order for this to work, I added an argument in the trait which allows for a reference to the instance which is generated in codegen/src/decorators/derive_form.rs around line 180. The problem with this is now there are tons of unused variable warnings for the default types which don't use the backreference. The alternative to this would have been to modify the arm generation and inspect the field type to see if it is something which has the Extend trait and create a separate code path which passes in the backreference and also instantiates the instance.

Where my experience falls short is in how to use the syntax crate and inspect a node Path's type's existing traits. If someone knew how to do that, I'd put it on line 169 in codegen/src/decorators/derive_form.rs.

I'll let these comments simmer a little before submitting a PR.

@nCrazed
Copy link

nCrazed commented Nov 6, 2017

Could the current behaviour be changed to aggregate all values under the same name while adjusting the method signature from:

fn from_form_value(form_value: &'v RawStr) -> Result<Self, Self::Error>

to:

fn from_form_value(form_value: Vec<&'v RawStr>) -> Result<Self, Self::Error>

This would have the added benefit of allowing a single value field implementation to verify that there's actually only one incoming value.

@nCrazed
Copy link

nCrazed commented Nov 7, 2017

This can then further be expanded to something like:

fn from_form_value(form_value: Vec<&'v Field>) -> Result<Self, Self::Error>

where Field contains the full input field name (e.g. name, name[0], name[0][title], name[0][first] etc.) and the vector contains all the input fields matching the root name.

So given a request to ?simple=one&names=John&names=Jane&other[0]=x&other[2]=y and form:

#[derive(FromForm)]
struct Foo {
    simple: String,
    names: Vec<String>,
    other: HashMap<usize,String>,
}

the implementations would look something like this:

  fn from_form_value(form_value: Vec<&'v Field>) -> Result<String, Self::Error> {
    Ok(form_values.first().value)
  }

  fn from_form_value(form_value: Vec<&'v Field>) -> Result<Vec<String>, Self::Error> {
    Ok(form_values.iter().map(|x|x.value).collect())
  }

  fn from_form_value(form_value: Vec<&'v Field>) -> Result<HashMap<usize,String>, Self::Error> {
    let mut map: HashMap<usize,String> = HashMap::new();
    for field in form_value: {
        let (key, tail) = field.leftmost_key(); // allow arbitrary depth
        map.insert(key, field.value);
    }
    Ok(map)
  }

yielding:

Foo {
    simple: "one",
    names: ["John","Jane"],
    other: [0 => "x", 1 => "y"],
}

@SergioBenitez
Copy link
Member

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

@mjanda
Copy link

mjanda commented Apr 26, 2020

Hello,
any news on this issue? Just want to know if I should wait for this or implement some workaround (probably collecting values into single comma separated field and parsing it from there - fortunately, my use case is trival, just a bunch of checkboxes with numerical values).

Thank you

@jebrosen
Copy link
Collaborator

Nobody has really worked on an integrated solution as far as I know - you have to implement FromForm manually or work around it another way like you described.

@mjanda
Copy link

mjanda commented Apr 28, 2020

@jebrosen Thank you for suggesting FromForm implementation. Somehow I managed to hack together my own derive that works with Vec.

@brownjohnf
Copy link

@mjanda I'm looking for something similar; would you mind sharing your solution?

@TotalKrill
Copy link
Contributor

TotalKrill commented Jan 6, 2021

Since this seems to have a work-around, and is on the list for 0.5 milestones, could this maybe be pushed to 0.6? I would very much like to see a release of 0.5 expedited.

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.
@trombonehero
Copy link
Author

Hooray! Wonderful...

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