From 5f9ff3f3aff3f1ce587c37c645f56889aa8fdb7b Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 2 May 2024 16:00:38 -0700 Subject: [PATCH] Impl 'FromForm' for char, 'Range' types. Implements 'FromForm' for: * `char` * `Range` * `RangeFrom` * `RangeTo` * `RangeToInclusive` Resolves #2759. --- core/lib/src/form/error.rs | 7 ++ core/lib/src/form/from_form.rs | 102 +++++++++++++++++++++------ core/lib/src/form/from_form_field.rs | 3 +- core/lib/src/lib.rs | 8 ++- 4 files changed, 94 insertions(+), 26 deletions(-) diff --git a/core/lib/src/form/error.rs b/core/lib/src/form/error.rs index d6a42a55..b2c2c06e 100644 --- a/core/lib/src/form/error.rs +++ b/core/lib/src/form/error.rs @@ -3,6 +3,7 @@ use std::{fmt, io}; use std::num::{ParseIntError, ParseFloatError}; use std::str::{Utf8Error, ParseBoolError}; +use std::char::ParseCharError; use std::net::AddrParseError; use std::borrow::Cow; @@ -200,6 +201,8 @@ pub enum ErrorKind<'v> { Multipart(multer::Error), /// A string was invalid UTF-8. Utf8(Utf8Error), + /// A value failed to parse as a char. + Char(ParseCharError), /// A value failed to parse as an integer. Int(ParseIntError), /// A value failed to parse as a boolean. @@ -857,6 +860,7 @@ impl fmt::Display for ErrorKind<'_> { ErrorKind::Custom(_, e) => e.fmt(f)?, ErrorKind::Multipart(e) => write!(f, "invalid multipart: {}", 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::Bool(e) => write!(f, "invalid boolean: {}", 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), Multipart(e) => Multipart(e), Utf8(e) => Utf8(e), + Char(e) => Char(e), Int(e) => Int(e), Bool(e) => Bool(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> 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> ParseBoolError => ErrorKind<'a> as Bool); impl_from_for!(<'a> AddrParseError => ErrorKind<'a> as Addr); @@ -1024,6 +1030,7 @@ impl Entity { | ErrorKind::OutOfRange { .. } | ErrorKind::Validation { .. } | ErrorKind::Utf8(_) + | ErrorKind::Char(_) | ErrorKind::Int(_) | ErrorKind::Float(_) | ErrorKind::Bool(_) diff --git a/core/lib/src/form/from_form.rs b/core/lib/src/form/from_form.rs index 7bf79148..2f9d443d 100644 --- a/core/lib/src/form/from_form.rs +++ b/core/lib/src/form/from_form.rs @@ -102,29 +102,33 @@ use crate::http::uncased::AsUncased; /// applications will never need a custom implementation of `FromForm` or /// `FromFormField`. Their behavior is documented in the table below. /// -/// | Type | Strategy | Default | Data | Value | Notes | -/// |--------------------|-------------|-------------------|--------|--------|----------------------------------------------------| -/// | [`Strict`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` | -/// | [`Lenient`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` | -/// | `Option` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` | -/// | [`Result`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` | -/// | `Vec` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` | -/// | [`HashMap`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` | -/// | [`BTreeMap`] | _inherit_ | `BTreeMap::new()` | if `V` | if `V` | `K: FromForm + Ord`, `V: FromForm` | -/// | `bool` | _inherit_ | `false` | No | Yes | `"yes"/"on"/"true"`, `"no"/"off"/"false"` | -/// | (un)signed int | _inherit_ | **no default** | No | Yes | `{u,i}{size,8,16,32,64,128}` | -/// | _nonzero_ int | _inherit_ | **no default** | No | Yes | `NonZero{I,U}{size,8,16,32,64,128}` | -/// | float | _inherit_ | **no default** | No | Yes | `f{32,64}` | -/// | `&str` | _inherit_ | **no default** | Yes | Yes | Percent-decoded. Data limit `string` applies. | -/// | `&[u8]` | _inherit_ | **no default** | Yes | Yes | Raw bytes. Data limit `bytes` applies. | -/// | `String` | _inherit_ | **no default** | Yes | Yes | Exactly `&str`, but owned. Prefer `&str`. | -/// | IP Address | _inherit_ | **no default** | No | Yes | [`IpAddr`], [`Ipv4Addr`], [`Ipv6Addr`] | -/// | Socket Address | _inherit_ | **no default** | No | Yes | [`SocketAddr`], [`SocketAddrV4`], [`SocketAddrV6`] | -/// | [`TempFile`] | _inherit_ | **no default** | Yes | Yes | Data limits apply. See [`TempFile`]. | -/// | [`Capped`] | _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]`) | +/// | Type | Strategy | Default | Data | Value | Notes | +/// |------------------------|-------------|-------------------|--------|--------|----------------------------------------------------| +/// | [`Strict`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` | +/// | [`Lenient`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` | +/// | `Option` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` | +/// | [`Result`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` | +/// | `Vec` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` | +/// | [`HashMap`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` | +/// | [`BTreeMap`] | _inherit_ | `BTreeMap::new()` | if `V` | if `V` | `K: FromForm + Ord`, `V: FromForm` | +/// | [`Range`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `start`, `end` fields | +/// | [`RangeFrom`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `start` field | +/// | [`RangeTo`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `end` field | +/// | [`RangeToInclusive`]| _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `end` field | +/// | `bool` | _inherit_ | `false` | No | Yes | `"yes"/"on"/"true"`, `"no"/"off"/"false"` | +/// | (un)signed int | _inherit_ | **no default** | No | Yes | `{u,i}{size,8,16,32,64,128}` | +/// | _nonzero_ int | _inherit_ | **no default** | No | Yes | `NonZero{I,U}{size,8,16,32,64,128}` | +/// | float | _inherit_ | **no default** | No | Yes | `f{32,64}` | +/// | `&str` | _inherit_ | **no default** | Yes | Yes | Percent-decoded. Data limit `string` applies. | +/// | `&[u8]` | _inherit_ | **no default** | Yes | Yes | Raw bytes. Data limit `bytes` applies. | +/// | `String` | _inherit_ | **no default** | Yes | Yes | Exactly `&str`, but owned. Prefer `&str`. | +/// | IP Address | _inherit_ | **no default** | No | Yes | [`IpAddr`], [`Ipv4Addr`], [`Ipv6Addr`] | +/// | Socket Address | _inherit_ | **no default** | No | Yes | [`SocketAddr`], [`SocketAddrV4`], [`SocketAddrV6`] | +/// | [`TempFile`] | _inherit_ | **no default** | Yes | Yes | Data limits apply. See [`TempFile`]. | +/// | [`Capped`] | _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`]: crate::form::Result /// [`Strict`]: crate::form::Strict @@ -140,6 +144,10 @@ use crate::http::uncased::AsUncased; /// [`SocketAddr`]: std::net::SocketAddr /// [`SocketAddrV4`]: std::net::SocketAddrV4 /// [`SocketAddrV6`]: std::net::SocketAddrV6 +/// [`Range`]: https://doc.rust-lang.org/stable/std/ops/struct.Range.html +/// [`RangeFrom`]: https://doc.rust-lang.org/stable/std/ops/struct.RangeFrom.html +/// [`RangeTo`]: https://doc.rust-lang.org/stable/std/ops/struct.RangeTo.html +/// [`RangeToInclusive`]: https://doc.rust-lang.org/stable/std/ops/struct.RangeToInclusive.html /// /// ## Additional Notes /// @@ -931,3 +939,51 @@ impl<'v, T: FromForm<'v> + Sync> FromForm<'v> for Arc { 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 = as FromForm<'v>>::Context; + + fn init(opts: Options) -> Self::Context { + >::init(opts) + } + + fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>) { + >::push_value(ctxt, field) + } + + async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) { + >::push_data(ctxt, field).await + } + + fn finalize(this: Self::Context) -> Result<'v, Self> { + let proxy = >::finalize(this)?; + Ok($R { + $($f : proxy.$f),* + }) + } + } + }; + } +} + +use std::ops::{Range, RangeFrom, RangeTo, RangeToInclusive}; + +impl_via_proxy!(Range => struct Range { start: T, end: T }); +impl_via_proxy!(RangeFrom => struct RangeFrom { start: T }); +impl_via_proxy!(RangeTo => struct RangeTo { end: T }); +impl_via_proxy!(RangeToInclusive => struct RangeToInclusive { end: T }); diff --git a/core/lib/src/form/from_form_field.rs b/core/lib/src/form/from_form_field.rs index 7e733133..2a7f5ab2 100644 --- a/core/lib/src/form/from_form_field.rs +++ b/core/lib/src/form/from_form_field.rs @@ -391,6 +391,7 @@ macro_rules! impl_with_parse { } impl_with_parse!( + char, f32, f64, isize, i8, i16, i32, i64, i128, usize, u8, u16, u32, u64, u128, @@ -398,7 +399,7 @@ impl_with_parse!( NonZeroUsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, Ipv4Addr, IpAddr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr ); -// + // Keep formats in sync with 'FromFormField' impls. static DATE_FMT: &[FormatItem<'_>] = format_description!("[year padding:none]-[month]-[day]"); static TIME_FMT1: &[FormatItem<'_>] = format_description!("[hour padding:none]:[minute]:[second]"); diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index bbf10cc3..be2e2d8e 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -107,6 +107,9 @@ //! [testing guide]: https://rocket.rs/master/guide/testing/#testing //! [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 /// figment's version number in docs. #[doc(hidden)] @@ -149,11 +152,12 @@ pub mod tls; #[cfg_attr(nightly, doc(cfg(feature = "mtls")))] pub mod mtls; +#[path = "rocket.rs"] +mod rkt; mod util; mod server; mod lifecycle; mod state; -mod rocket; mod router; mod phase; mod erased; @@ -171,7 +175,7 @@ mod erased; #[doc(inline)] pub use crate::error::Error; #[doc(inline)] pub use crate::sentinel::Sentinel; #[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::state::State;