rocket_community/form/
validate.rs

1//! Form field validation routines.
2//!
3//! Each function in this module can be used as the target of the
4//! `field(validate)` field attribute of the `FromForm` derive.
5//!
6//! ```rust
7//! # extern crate rocket_community as rocket;
8//! use rocket::form::FromForm;
9//!
10//! #[derive(FromForm)]
11//! struct MyForm<'r> {
12//!     #[field(validate = range(2..10))]
13//!     id: usize,
14//!     #[field(validate = omits("password"))]
15//!     password: &'r str,
16//! }
17//! ```
18//!
19//! The `validate` parameter takes any expression that returns a
20//! [`form::Result<()>`](crate::form::Result). If the expression is a function
21//! call, a reference to the field is inserted as the first parameter. Thus,
22//! functions calls to `validate` must take a reference to _some_ type,
23//! typically a generic with some bounds, as their first argument.
24//!
25//! ## Custom Error Messages
26//!
27//! To set a custom error messages, it is useful to chain results:
28//!
29//! ```rust
30//! # extern crate rocket_community as rocket;
31//! use rocket::form::{Errors, Error, FromForm};
32//!
33//! #[derive(FromForm)]
34//! struct MyForm<'r> {
35//!     // By defining another function...
36//!     #[field(validate = omits("password").map_err(pass_help))]
37//!     password: &'r str,
38//!     // or inline using the `msg` helper. `or_else` inverts the validator
39//!     #[field(validate = omits("password").or_else(msg!("please omit `password`")))]
40//!     password2: &'r str,
41//!     // You can even refer to the field in the message...
42//!     #[field(validate = range(1..).or_else(msg!("`{}` < 1", self.n)))]
43//!     n: isize,
44//!     // ..or other fields!
45//!     #[field(validate = range(..self.n).or_else(msg!("`{}` > `{}`", self.z, self.n)))]
46//!     z: isize,
47//! }
48//!
49//! fn pass_help<'a>(errors: Errors<'_>) -> Errors<'a> {
50//!     Error::validation("passwords can't contain the text \"password\"").into()
51//! }
52//! ```
53//!
54//! ## Custom Validation
55//!
56//! Custom validation routines can be defined as regular functions. Consider a
57//! routine that tries to validate a credit card number:
58//!
59//! ```rust
60//! # extern crate rocket_community as rocket;
61//! extern crate time;
62//!
63//! use rocket::form::{self, FromForm, Error};
64//!
65//! #[derive(FromForm)]
66//! struct CreditCard {
67//!     #[field(validate = luhn(self.cvv, &self.expiration))]
68//!     number: u64,
69//!     cvv: u16,
70//!     expiration: time::Date,
71//! }
72//!
73//! // Implementation of Luhn validator.
74//! fn luhn<'v>(number: &u64, cvv: u16, exp: &time::Date) -> form::Result<'v, ()> {
75//!     # let valid = false;
76//!     if !valid {
77//!         Err(Error::validation("invalid credit card number"))?;
78//!     }
79//!
80//!     Ok(())
81//! }
82//! ```
83
84use std::borrow::Cow;
85use std::fmt::Debug;
86use std::ops::{Bound, RangeBounds};
87
88use crate::data::{ByteUnit, Capped};
89use rocket_http::ContentType;
90
91use crate::{
92    form::{Error, Result},
93    fs::TempFile,
94};
95
96crate::export! {
97    /// A helper macro for custom validation error messages.
98    ///
99    /// The macro works similar to [`std::format!`]. It generates a form
100    /// [`Validation`] error message. While useful in other contexts, it is
101    /// designed to be chained to validation results in derived `FromForm`
102    /// `#[field]` attributes via `.or_else()` and `.and_then()`.
103    ///
104    /// [`Validation`]: crate::form::error::ErrorKind::Validation
105    /// [`form::validate`]: crate::form::validate
106    ///
107    /// # Example
108    ///
109    /// ```rust
110    /// # extern crate rocket_community as rocket;
111    /// use rocket::form::FromForm;
112    ///
113    /// #[derive(FromForm)]
114    /// struct Person<'r> {
115    ///     #[field(validate = len(3..).or_else(msg!("that's a short name...")))]
116    ///     name: &'r str,
117    ///     #[field(validate = contains('f').and_then(msg!("please, no `f`!")))]
118    ///     non_f_name: &'r str,
119    /// }
120    /// ```
121    ///
122    /// _**Note:** this macro _never_ needs to be imported when used with a
123    /// `FromForm` derive; all items in [`form::validate`] are automatically in
124    /// scope in `FromForm` derive attributes._
125    ///
126    /// See the [top-level docs](crate::form::validate) for more examples.
127    ///
128    /// # Syntax
129    ///
130    /// The macro has the following "signatures":
131    ///
132    /// ## Variant 1
133    ///
134    /// ```rust
135    /// # extern crate rocket_community as rocket;
136    /// # use rocket::form;
137    /// # trait Expr {}
138    /// fn msg<'a, T, P, E: Expr>(expr: E) -> impl Fn(P) -> form::Result<'a, T>
139    /// # { |_| unimplemented!() }
140    /// ```
141    ///
142    /// Takes any expression and returns a function that takes any argument type
143    /// and evaluates to a [`form::Result`](crate::form::Result) with an `Ok` of
144    /// any type. The `Result` is guaranteed to be an `Err` of kind
145    /// [`Validation`] with `expr` as the message.
146    ///
147    /// ## Variant 2
148    ///
149    /// ```rust
150    /// # extern crate rocket_community as rocket;
151    /// # use rocket::form;
152    /// # trait Format {}
153    /// # trait Args {}
154    /// fn msg<'a, T, P, A: Args>(fmt: &str, args: A) -> impl Fn(P) -> form::Result<'a, T>
155    /// # { |_| unimplemented!() }
156    /// ```
157    ///
158    /// Invokes the first variant as `msg!(format!(fmt, args))`.
159    macro_rules! msg {
160        ($e:expr) => (|_| {
161            Err($crate::form::Errors::from(
162                    $crate::form::Error::validation($e)
163            )) as $crate::form::Result<()>
164        });
165        ($($arg:tt)*) => ($crate::form::validate::msg!(format!($($arg)*)));
166    }
167}
168
169/// Equality validator: succeeds exactly when `a` == `b`, using [`PartialEq`].
170///
171/// On error, returns a validation error with the following message:
172///
173/// ```text
174/// value does not match expected value
175/// ```
176///
177/// # Example
178///
179/// ```rust
180/// # extern crate rocket_community as rocket;
181/// use rocket::form::{FromForm, FromFormField};
182///
183/// #[derive(FromFormField, PartialEq)]
184/// enum Kind {
185///     Car,
186///     Truck
187/// }
188///
189/// #[derive(FromForm)]
190/// struct Foo<'r> {
191///     #[field(validate = eq("Bob Marley"))]
192///     name: &'r str,
193///     #[field(validate = eq(Kind::Car))]
194///     vehicle: Kind,
195///     #[field(validate = eq(&[5, 7, 8]))]
196///     numbers: Vec<usize>,
197/// }
198/// ```
199pub fn eq<'v, A, B>(a: &A, b: B) -> Result<'v, ()>
200where
201    A: PartialEq<B>,
202{
203    if a != &b {
204        Err(Error::validation("value does not match expected value"))?
205    }
206
207    Ok(())
208}
209
210/// Debug equality validator: like [`eq()`] but mentions `b` in the error
211/// message.
212///
213/// The is identical to [`eq()`] except that `b` must be `Debug` and the error
214/// message is as follows, where `$b` is the [`Debug`] representation of `b`:
215///
216/// ```text
217/// value must be $b
218/// ```
219///
220/// # Example
221///
222/// ```rust
223/// # extern crate rocket_community as rocket;
224/// use rocket::form::{FromForm, FromFormField};
225///
226/// #[derive(PartialEq, Debug, Clone, Copy, FromFormField)]
227/// enum Pet { Cat, Dog }
228///
229/// #[derive(FromForm)]
230/// struct Foo {
231///     number: usize,
232///     #[field(validate = dbg_eq(self.number))]
233///     confirm_num: usize,
234///     #[field(validate = dbg_eq(Pet::Dog))]
235///     best_pet: Pet,
236/// }
237/// ```
238pub fn dbg_eq<'v, A, B>(a: &A, b: B) -> Result<'v, ()>
239where
240    A: PartialEq<B>,
241    B: Debug,
242{
243    if a != &b {
244        Err(Error::validation(format!("value must be {:?}", b)))?
245    }
246
247    Ok(())
248}
249
250/// Negative equality validator: succeeds exactly when `a` != `b`, using
251/// [`PartialEq`].
252///
253/// On error, returns a validation error with the following message:
254///
255/// ```text
256/// value is equal to an invalid value
257/// ```
258///
259/// # Example
260///
261/// ```rust
262/// # extern crate rocket_community as rocket;
263/// use rocket::form::{FromForm, FromFormField};
264///
265/// #[derive(FromFormField, PartialEq)]
266/// enum Kind {
267///     Car,
268///     Truck
269/// }
270///
271/// #[derive(FromForm)]
272/// struct Foo<'r> {
273///     #[field(validate = neq("Bob Marley"))]
274///     name: &'r str,
275///     #[field(validate = neq(Kind::Car))]
276///     vehicle: Kind,
277///     #[field(validate = neq(&[5, 7, 8]))]
278///     numbers: Vec<usize>,
279/// }
280/// ```
281pub fn neq<'v, A, B>(a: &A, b: B) -> Result<'v, ()>
282where
283    A: PartialEq<B>,
284{
285    if a == &b {
286        Err(Error::validation("value is equal to an invalid value"))?
287    }
288
289    Ok(())
290}
291
292/// Types for values that have a length.
293///
294/// At present, these are:
295///
296/// | type                              | length description                   |
297/// |-----------------------------------|--------------------------------------|
298/// | `&str`, `String`                  | length in bytes                      |
299/// | `Vec<T>`                          | number of elements in the vector     |
300/// | `HashMap<K, V>`, `BTreeMap<K, V>` | number of key/value pairs in the map |
301/// | [`TempFile`]                      | length of the file in bytes          |
302/// | `Option<T>` where `T: Len`        | length of `T` or 0 if `None`         |
303/// | [`form::Result<'_, T>`]           | length of `T` or 0 if `Err`          |
304///
305/// [`form::Result<'_, T>`]: crate::form::Result
306#[allow(clippy::len_without_is_empty)]
307pub trait Len<L> {
308    /// The length of the value.
309    fn len(&self) -> L;
310
311    /// Convert `len` into `u64`.
312    fn len_into_u64(len: L) -> u64;
313
314    /// The zero value for `L`.
315    fn zero_len() -> L;
316}
317
318macro_rules! impl_len {
319    (<$($gen:ident),*> $T:ty => $L:ty) => (
320        impl <$($gen),*> Len<$L> for $T {
321            fn len(&self) -> $L { self.len() }
322            fn len_into_u64(len: $L) -> u64 { len as u64 }
323            fn zero_len() -> $L { 0 }
324        }
325    )
326}
327
328impl_len!(<> str => usize);
329impl_len!(<> String => usize);
330impl_len!(<T> Vec<T> => usize);
331impl_len!(<> TempFile<'_> => u64);
332impl_len!(<K, V> std::collections::HashMap<K, V> => usize);
333impl_len!(<K, V> std::collections::BTreeMap<K, V> => usize);
334
335impl Len<ByteUnit> for TempFile<'_> {
336    fn len(&self) -> ByteUnit {
337        self.len().into()
338    }
339    fn len_into_u64(len: ByteUnit) -> u64 {
340        len.into()
341    }
342    fn zero_len() -> ByteUnit {
343        ByteUnit::from(0)
344    }
345}
346
347impl<L, T: Len<L> + ?Sized> Len<L> for &T {
348    fn len(&self) -> L {
349        <T as Len<L>>::len(self)
350    }
351    fn len_into_u64(len: L) -> u64 {
352        T::len_into_u64(len)
353    }
354    fn zero_len() -> L {
355        T::zero_len()
356    }
357}
358
359impl<L, T: Len<L>> Len<L> for Option<T> {
360    fn len(&self) -> L {
361        self.as_ref().map(|v| v.len()).unwrap_or_else(T::zero_len)
362    }
363    fn len_into_u64(len: L) -> u64 {
364        T::len_into_u64(len)
365    }
366    fn zero_len() -> L {
367        T::zero_len()
368    }
369}
370
371impl<L, T: Len<L>> Len<L> for Capped<T> {
372    fn len(&self) -> L {
373        self.value.len()
374    }
375    fn len_into_u64(len: L) -> u64 {
376        T::len_into_u64(len)
377    }
378    fn zero_len() -> L {
379        T::zero_len()
380    }
381}
382
383impl<L, T: Len<L>> Len<L> for Result<'_, T> {
384    fn len(&self) -> L {
385        self.as_ref().ok().len()
386    }
387    fn len_into_u64(len: L) -> u64 {
388        T::len_into_u64(len)
389    }
390    fn zero_len() -> L {
391        T::zero_len()
392    }
393}
394
395#[cfg(feature = "json")]
396impl<L, T: Len<L>> Len<L> for crate::serde::json::Json<T> {
397    fn len(&self) -> L {
398        self.0.len()
399    }
400    fn len_into_u64(len: L) -> u64 {
401        T::len_into_u64(len)
402    }
403    fn zero_len() -> L {
404        T::zero_len()
405    }
406}
407
408#[cfg(feature = "msgpack")]
409impl<L, T: Len<L>> Len<L> for crate::serde::msgpack::MsgPack<T> {
410    fn len(&self) -> L {
411        self.0.len()
412    }
413    fn len_into_u64(len: L) -> u64 {
414        T::len_into_u64(len)
415    }
416    fn zero_len() -> L {
417        T::zero_len()
418    }
419}
420
421/// Length validator: succeeds when the length of a value is within a `range`.
422///
423/// The value must implement [`Len`]. On error, returns an [`InvalidLength`]
424/// error. See [`Len`] for supported types and how their length is computed.
425///
426/// [`InvalidLength`]: crate::form::error::ErrorKind::InvalidLength
427///
428/// # Data Limits
429///
430/// All form types are constrained by a data limit. As such, the `len()`
431/// validator should be used only when a data limit is insufficiently specific.
432/// For example, prefer to use data [`Limits`](crate::data::Limits) to validate
433/// the length of files as not doing so will result in writing more data to disk
434/// than necessary.
435///
436/// # Example
437///
438/// ```rust
439/// # extern crate rocket_community as rocket;
440/// use rocket::http::ContentType;
441/// use rocket::form::{FromForm, FromFormField};
442/// use rocket::data::ToByteUnit;
443/// use rocket::fs::TempFile;
444///
445/// #[derive(FromForm)]
446/// struct Foo<'r> {
447///     #[field(validate = len(5..20))]
448///     name: &'r str,
449///     #[field(validate = len(..=200))]
450///     maybe_name: Option<&'r str>,
451///     #[field(validate = len(..=2.mebibytes()))]
452///     #[field(validate = ext(ContentType::Plain))]
453///     file: TempFile<'r>,
454/// }
455/// ```
456pub fn len<'v, V, L, R>(value: V, range: R) -> Result<'v, ()>
457where
458    V: Len<L>,
459    L: Copy + PartialOrd,
460    R: RangeBounds<L>,
461{
462    if !range.contains(&value.len()) {
463        let start = match range.start_bound() {
464            Bound::Included(v) => Some(V::len_into_u64(*v)),
465            Bound::Excluded(v) => Some(V::len_into_u64(*v).saturating_add(1)),
466            Bound::Unbounded => None,
467        };
468
469        let end = match range.end_bound() {
470            Bound::Included(v) => Some(V::len_into_u64(*v)),
471            Bound::Excluded(v) => Some(V::len_into_u64(*v).saturating_sub(1)),
472            Bound::Unbounded => None,
473        };
474
475        Err((start, end))?
476    }
477
478    Ok(())
479}
480
481/// Types for values that contain items.
482///
483/// At present, these are:
484///
485/// | type                    | contains                                           |
486/// |-------------------------|----------------------------------------------------|
487/// | `&str`, `String`        | `&str`, `char`, `&[char]` `F: FnMut(char) -> bool` |
488/// | `Vec<T>`                | `T`, `&T`                                          |
489/// | `Option<T>`             | `I` where `T: Contains<I>`                         |
490/// | [`form::Result<'_, T>`] | `I` where `T: Contains<I>`                         |
491///
492/// [`form::Result<'_, T>`]: crate::form::Result
493pub trait Contains<I> {
494    /// Returns `true` if `self` contains `item`.
495    fn contains(&self, item: I) -> bool;
496}
497
498macro_rules! impl_contains {
499    ([$($gen:tt)*] $T:ty [contains] $I:ty [via] $P:ty) => {
500        impl_contains!([$($gen)*] $T [contains] $I [via] $P [with] |v| v);
501    };
502
503    ([$($gen:tt)*] $T:ty [contains] $I:ty [via] $P:ty [with] $f:expr) => {
504        impl<$($gen)*> Contains<$I> for $T {
505            fn contains(&self, item: $I) -> bool {
506                <$P>::contains(self, $f(item))
507            }
508        }
509    };
510}
511
512fn coerce<T, const N: usize>(slice: &[T; N]) -> &[T] {
513    &slice[..]
514}
515
516impl_contains!([] str [contains] &str [via] str);
517impl_contains!([] str [contains] char [via] str);
518impl_contains!([] str [contains] &[char] [via] str);
519impl_contains!([const N: usize] str [contains] &[char; N] [via] str [with] coerce);
520impl_contains!([] String [contains] &str [via] str);
521impl_contains!([] String [contains] char [via] str);
522impl_contains!([] String [contains] &[char] [via] str);
523impl_contains!([const N: usize] String [contains] &[char; N] [via] str [with] coerce);
524impl_contains!([T: PartialEq] Vec<T> [contains] &T [via] [T]);
525
526impl<F: FnMut(char) -> bool> Contains<F> for str {
527    fn contains(&self, f: F) -> bool {
528        <str>::contains(self, f)
529    }
530}
531
532impl<F: FnMut(char) -> bool> Contains<F> for String {
533    fn contains(&self, f: F) -> bool {
534        <str>::contains(self, f)
535    }
536}
537
538impl<T: PartialEq> Contains<T> for Vec<T> {
539    fn contains(&self, item: T) -> bool {
540        <[T]>::contains(self, &item)
541    }
542}
543
544impl<I, T: Contains<I>> Contains<I> for Option<T> {
545    fn contains(&self, item: I) -> bool {
546        self.as_ref().map(|v| v.contains(item)).unwrap_or(false)
547    }
548}
549
550impl<I, T: Contains<I>> Contains<I> for Result<'_, T> {
551    fn contains(&self, item: I) -> bool {
552        self.as_ref().map(|v| v.contains(item)).unwrap_or(false)
553    }
554}
555
556impl<I, T: Contains<I> + ?Sized> Contains<I> for &T {
557    fn contains(&self, item: I) -> bool {
558        <T as Contains<I>>::contains(self, item)
559    }
560}
561
562/// Contains validator: succeeds when a value contains `item`.
563///
564/// This is the dual of [`omits()`]. The value must implement
565/// [`Contains<I>`](Contains) where `I` is the type of the `item`. See
566/// [`Contains`] for supported types and items.
567///
568/// On error, returns a validation error with the following message:
569///
570/// ```text
571/// value is equal to an invalid value
572/// ```
573///
574/// If the collection is empty, this validator fails.
575///
576/// # Example
577///
578/// ```rust
579/// # extern crate rocket_community as rocket;
580/// use rocket::form::{FromForm, FromFormField};
581///
582/// #[derive(PartialEq, FromFormField)]
583/// enum Pet { Cat, Dog }
584///
585/// #[derive(FromForm)]
586/// struct Foo<'r> {
587///     best_pet: Pet,
588///     #[field(validate = contains(Pet::Cat))]
589///     #[field(validate = contains(&self.best_pet))]
590///     pets: Vec<Pet>,
591///     #[field(validate = contains('/'))]
592///     #[field(validate = contains(&['/', ':']))]
593///     license: &'r str,
594///     #[field(validate = contains("@rust-lang.org"))]
595///     #[field(validate = contains(|c: char| c.to_ascii_lowercase() == 's'))]
596///     rust_lang_email: &'r str,
597/// }
598/// ```
599pub fn contains<'v, V, I>(value: V, item: I) -> Result<'v, ()>
600where
601    V: Contains<I>,
602{
603    if !value.contains(item) {
604        Err(Error::validation("value does not contain expected item"))?
605    }
606
607    Ok(())
608}
609
610/// Debug contains validator: like [`contains()`] but mentions `item` in the
611/// error message.
612///
613/// This is the dual of [`dbg_omits()`]. The is identical to [`contains()`]
614/// except that `item` must be `Debug + Copy` and the error message is as
615/// follows, where `$item` is the [`Debug`] representation of `item`:
616///
617/// ```text
618/// values must contains $item
619/// ```
620///
621/// If the collection is empty, this validator fails.
622///
623/// # Example
624///
625/// ```rust
626/// # extern crate rocket_community as rocket;
627/// use rocket::form::{FromForm, FromFormField};
628///
629/// #[derive(PartialEq, Debug, Clone, Copy, FromFormField)]
630/// enum Pet { Cat, Dog }
631///
632/// #[derive(FromForm)]
633/// struct Foo {
634///     best_pet: Pet,
635///     #[field(validate = dbg_contains(Pet::Dog))]
636///     #[field(validate = dbg_contains(&self.best_pet))]
637///     pets: Vec<Pet>,
638/// }
639/// ```
640pub fn dbg_contains<'v, V, I>(value: V, item: I) -> Result<'v, ()>
641where
642    V: Contains<I>,
643    I: Debug + Copy,
644{
645    if !value.contains(item) {
646        Err(Error::validation(format!("value must contain {:?}", item)))?
647    }
648
649    Ok(())
650}
651
652/// Omits validator: succeeds when a value _does not_ contains `item`.
653/// error message.
654///
655/// This is the dual of [`contains()`]. The value must implement
656/// [`Contains<I>`](Contains) where `I` is the type of the `item`. See
657/// [`Contains`] for supported types and items.
658///
659/// On error, returns a validation error with the following message:
660///
661/// ```text
662/// value contains a disallowed item
663/// ```
664///
665/// If the collection is empty, this validator succeeds.
666///
667/// # Example
668///
669/// ```rust
670/// # extern crate rocket_community as rocket;
671/// use rocket::form::{FromForm, FromFormField};
672///
673/// #[derive(PartialEq, FromFormField)]
674/// enum Pet { Cat, Dog }
675///
676/// #[derive(FromForm)]
677/// struct Foo<'r> {
678///     #[field(validate = omits(Pet::Cat))]
679///     pets: Vec<Pet>,
680///     #[field(validate = omits('@'))]
681///     not_email: &'r str,
682///     #[field(validate = omits("@gmail.com"))]
683///     non_gmail_email: &'r str,
684/// }
685/// ```
686pub fn omits<'v, V, I>(value: V, item: I) -> Result<'v, ()>
687where
688    V: Contains<I>,
689{
690    if value.contains(item) {
691        Err(Error::validation("value contains a disallowed item"))?
692    }
693
694    Ok(())
695}
696
697/// Debug omits validator: like [`omits()`] but mentions `item` in the error
698/// message.
699///
700/// This is the dual of [`dbg_contains()`]. The is identical to [`omits()`]
701/// except that `item` must be `Debug + Copy` and the error message is as
702/// follows, where `$item` is the [`Debug`] representation of `item`:
703///
704/// ```text
705/// value cannot contain $item
706/// ```
707///
708/// If the collection is empty, this validator succeeds.
709///
710/// # Example
711///
712/// ```rust
713/// # extern crate rocket_community as rocket;
714/// use rocket::form::{FromForm, FromFormField};
715///
716/// #[derive(PartialEq, Debug, Clone, Copy, FromFormField)]
717/// enum Pet { Cat, Dog }
718///
719/// #[derive(FromForm)]
720/// struct Foo<'r> {
721///     #[field(validate = dbg_omits(Pet::Cat))]
722///     pets: Vec<Pet>,
723///     #[field(validate = dbg_omits('@'))]
724///     not_email: &'r str,
725///     #[field(validate = dbg_omits("@gmail.com"))]
726///     non_gmail_email: &'r str,
727/// }
728/// ```
729pub fn dbg_omits<'v, V, I>(value: V, item: I) -> Result<'v, ()>
730where
731    V: Contains<I>,
732    I: Copy + Debug,
733{
734    if value.contains(item) {
735        Err(Error::validation(format!(
736            "value cannot contain {:?}",
737            item
738        )))?
739    }
740
741    Ok(())
742}
743
744/// Integer range validator: succeeds when an integer value is within a range.
745///
746/// The value must be an integer type that implement `TryInto<isize> + Copy`. On
747/// error, returns an [`OutOfRange`] error.
748///
749/// [`OutOfRange`]: crate::form::error::ErrorKind::OutOfRange
750///
751/// # Example
752///
753/// ```rust
754/// # extern crate rocket_community as rocket;
755/// use rocket::form::FromForm;
756///
757/// #[derive(FromForm)]
758/// struct Foo {
759///     #[field(validate = range(0..))]
760///     non_negative: isize,
761///     #[field(validate = range(18..=130))]
762///     maybe_adult: u8,
763/// }
764/// ```
765pub fn range<'v, V, R>(value: &V, range: R) -> Result<'v, ()>
766where
767    V: TryInto<isize> + Copy,
768    R: RangeBounds<isize>,
769{
770    if let Ok(v) = (*value).try_into() {
771        if range.contains(&v) {
772            return Ok(());
773        }
774    }
775
776    let start = match range.start_bound() {
777        Bound::Included(v) => Some(*v),
778        Bound::Excluded(v) => Some(v.saturating_add(1)),
779        Bound::Unbounded => None,
780    };
781
782    let end = match range.end_bound() {
783        Bound::Included(v) => Some(*v),
784        Bound::Excluded(v) => Some(v.saturating_sub(1)),
785        Bound::Unbounded => None,
786    };
787
788    Err((start, end))?
789}
790
791/// Contains one of validator: succeeds when a value contains at least one item
792/// in an `items` iterator.
793///
794/// The value must implement [`Contains<I>`](Contains) where `I` is the type of
795/// the `item`. The iterator must be [`Clone`]. See [`Contains`] for supported
796/// types and items. The item must be [`Debug`].
797///
798/// On error, returns a [`InvalidChoice`] error with the debug representation
799/// of each item in `items`.
800///
801/// [`InvalidChoice`]: crate::form::error::ErrorKind::InvalidChoice
802///
803/// # Example
804///
805/// ```rust
806/// # extern crate rocket_community as rocket;
807/// use rocket::form::FromForm;
808///
809/// #[derive(FromForm)]
810/// struct Foo<'r> {
811///     #[field(validate = one_of(&[3, 5, 7]))]
812///     single_digit_primes: Vec<u8>,
813///     #[field(validate = one_of(" \t\n".chars()))]
814///     has_space_char: &'r str,
815///     #[field(validate = one_of(" \t\n".chars()).and_then(msg!("no spaces")))]
816///     no_space: &'r str,
817/// }
818/// ```
819pub fn one_of<'v, V, I, R>(value: V, items: R) -> Result<'v, ()>
820where
821    V: Contains<I>,
822    I: Debug,
823    R: IntoIterator<Item = I>,
824    <R as IntoIterator>::IntoIter: Clone,
825{
826    let items = items.into_iter();
827    for item in items.clone() {
828        if value.contains(item) {
829            return Ok(());
830        }
831    }
832
833    let choices: Vec<Cow<'_, str>> = items.map(|item| format!("{:?}", item).into()).collect();
834
835    Err(choices)?
836}
837
838/// File type validator: succeeds when a [`TempFile`] has the Content-Type
839/// `content_type`.
840///
841/// On error, returns a validation error with one of the following messages:
842///
843/// ```text
844/// // the file has an incorrect extension
845/// file type was .$file_ext but must be $type
846///
847/// // the file does not have an extension
848/// file type must be $type
849/// ```
850///
851/// # Example
852///
853/// ```rust
854/// # extern crate rocket_community as rocket;
855/// use rocket::form::FromForm;
856/// use rocket::data::ToByteUnit;
857/// use rocket::http::ContentType;
858/// use rocket::fs::TempFile;
859///
860/// #[derive(FromForm)]
861/// struct Foo<'r> {
862///     #[field(validate = ext(ContentType::PDF))]
863///     #[field(validate = len(..1.mebibytes()))]
864///     document: TempFile<'r>,
865/// }
866/// ```
867pub fn ext<'v>(file: &TempFile<'_>, r#type: ContentType) -> Result<'v, ()> {
868    if let Some(file_ct) = file.content_type() {
869        if file_ct == &r#type {
870            return Ok(());
871        }
872    }
873
874    let msg = match (
875        file.content_type().and_then(|c| c.extension()),
876        r#type.extension(),
877    ) {
878        (Some(a), Some(b)) => format!("invalid file type: .{}, must be .{}", a, b),
879        (Some(a), None) => format!("invalid file type: .{}, must be {}", a, r#type),
880        (None, Some(b)) => format!("file type must be .{}", b),
881        (None, None) => format!("file type must be {}", r#type),
882    };
883
884    Err(Error::validation(msg))?
885}
886
887/// With validator: succeeds when an arbitrary function or closure does.
888///
889/// This is the most generic validator and, for readability, should only be used
890/// when a more case-specific option does not exist. It succeeds exactly when
891/// `f` returns `true` and fails otherwise.
892///
893/// On error, returns a validation error with the message `msg`.
894///
895/// # Example
896///
897/// ```rust
898/// # extern crate rocket_community as rocket;
899/// use rocket::form::{FromForm, FromFormField};
900///
901/// #[derive(PartialEq, FromFormField)]
902/// enum Pet { Cat, Dog }
903///
904/// fn is_dog(p: &Pet) -> bool {
905///     matches!(p, Pet::Dog)
906/// }
907///
908/// #[derive(FromForm)]
909/// struct Foo {
910///     // These are equivalent. Prefer the former.
911///     #[field(validate = contains(Pet::Dog))]
912///     #[field(validate = with(|pets| pets.iter().any(|p| *p == Pet::Dog), "missing dog"))]
913///     pets: Vec<Pet>,
914///     // These are equivalent. Prefer the former.
915///     #[field(validate = eq(Pet::Dog))]
916///     #[field(validate = with(|p| matches!(p, Pet::Dog), "expected a dog"))]
917///     #[field(validate = with(|p| is_dog(p), "expected a dog"))]
918///   # #[field(validate = with(|p| is_dog(&self.dog), "expected a dog"))]
919///     #[field(validate = with(is_dog, "expected a dog"))]
920///     dog: Pet,
921///     // These are equivalent. Prefer the former.
922///     #[field(validate = contains(&self.dog))]
923///   # #[field(validate = with(|p| is_dog(&self.dog), "expected a dog"))]
924///     #[field(validate = with(|pets| pets.iter().any(|p| p == &self.dog), "missing dog"))]
925///     one_dog_please: Vec<Pet>,
926/// }
927/// ```
928pub fn with<'v, V, F, M>(value: V, f: F, msg: M) -> Result<'v, ()>
929where
930    F: FnOnce(V) -> bool,
931    M: Into<Cow<'static, str>>,
932{
933    if !f(value) {
934        Err(Error::validation(msg.into()))?
935    }
936
937    Ok(())
938}
939
940/// _Try_ With validator: succeeds when an arbitrary function or closure does.
941///
942/// Along with [`with`], this is the most generic validator. It succeeds
943/// exactly when `f` returns `Ok` and fails otherwise.
944///
945/// On error, returns a validation error with the message in the `Err`
946/// variant converted into a string.
947///
948/// # Example
949///
950/// Assuming `Token` has a `from_str` method:
951///
952/// ```rust
953/// # extern crate rocket_community as rocket;
954/// # use rocket::form::FromForm;
955/// # impl FromStr for Token<'_> {
956/// #     type Err = &'static str;
957/// #     fn from_str(s: &str) -> Result<Self, Self::Err> { todo!() }
958/// # }
959/// use std::str::FromStr;
960///
961/// #[derive(FromForm)]
962/// #[field(validate = try_with(|s| Token::from_str(s)))]
963/// struct Token<'r>(&'r str);
964///
965/// #[derive(FromForm)]
966/// #[field(validate = try_with(|s| s.parse::<Token>()))]
967/// struct Token2<'r>(&'r str);
968/// ```
969pub fn try_with<'v, V, F, T, E>(value: V, f: F) -> Result<'v, ()>
970where
971    F: FnOnce(V) -> std::result::Result<T, E>,
972    E: std::fmt::Display,
973{
974    match f(value) {
975        Ok(_) => Ok(()),
976        Err(e) => Err(Error::validation(e.to_string()).into()),
977    }
978}