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}