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}