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::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(_)

View File

@ -103,7 +103,7 @@ use crate::http::uncased::AsUncased;
/// `FromFormField`. Their behavior is documented in the table below.
///
/// | Type | Strategy | Default | Data | Value | Notes |
/// |--------------------|-------------|-------------------|--------|--------|----------------------------------------------------|
/// |------------------------|-------------|-------------------|--------|--------|----------------------------------------------------|
/// | [`Strict<T>`] | **strict** | if `strict` `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` |
@ -111,6 +111,10 @@ use crate::http::uncased::AsUncased;
/// | `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` |
/// | [`BTreeMap<K, V>`] | _inherit_ | `BTreeMap::new()` | if `V` | if `V` | `K: FromForm + Ord`, `V: FromForm` |
/// | [`Range<T>`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `start`, `end` fields |
/// | [`RangeFrom<T>`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `start` field |
/// | [`RangeTo<T>`] | _inherit_ | **no default** | if `T` | if `T` | `T: FromForm`, expects `end` field |
/// | [`RangeToInclusive<T>`]| _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}` |
@ -140,6 +144,10 @@ use crate::http::uncased::AsUncased;
/// [`SocketAddr`]: std::net::SocketAddr
/// [`SocketAddrV4`]: std::net::SocketAddrV4
/// [`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
///
@ -931,3 +939,51 @@ impl<'v, T: FromForm<'v> + Sync> FromForm<'v> for Arc<T> {
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!(
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]");

View File

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