Impl 'FromForm' for char, 'Range' types.

Implements 'FromForm' for:

  * `char`
  * `Range<T: FromForm>`
  * `RangeFrom<T: FromForm>`
  * `RangeTo<T: FromForm>`
  * `RangeToInclusive<T: FromForm>`

Resolves #2759.
This commit is contained in:
Sergio Benitez 2024-05-02 16:00:38 -07:00
parent fd2094c5f3
commit 5f9ff3f3af
4 changed files with 94 additions and 26 deletions

View File

@ -3,6 +3,7 @@
use std::{fmt, io}; use std::{fmt, io};
use std::num::{ParseIntError, ParseFloatError}; use std::num::{ParseIntError, ParseFloatError};
use std::str::{Utf8Error, ParseBoolError}; use std::str::{Utf8Error, ParseBoolError};
use std::char::ParseCharError;
use std::net::AddrParseError; use std::net::AddrParseError;
use std::borrow::Cow; use std::borrow::Cow;
@ -200,6 +201,8 @@ pub enum ErrorKind<'v> {
Multipart(multer::Error), Multipart(multer::Error),
/// A string was invalid UTF-8. /// A string was invalid UTF-8.
Utf8(Utf8Error), Utf8(Utf8Error),
/// A value failed to parse as a char.
Char(ParseCharError),
/// A value failed to parse as an integer. /// A value failed to parse as an integer.
Int(ParseIntError), Int(ParseIntError),
/// A value failed to parse as a boolean. /// A value failed to parse as a boolean.
@ -857,6 +860,7 @@ impl fmt::Display for ErrorKind<'_> {
ErrorKind::Custom(_, e) => e.fmt(f)?, ErrorKind::Custom(_, e) => e.fmt(f)?,
ErrorKind::Multipart(e) => write!(f, "invalid multipart: {}", e)?, ErrorKind::Multipart(e) => write!(f, "invalid multipart: {}", e)?,
ErrorKind::Utf8(e) => write!(f, "invalid UTF-8: {}", e)?, ErrorKind::Utf8(e) => write!(f, "invalid UTF-8: {}", e)?,
ErrorKind::Char(e) => write!(f, "invalid character: {}", e)?,
ErrorKind::Int(e) => write!(f, "invalid integer: {}", e)?, ErrorKind::Int(e) => write!(f, "invalid integer: {}", e)?,
ErrorKind::Bool(e) => write!(f, "invalid boolean: {}", e)?, ErrorKind::Bool(e) => write!(f, "invalid boolean: {}", e)?,
ErrorKind::Float(e) => write!(f, "invalid float: {}", e)?, ErrorKind::Float(e) => write!(f, "invalid float: {}", e)?,
@ -885,6 +889,7 @@ impl crate::http::ext::IntoOwned for ErrorKind<'_> {
Custom(s, e) => Custom(s, e), Custom(s, e) => Custom(s, e),
Multipart(e) => Multipart(e), Multipart(e) => Multipart(e),
Utf8(e) => Utf8(e), Utf8(e) => Utf8(e),
Char(e) => Char(e),
Int(e) => Int(e), Int(e) => Int(e),
Bool(e) => Bool(e), Bool(e) => Bool(e),
Float(e) => Float(e), Float(e) => Float(e),
@ -985,6 +990,7 @@ macro_rules! impl_from_for {
impl_from_for!(<'a> Utf8Error => ErrorKind<'a> as Utf8); impl_from_for!(<'a> Utf8Error => ErrorKind<'a> as Utf8);
impl_from_for!(<'a> ParseIntError => ErrorKind<'a> as Int); impl_from_for!(<'a> ParseIntError => ErrorKind<'a> as Int);
impl_from_for!(<'a> ParseCharError => ErrorKind<'a> as Char);
impl_from_for!(<'a> ParseFloatError => ErrorKind<'a> as Float); impl_from_for!(<'a> ParseFloatError => ErrorKind<'a> as Float);
impl_from_for!(<'a> ParseBoolError => ErrorKind<'a> as Bool); impl_from_for!(<'a> ParseBoolError => ErrorKind<'a> as Bool);
impl_from_for!(<'a> AddrParseError => ErrorKind<'a> as Addr); impl_from_for!(<'a> AddrParseError => ErrorKind<'a> as Addr);
@ -1024,6 +1030,7 @@ impl Entity {
| ErrorKind::OutOfRange { .. } | ErrorKind::OutOfRange { .. }
| ErrorKind::Validation { .. } | ErrorKind::Validation { .. }
| ErrorKind::Utf8(_) | ErrorKind::Utf8(_)
| ErrorKind::Char(_)
| ErrorKind::Int(_) | ErrorKind::Int(_)
| ErrorKind::Float(_) | ErrorKind::Float(_)
| ErrorKind::Bool(_) | ErrorKind::Bool(_)

View File

@ -102,29 +102,33 @@ use crate::http::uncased::AsUncased;
/// applications will never need a custom implementation of `FromForm` or /// applications will never need a custom implementation of `FromForm` or
/// `FromFormField`. Their behavior is documented in the table below. /// `FromFormField`. Their behavior is documented in the table below.
/// ///
/// | Type | Strategy | Default | Data | Value | Notes | /// | Type | Strategy | Default | Data | Value | Notes |
/// |--------------------|-------------|-------------------|--------|--------|----------------------------------------------------| /// |------------------------|-------------|-------------------|--------|--------|----------------------------------------------------|
/// | [`Strict<T>`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` | /// | [`Strict<T>`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` |
/// | [`Lenient<T>`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` | /// | [`Lenient<T>`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` |
/// | `Option<T>` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` | /// | `Option<T>` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` |
/// | [`Result<T>`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` | /// | [`Result<T>`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` |
/// | `Vec<T>` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` | /// | `Vec<T>` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` |
/// | [`HashMap<K, V>`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` | /// | [`HashMap<K, V>`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` |
/// | [`BTreeMap<K, V>`] | _inherit_ | `BTreeMap::new()` | if `V` | if `V` | `K: FromForm + Ord`, `V: FromForm` | /// | [`BTreeMap<K, V>`] | _inherit_ | `BTreeMap::new()` | if `V` | if `V` | `K: FromForm + Ord`, `V: FromForm` |
/// | `bool` | _inherit_ | `false` | No | Yes | `"yes"/"on"/"true"`, `"no"/"off"/"false"` | /// | [`Range<T>`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `start`, `end` fields |
/// | (un)signed int | _inherit_ | **no default** | No | Yes | `{u,i}{size,8,16,32,64,128}` | /// | [`RangeFrom<T>`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `start` field |
/// | _nonzero_ int | _inherit_ | **no default** | No | Yes | `NonZero{I,U}{size,8,16,32,64,128}` | /// | [`RangeTo<T>`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `end` field |
/// | float | _inherit_ | **no default** | No | Yes | `f{32,64}` | /// | [`RangeToInclusive<T>`]| _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `end` field |
/// | `&str` | _inherit_ | **no default** | Yes | Yes | Percent-decoded. Data limit `string` applies. | /// | `bool` | _inherit_ | `false` | No | Yes | `"yes"/"on"/"true"`, `"no"/"off"/"false"` |
/// | `&[u8]` | _inherit_ | **no default** | Yes | Yes | Raw bytes. Data limit `bytes` applies. | /// | (un)signed int | _inherit_ | **no default** | No | Yes | `{u,i}{size,8,16,32,64,128}` |
/// | `String` | _inherit_ | **no default** | Yes | Yes | Exactly `&str`, but owned. Prefer `&str`. | /// | _nonzero_ int | _inherit_ | **no default** | No | Yes | `NonZero{I,U}{size,8,16,32,64,128}` |
/// | IP Address | _inherit_ | **no default** | No | Yes | [`IpAddr`], [`Ipv4Addr`], [`Ipv6Addr`] | /// | float | _inherit_ | **no default** | No | Yes | `f{32,64}` |
/// | Socket Address | _inherit_ | **no default** | No | Yes | [`SocketAddr`], [`SocketAddrV4`], [`SocketAddrV6`] | /// | `&str` | _inherit_ | **no default** | Yes | Yes | Percent-decoded. Data limit `string` applies. |
/// | [`TempFile`] | _inherit_ | **no default** | Yes | Yes | Data limits apply. See [`TempFile`]. | /// | `&[u8]` | _inherit_ | **no default** | Yes | Yes | Raw bytes. Data limit `bytes` applies. |
/// | [`Capped<C>`] | _inherit_ | **no default** | Yes | Yes | `C` is `&str`, `String`, `&[u8]` or `TempFile`. | /// | `String` | _inherit_ | **no default** | Yes | Yes | Exactly `&str`, but owned. Prefer `&str`. |
/// | [`time::Date`] | _inherit_ | **no default** | No | Yes | `%F` (`YYYY-MM-DD`). HTML "date" input. | /// | IP Address | _inherit_ | **no default** | No | Yes | [`IpAddr`], [`Ipv4Addr`], [`Ipv6Addr`] |
/// | [`time::DateTime`] | _inherit_ | **no default** | No | Yes | `%FT%R` or `%FT%T` (`YYYY-MM-DDTHH:MM[:SS]`) | /// | Socket Address | _inherit_ | **no default** | No | Yes | [`SocketAddr`], [`SocketAddrV4`], [`SocketAddrV6`] |
/// | [`time::Time`] | _inherit_ | **no default** | No | Yes | `%R` or `%T` (`HH:MM[:SS]`) | /// | [`TempFile`] | _inherit_ | **no default** | Yes | Yes | Data limits apply. See [`TempFile`]. |
/// | [`Capped<C>`] | _inherit_ | **no default** | Yes | Yes | `C` is `&str`, `String`, `&[u8]` or `TempFile`. |
/// | [`time::Date`] | _inherit_ | **no default** | No | Yes | `%F` (`YYYY-MM-DD`). HTML "date" input. |
/// | [`time::DateTime`] | _inherit_ | **no default** | No | Yes | `%FT%R` or `%FT%T` (`YYYY-MM-DDTHH:MM[:SS]`) |
/// | [`time::Time`] | _inherit_ | **no default** | No | Yes | `%R` or `%T` (`HH:MM[:SS]`) |
/// ///
/// [`Result<T>`]: crate::form::Result /// [`Result<T>`]: crate::form::Result
/// [`Strict<T>`]: crate::form::Strict /// [`Strict<T>`]: crate::form::Strict
@ -140,6 +144,10 @@ use crate::http::uncased::AsUncased;
/// [`SocketAddr`]: std::net::SocketAddr /// [`SocketAddr`]: std::net::SocketAddr
/// [`SocketAddrV4`]: std::net::SocketAddrV4 /// [`SocketAddrV4`]: std::net::SocketAddrV4
/// [`SocketAddrV6`]: std::net::SocketAddrV6 /// [`SocketAddrV6`]: std::net::SocketAddrV6
/// [`Range<T>`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html
/// [`RangeFrom<T>`]: https://doc.rust-lang.org/stable/std/ops/struct.RangeFrom.html
/// [`RangeTo<T>`]: https://doc.rust-lang.org/stable/std/ops/struct.RangeTo.html
/// [`RangeToInclusive<T>`]: https://doc.rust-lang.org/stable/std/ops/struct.RangeToInclusive.html
/// ///
/// ## Additional Notes /// ## Additional Notes
/// ///
@ -931,3 +939,51 @@ impl<'v, T: FromForm<'v> + Sync> FromForm<'v> for Arc<T> {
T::finalize(this).map(Arc::new) T::finalize(this).map(Arc::new)
} }
} }
macro_rules! impl_via_proxy {
($R:ident => struct $T:ident <$($G:ident),*> { $($f:ident : $F:ident),* }) => {
const _: () = {
use super::*;
mod proxy {
#[derive(rocket::FromForm)]
pub struct $T<$($G),*> {
$(pub $f : $F),*
}
}
#[crate::async_trait]
impl<'v, $($G: Send),*> FromForm<'v> for $R<$($G),*>
where proxy::$T<$($G),*>: FromForm<'v>
{
type Context = <proxy::$T<$($G),*> as FromForm<'v>>::Context;
fn init(opts: Options) -> Self::Context {
<proxy::$T<$($G),*>>::init(opts)
}
fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>) {
<proxy::$T<$($G),*>>::push_value(ctxt, field)
}
async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
<proxy::$T<$($G),*>>::push_data(ctxt, field).await
}
fn finalize(this: Self::Context) -> Result<'v, Self> {
let proxy = <proxy::$T<$($G),*>>::finalize(this)?;
Ok($R {
$($f : proxy.$f),*
})
}
}
};
}
}
use std::ops::{Range, RangeFrom, RangeTo, RangeToInclusive};
impl_via_proxy!(Range => struct Range<T> { start: T, end: T });
impl_via_proxy!(RangeFrom => struct RangeFrom<T> { start: T });
impl_via_proxy!(RangeTo => struct RangeTo<T> { end: T });
impl_via_proxy!(RangeToInclusive => struct RangeToInclusive<T> { end: T });

View File

@ -391,6 +391,7 @@ macro_rules! impl_with_parse {
} }
impl_with_parse!( impl_with_parse!(
char,
f32, f64, f32, f64,
isize, i8, i16, i32, i64, i128, isize, i8, i16, i32, i64, i128,
usize, u8, u16, u32, u64, u128, usize, u8, u16, u32, u64, u128,
@ -398,7 +399,7 @@ impl_with_parse!(
NonZeroUsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128,
Ipv4Addr, IpAddr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr Ipv4Addr, IpAddr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr
); );
//
// Keep formats in sync with 'FromFormField' impls. // Keep formats in sync with 'FromFormField' impls.
static DATE_FMT: &[FormatItem<'_>] = format_description!("[year padding:none]-[month]-[day]"); static DATE_FMT: &[FormatItem<'_>] = format_description!("[year padding:none]-[month]-[day]");
static TIME_FMT1: &[FormatItem<'_>] = format_description!("[hour padding:none]:[minute]:[second]"); static TIME_FMT1: &[FormatItem<'_>] = format_description!("[hour padding:none]:[minute]:[second]");

View File

@ -107,6 +107,9 @@
//! [testing guide]: https://rocket.rs/master/guide/testing/#testing //! [testing guide]: https://rocket.rs/master/guide/testing/#testing
//! [Figment]: https://docs.rs/figment //! [Figment]: https://docs.rs/figment
// Allows using Rocket's codegen in Rocket itself.
extern crate self as rocket;
/// These are public dependencies! Update docs if these are changed, especially /// These are public dependencies! Update docs if these are changed, especially
/// figment's version number in docs. /// figment's version number in docs.
#[doc(hidden)] #[doc(hidden)]
@ -149,11 +152,12 @@ pub mod tls;
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))] #[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
pub mod mtls; pub mod mtls;
#[path = "rocket.rs"]
mod rkt;
mod util; mod util;
mod server; mod server;
mod lifecycle; mod lifecycle;
mod state; mod state;
mod rocket;
mod router; mod router;
mod phase; mod phase;
mod erased; mod erased;
@ -171,7 +175,7 @@ mod erased;
#[doc(inline)] pub use crate::error::Error; #[doc(inline)] pub use crate::error::Error;
#[doc(inline)] pub use crate::sentinel::Sentinel; #[doc(inline)] pub use crate::sentinel::Sentinel;
#[doc(inline)] pub use crate::request::Request; #[doc(inline)] pub use crate::request::Request;
#[doc(inline)] pub use crate::rocket::Rocket; #[doc(inline)] pub use crate::rkt::Rocket;
#[doc(inline)] pub use crate::shutdown::Shutdown; #[doc(inline)] pub use crate::shutdown::Shutdown;
#[doc(inline)] pub use crate::state::State; #[doc(inline)] pub use crate::state::State;