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