inquire/validator.rs
1//! Traits and structs used by prompts to validate user input before
2//! returning the values to their callers.
3//!
4//! Validators receive the user input to a given prompt and decide whether
5//! they are valid, returning `Ok(Validation::Valid)` in the process, or
6//! invalid, returning `Ok(Validation::Invalid(ErrorMessage))`, where the
7//! `ErrorMessage` content is an error message to be displayed to the end user.
8//!
9//! Validators can also return errors, which propagate to the caller prompt
10//! and cause the prompt to return the error.
11//!
12//! This module also provides several macros as shorthands to the struct
13//! constructor functions, exported with the `macros` feature.
14
15use dyn_clone::DynClone;
16
17use crate::{error::CustomUserError, list_option::ListOption};
18
19/// Error message that is displayed to the users when their input is considered not
20/// valid by registered validators.
21#[derive(Clone, Default, Debug, PartialEq, Eq)]
22pub enum ErrorMessage {
23 /// No custom message is defined, a standard one defined in the set
24 /// [`RenderConfig`](crate::ui::RenderConfig) is used instead.
25 #[default]
26 Default,
27
28 /// Custom error message, used instead of the standard one.
29 Custom(String),
30}
31
32impl<T> From<T> for ErrorMessage
33where
34 T: ToString,
35{
36 fn from(msg: T) -> Self {
37 Self::Custom(msg.to_string())
38 }
39}
40
41/// The result type of validation operations when the execution of the validator
42/// function succeeds.
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub enum Validation {
45 /// Variant that indicates that the input value is valid according to the validator.
46 Valid,
47
48 /// Variant that indicates that the input value is invalid according to the validator.
49 ///
50 /// The member represents a custom error message that will be displayed to the user when present.
51 /// When empty a standard error message, configured via the RenderConfig struct, will be shown
52 /// instead.
53 Invalid(ErrorMessage),
54}
55
56/// Validator that receives a string slice as the input, such as [`Text`](crate::Text) and
57/// [`Password`](crate::Password).
58///
59/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
60///
61/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
62/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
63/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
64///
65/// # Examples
66///
67/// ```
68/// use inquire::validator::{StringValidator, Validation};
69///
70/// let validator = |input: &str| match input.chars().find(|c| c.is_numeric()) {
71/// Some(_) => Ok(Validation::Valid),
72/// None => Ok(Validation::Invalid(
73/// "Your password should contain at least 1 digit".into(),
74/// )),
75/// };
76///
77/// assert_eq!(Validation::Valid, validator.validate("hunter2")?);
78/// assert_eq!(
79/// Validation::Invalid("Your password should contain at least 1 digit".into()),
80/// validator.validate("password")?
81/// );
82/// # Ok::<(), inquire::error::CustomUserError>(())
83/// ```
84pub trait StringValidator: DynClone {
85 /// Confirm the given input string is a valid value.
86 fn validate(&self, input: &str) -> Result<Validation, CustomUserError>;
87}
88
89impl Clone for Box<dyn StringValidator> {
90 fn clone(&self) -> Self {
91 dyn_clone::clone_box(&**self)
92 }
93}
94
95impl<F> StringValidator for F
96where
97 F: Fn(&str) -> Result<Validation, CustomUserError> + Clone,
98{
99 fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
100 (self)(input)
101 }
102}
103
104/// Validator used in [`DateSelect`](crate::DateSelect) prompts.
105///
106/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
107///
108/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
109/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
110/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
111///
112/// # Examples
113///
114/// ```
115/// use chrono::{Datelike, NaiveDate, Weekday};
116/// use inquire::validator::{DateValidator, Validation};
117///
118/// let validator = |input: NaiveDate| {
119/// if input.weekday() == Weekday::Sat || input.weekday() == Weekday::Sun {
120/// Ok(Validation::Invalid("Weekends are not allowed".into()))
121/// } else {
122/// Ok(Validation::Valid)
123/// }
124/// };
125///
126/// assert_eq!(Validation::Valid, validator.validate(NaiveDate::from_ymd(2021, 7, 26))?);
127/// assert_eq!(
128/// Validation::Invalid("Weekends are not allowed".into()),
129/// validator.validate(NaiveDate::from_ymd(2021, 7, 25))?
130/// );
131/// # Ok::<(), inquire::error::CustomUserError>(())
132/// ```
133#[cfg(feature = "date")]
134pub trait DateValidator: DynClone {
135 /// Confirm the given input date is a valid value.
136 fn validate(&self, input: chrono::NaiveDate) -> Result<Validation, CustomUserError>;
137}
138
139#[cfg(feature = "date")]
140impl Clone for Box<dyn DateValidator> {
141 fn clone(&self) -> Self {
142 dyn_clone::clone_box(&**self)
143 }
144}
145
146#[cfg(feature = "date")]
147impl<F> DateValidator for F
148where
149 F: Fn(chrono::NaiveDate) -> Result<Validation, CustomUserError> + Clone,
150{
151 fn validate(&self, input: chrono::NaiveDate) -> Result<Validation, CustomUserError> {
152 (self)(input)
153 }
154}
155
156/// Validator used in [`MultiSelect`](crate::MultiSelect) prompts.
157///
158/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
159///
160/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
161/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
162/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
163///
164/// # Examples
165///
166/// ```
167/// use inquire::list_option::ListOption;
168/// use inquire::validator::{MultiOptionValidator, Validation};
169///
170/// let validator = |input: &[ListOption<&&str>]| {
171/// if input.len() <= 2 {
172/// Ok(Validation::Valid)
173/// } else {
174/// Ok(Validation::Invalid("You should select at most two options".into()))
175/// }
176/// };
177///
178/// let mut ans = vec![ListOption::new(0, &"a"), ListOption::new(1, &"b")];
179///
180/// assert_eq!(Validation::Valid, validator.validate(&ans[..])?);
181///
182/// ans.push(ListOption::new(3, &"d"));
183/// assert_eq!(
184/// Validation::Invalid("You should select at most two options".into()),
185/// validator.validate(&ans[..])?
186/// );
187/// # Ok::<(), inquire::error::CustomUserError>(())
188/// ```
189pub trait MultiOptionValidator<T: ?Sized>: DynClone {
190 /// Confirm the given input list is a valid value.
191 fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError>;
192}
193
194impl<T> Clone for Box<dyn MultiOptionValidator<T>> {
195 fn clone(&self) -> Self {
196 dyn_clone::clone_box(&**self)
197 }
198}
199
200impl<F, T> MultiOptionValidator<T> for F
201where
202 F: Fn(&[ListOption<&T>]) -> Result<Validation, CustomUserError> + Clone,
203 T: ?Sized,
204{
205 fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
206 (self)(input)
207 }
208}
209
210/// Validator used in [`CustomType`](crate::CustomType) prompts.
211///
212/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
213///
214/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
215/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
216/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
217///
218/// # Examples
219///
220/// ```
221/// use inquire::list_option::ListOption;
222/// use inquire::validator::{MultiOptionValidator, Validation};
223///
224/// let validator = |input: &[ListOption<&&str>]| {
225/// if input.len() <= 2 {
226/// Ok(Validation::Valid)
227/// } else {
228/// Ok(Validation::Invalid("You should select at most two options".into()))
229/// }
230/// };
231///
232/// let mut ans = vec![ListOption::new(0, &"a"), ListOption::new(1, &"b")];
233///
234/// assert_eq!(Validation::Valid, validator.validate(&ans[..])?);
235///
236/// ans.push(ListOption::new(3, &"d"));
237/// assert_eq!(
238/// Validation::Invalid("You should select at most two options".into()),
239/// validator.validate(&ans[..])?
240/// );
241/// # Ok::<(), inquire::error::CustomUserError>(())
242/// ```
243pub trait CustomTypeValidator<T: ?Sized>: DynClone {
244 /// Confirm the given input list is a valid value.
245 fn validate(&self, input: &T) -> Result<Validation, CustomUserError>;
246}
247
248impl<T> Clone for Box<dyn CustomTypeValidator<T>> {
249 fn clone(&self) -> Self {
250 dyn_clone::clone_box(&**self)
251 }
252}
253
254impl<F, T> CustomTypeValidator<T> for F
255where
256 F: Fn(&T) -> Result<Validation, CustomUserError> + Clone,
257 T: ?Sized,
258{
259 fn validate(&self, input: &T) -> Result<Validation, CustomUserError> {
260 (self)(input)
261 }
262}
263
264/// Custom trait to call correct method to retrieve input length.
265///
266/// The method can vary depending on the type of input.
267///
268/// String inputs should count the number of graphemes, via
269/// `.graphemes(true).count()`, instead of the number of bytes
270/// via `.len()`. While simple slices should keep using `.len()`
271pub trait InquireLength {
272 /// String inputs should count the number of graphemes, via
273 /// `.graphemes(true).count()`, instead of the number of bytes
274 /// via `.len()`. While simple slices keep using `.len()`
275 fn inquire_length(&self) -> usize;
276}
277
278impl InquireLength for &str {
279 fn inquire_length(&self) -> usize {
280 use unicode_segmentation::UnicodeSegmentation;
281
282 self.graphemes(true).count()
283 }
284}
285
286impl<T> InquireLength for &[T] {
287 fn inquire_length(&self) -> usize {
288 self.len()
289 }
290}
291
292/// Built-in validator that checks whether the answer is not empty.
293///
294/// # Examples
295///
296/// ```
297/// use inquire::validator::{StringValidator, Validation, ValueRequiredValidator};
298///
299/// let validator = ValueRequiredValidator::default();
300/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
301/// assert_eq!(Validation::Invalid("A response is required.".into()), validator.validate("")?);
302///
303/// let validator = ValueRequiredValidator::new("No empty!");
304/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
305/// assert_eq!(Validation::Invalid("No empty!".into()), validator.validate("")?);
306/// # Ok::<(), inquire::error::CustomUserError>(())
307/// ```
308#[derive(Clone)]
309pub struct ValueRequiredValidator {
310 message: String,
311}
312
313impl ValueRequiredValidator {
314 /// Create a new instance of this validator with given error message.
315 pub fn new(message: impl Into<String>) -> Self {
316 Self {
317 message: message.into(),
318 }
319 }
320}
321
322impl Default for ValueRequiredValidator {
323 /// Create a new instance of this validator with the default error message
324 /// `A response is required`.
325 fn default() -> Self {
326 Self {
327 message: "A response is required.".to_owned(),
328 }
329 }
330}
331
332impl StringValidator for ValueRequiredValidator {
333 fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
334 Ok(if input.is_empty() {
335 Validation::Invalid(self.message.as_str().into())
336 } else {
337 Validation::Valid
338 })
339 }
340}
341
342/// Shorthand for the built-in [`ValueRequiredValidator`] that checks whether the answer is not
343/// empty.
344///
345/// # Arguments
346///
347/// * `$message` - optional - Error message returned by the validator.
348/// Defaults to "A response is required."
349///
350/// # Examples
351///
352/// ```
353/// use inquire::{required, validator::{StringValidator, Validation}};
354///
355/// let validator = required!();
356/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
357/// assert_eq!(Validation::Invalid("A response is required.".into()), validator.validate("")?);
358///
359/// let validator = required!("No empty!");
360/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
361/// assert_eq!(Validation::Invalid("No empty!".into()), validator.validate("")?);
362/// # Ok::<(), inquire::error::CustomUserError>(())
363/// ```
364#[macro_export]
365#[cfg(feature = "macros")]
366macro_rules! required {
367 () => {
368 $crate::validator::ValueRequiredValidator::default()
369 };
370
371 ($message:expr) => {
372 $crate::validator::ValueRequiredValidator::new($message)
373 };
374}
375
376/// Built-in validator that checks whether the answer length is smaller than
377/// or equal to the specified threshold.
378///
379/// The validator uses a custom-built length function that
380/// has a special implementation for strings which counts the number of
381/// graphemes. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
382///
383/// # Examples
384///
385/// ```
386/// use inquire::validator::{MaxLengthValidator, StringValidator, Validation};
387///
388/// let validator = MaxLengthValidator::new(5);
389/// assert_eq!(Validation::Valid, validator.validate("Good")?);
390/// assert_eq!(
391/// Validation::Invalid("The length of the response should be at most 5".into()),
392/// validator.validate("Terrible")?,
393/// );
394///
395/// let validator = MaxLengthValidator::new(5).with_message("Not too large!");
396/// assert_eq!(Validation::Valid, validator.validate("Good")?);
397/// assert_eq!(Validation::Invalid("Not too large!".into()), validator.validate("Terrible")?);
398/// # Ok::<(), inquire::error::CustomUserError>(())
399/// ```
400#[derive(Clone)]
401pub struct MaxLengthValidator {
402 limit: usize,
403 message: String,
404}
405
406impl MaxLengthValidator {
407 /// Create a new instance of this validator, requiring at most the given length, otherwise
408 /// returning an error with default message.
409 pub fn new(limit: usize) -> Self {
410 Self {
411 limit,
412 message: format!("The length of the response should be at most {limit}"),
413 }
414 }
415
416 /// Define a custom error message returned by the validator.
417 /// Defaults to `The length of the response should be at most $length`.
418 pub fn with_message(mut self, message: impl Into<String>) -> Self {
419 self.message = message.into();
420 self
421 }
422
423 fn validate_inquire_length<T: InquireLength>(
424 &self,
425 input: T,
426 ) -> Result<Validation, CustomUserError> {
427 Ok(if input.inquire_length() <= self.limit {
428 Validation::Valid
429 } else {
430 Validation::Invalid(self.message.as_str().into())
431 })
432 }
433}
434
435impl StringValidator for MaxLengthValidator {
436 fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
437 self.validate_inquire_length(input)
438 }
439}
440
441impl<T: ?Sized> MultiOptionValidator<T> for MaxLengthValidator {
442 fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
443 self.validate_inquire_length(input)
444 }
445}
446
447/// Shorthand for the built-in [`MaxLengthValidator`] that checks whether the answer length is
448/// smaller than or equal to the specified threshold.
449///
450/// # Arguments
451///
452/// * `$length` - Maximum length of the input.
453/// * `$message` - optional - Error message returned by the validator.
454/// Defaults to "The length of the response should be at most $length"
455///
456/// # Examples
457///
458/// ```
459/// use inquire::{max_length, validator::{StringValidator, Validation}};
460///
461/// let validator = max_length!(5);
462/// assert_eq!(Validation::Valid, validator.validate("Good")?);
463/// assert_eq!(Validation::Invalid("The length of the response should be at most 5".into()), validator.validate("Terrible")?);
464///
465/// let validator = max_length!(5, "Not too large!");
466/// assert_eq!(Validation::Valid, validator.validate("Good")?);
467/// assert_eq!(Validation::Invalid("Not too large!".into()), validator.validate("Terrible")?);
468/// # Ok::<(), inquire::error::CustomUserError>(())
469/// ```
470#[macro_export]
471#[cfg(feature = "macros")]
472macro_rules! max_length {
473 ($length:expr) => {
474 $crate::validator::MaxLengthValidator::new($length)
475 };
476
477 ($length:expr, $message:expr) => {
478 $crate::max_length!($length).with_message($message)
479 };
480}
481
482/// Built-in validator that checks whether the answer length is larger than
483/// or equal to the specified threshold.
484///
485/// The validator uses a custom-built length function that
486/// has a special implementation for strings which counts the number of
487/// graphemes. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
488///
489/// # Examples
490///
491/// ```
492/// use inquire::validator::{MinLengthValidator, StringValidator, Validation};
493///
494/// let validator = MinLengthValidator::new(3);
495/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
496/// assert_eq!(
497/// Validation::Invalid("The length of the response should be at least 3".into()),
498/// validator.validate("No")?,
499/// );
500///
501/// let validator = MinLengthValidator::new(3).with_message("You have to give me more than that!");
502/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
503/// assert_eq!(
504/// Validation::Invalid("You have to give me more than that!".into()),
505/// validator.validate("No")?,
506/// );
507/// # Ok::<(), inquire::error::CustomUserError>(())
508/// ```
509#[derive(Clone)]
510pub struct MinLengthValidator {
511 limit: usize,
512 message: String,
513}
514
515impl MinLengthValidator {
516 /// Create a new instance of this validator, requiring at least the given length, otherwise
517 /// returning an error with default message.
518 pub fn new(limit: usize) -> Self {
519 Self {
520 limit,
521 message: format!("The length of the response should be at least {limit}"),
522 }
523 }
524
525 /// Define a custom error message returned by the validator.
526 /// Defaults to `The length of the response should be at least $length`.
527 pub fn with_message(mut self, message: impl Into<String>) -> Self {
528 self.message = message.into();
529 self
530 }
531
532 fn validate_inquire_length<T: InquireLength>(
533 &self,
534 input: T,
535 ) -> Result<Validation, CustomUserError> {
536 Ok(if input.inquire_length() >= self.limit {
537 Validation::Valid
538 } else {
539 Validation::Invalid(self.message.as_str().into())
540 })
541 }
542}
543
544impl StringValidator for MinLengthValidator {
545 fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
546 self.validate_inquire_length(input)
547 }
548}
549
550impl<T: ?Sized> MultiOptionValidator<T> for MinLengthValidator {
551 fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
552 self.validate_inquire_length(input)
553 }
554}
555
556/// Shorthand for the built-in [`MinLengthValidator`] that checks whether the answer length is
557/// larger than or equal to the specified threshold.
558///
559/// # Arguments
560///
561/// * `$length` - Minimum length of the input.
562/// * `$message` - optional - Error message returned by the validator.
563/// Defaults to "The length of the response should be at least $length"
564///
565/// # Examples
566///
567/// ```
568/// use inquire::{min_length, validator::{StringValidator, Validation}};
569///
570/// let validator = min_length!(3);
571/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
572/// assert_eq!(Validation::Invalid("The length of the response should be at least 3".into()), validator.validate("No")?);
573///
574/// let validator = min_length!(3, "You have to give me more than that!");
575/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
576/// assert_eq!(Validation::Invalid("You have to give me more than that!".into()), validator.validate("No")?);
577/// # Ok::<(), inquire::error::CustomUserError>(())
578/// ```
579#[macro_export]
580#[cfg(feature = "macros")]
581macro_rules! min_length {
582 ($length:expr) => {
583 $crate::validator::MinLengthValidator::new($length)
584 };
585
586 ($length:expr, $message:expr) => {
587 $crate::min_length!($length).with_message($message)
588 };
589}
590
591/// Built-in validator that checks whether the answer length is equal to
592/// the specified value.
593///
594/// The validator uses a custom-built length function that
595/// has a special implementation for strings which counts the number of
596/// graphemes. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
597///
598/// # Examples
599///
600/// ```
601/// use inquire::validator::{ExactLengthValidator, StringValidator, Validation};
602///
603/// let validator = ExactLengthValidator::new(3);
604/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
605/// assert_eq!(
606/// Validation::Invalid("The length of the response should be 3".into()),
607/// validator.validate("No")?,
608/// );
609///
610/// let validator = ExactLengthValidator::new(3).with_message("Three characters please.");
611/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
612/// assert_eq!(Validation::Invalid("Three characters please.".into()), validator.validate("No")?);
613/// # Ok::<(), inquire::error::CustomUserError>(())
614/// ```
615#[derive(Clone)]
616pub struct ExactLengthValidator {
617 length: usize,
618 message: String,
619}
620
621impl ExactLengthValidator {
622 /// Create a new instance of this validator, requiring exactly the given length, otherwise
623 /// returning an error with default message.
624 pub fn new(length: usize) -> Self {
625 Self {
626 length,
627 message: format!("The length of the response should be {length}"),
628 }
629 }
630
631 /// Define a custom error message returned by the validator.
632 /// Defaults to `The length of the response should be $length`.
633 pub fn with_message(mut self, message: impl Into<String>) -> Self {
634 self.message = message.into();
635 self
636 }
637
638 fn validate_inquire_length<T: InquireLength>(
639 &self,
640 input: T,
641 ) -> Result<Validation, CustomUserError> {
642 Ok(if input.inquire_length() == self.length {
643 Validation::Valid
644 } else {
645 Validation::Invalid(self.message.as_str().into())
646 })
647 }
648}
649
650impl StringValidator for ExactLengthValidator {
651 fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
652 self.validate_inquire_length(input)
653 }
654}
655
656impl<T: ?Sized> MultiOptionValidator<T> for ExactLengthValidator {
657 fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
658 self.validate_inquire_length(input)
659 }
660}
661
662/// Shorthand for the built-in [`ExactLengthValidator`] that checks whether the answer length is
663/// equal to the specified value.
664///
665/// # Arguments
666///
667/// * `$length` - Expected length of the input.
668/// * `$message` - optional - Error message returned by the validator.
669/// Defaults to "The length of the response should be $length"
670///
671/// # Examples
672///
673/// ```
674/// use inquire::{length, validator::{StringValidator, Validation}};
675///
676/// let validator = length!(3);
677/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
678/// assert_eq!(Validation::Invalid("The length of the response should be 3".into()), validator.validate("No")?);
679///
680/// let validator = length!(3, "Three characters please.");
681/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
682/// assert_eq!(Validation::Invalid("Three characters please.".into()), validator.validate("No")?);
683/// # Ok::<(), inquire::error::CustomUserError>(())
684/// ```
685#[macro_export]
686#[cfg(feature = "macros")]
687macro_rules! length {
688 ($length:expr) => {
689 $crate::validator::ExactLengthValidator::new($length)
690 };
691
692 ($length:expr, $message:expr) => {
693 $crate::length!($length).with_message($message)
694 };
695}
696
697#[cfg(test)]
698mod validators_test {
699 use crate::{
700 error::CustomUserError,
701 list_option::ListOption,
702 validator::{
703 ExactLengthValidator, MaxLengthValidator, MinLengthValidator, MultiOptionValidator,
704 StringValidator, Validation,
705 },
706 };
707
708 fn build_option_vec(len: usize) -> Vec<ListOption<&'static str>> {
709 let mut options = Vec::new();
710
711 for i in 0..len {
712 options.push(ListOption::new(i, ""));
713 }
714
715 options
716 }
717
718 #[test]
719 fn string_length_counts_graphemes() -> Result<(), CustomUserError> {
720 let validator = ExactLengthValidator::new(5);
721 let validator: &dyn StringValidator = &validator;
722
723 assert!(matches!(validator.validate("five!")?, Validation::Valid));
724 assert!(matches!(
725 validator.validate("♥️♥️♥️♥️♥️")?,
726 Validation::Valid
727 ));
728 assert!(matches!(
729 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
730 Validation::Valid
731 ));
732
733 assert!(matches!(
734 validator.validate("five!!!")?,
735 Validation::Invalid(_)
736 ));
737 assert!(matches!(
738 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
739 Validation::Invalid(_)
740 ));
741 assert!(matches!(
742 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
743 Validation::Invalid(_)
744 ));
745
746 Ok(())
747 }
748
749 #[test]
750 fn slice_length() -> Result<(), CustomUserError> {
751 let validator = ExactLengthValidator::new(5);
752 let validator: &dyn MultiOptionValidator<str> = &validator;
753
754 assert!(matches!(
755 validator.validate(&build_option_vec(5))?,
756 Validation::Valid
757 ));
758 assert!(matches!(
759 validator.validate(&build_option_vec(4))?,
760 Validation::Invalid(_)
761 ));
762 assert!(matches!(
763 validator.validate(&build_option_vec(6))?,
764 Validation::Invalid(_)
765 ));
766
767 Ok(())
768 }
769
770 #[test]
771 fn string_max_length_counts_graphemes() -> Result<(), CustomUserError> {
772 let validator = MaxLengthValidator::new(5);
773 let validator: &dyn StringValidator = &validator;
774
775 assert!(matches!(validator.validate("")?, Validation::Valid));
776 assert!(matches!(validator.validate("five!")?, Validation::Valid));
777 assert!(matches!(
778 validator.validate("♥️♥️♥️♥️♥️")?,
779 Validation::Valid
780 ));
781 assert!(matches!(
782 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
783 Validation::Valid
784 ));
785
786 assert!(matches!(
787 validator.validate("five!!!")?,
788 Validation::Invalid(_)
789 ));
790 assert!(matches!(
791 validator.validate("♥️♥️♥️♥️♥️♥️")?,
792 Validation::Invalid(_)
793 ));
794 assert!(matches!(
795 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
796 Validation::Invalid(_)
797 ));
798
799 Ok(())
800 }
801
802 #[test]
803 fn slice_max_length() -> Result<(), CustomUserError> {
804 let validator = MaxLengthValidator::new(5);
805 let validator: &dyn MultiOptionValidator<str> = &validator;
806
807 assert!(matches!(
808 validator.validate(&build_option_vec(0))?,
809 Validation::Valid
810 ));
811 assert!(matches!(
812 validator.validate(&build_option_vec(1))?,
813 Validation::Valid
814 ));
815 assert!(matches!(
816 validator.validate(&build_option_vec(2))?,
817 Validation::Valid
818 ));
819 assert!(matches!(
820 validator.validate(&build_option_vec(3))?,
821 Validation::Valid
822 ));
823 assert!(matches!(
824 validator.validate(&build_option_vec(4))?,
825 Validation::Valid
826 ));
827 assert!(matches!(
828 validator.validate(&build_option_vec(5))?,
829 Validation::Valid
830 ));
831 assert!(matches!(
832 validator.validate(&build_option_vec(6))?,
833 Validation::Invalid(_)
834 ));
835 assert!(matches!(
836 validator.validate(&build_option_vec(7))?,
837 Validation::Invalid(_)
838 ));
839 assert!(matches!(
840 validator.validate(&build_option_vec(8))?,
841 Validation::Invalid(_)
842 ));
843
844 Ok(())
845 }
846
847 #[test]
848 fn string_min_length_counts_graphemes() -> Result<(), CustomUserError> {
849 let validator = MinLengthValidator::new(5);
850 let validator: &dyn StringValidator = &validator;
851
852 assert!(matches!(validator.validate("")?, Validation::Invalid(_)));
853 assert!(matches!(
854 validator.validate("♥️♥️♥️♥️")?,
855 Validation::Invalid(_)
856 ));
857 assert!(matches!(
858 validator.validate("mike")?,
859 Validation::Invalid(_)
860 ));
861
862 assert!(matches!(validator.validate("five!")?, Validation::Valid));
863 assert!(matches!(validator.validate("five!!!")?, Validation::Valid));
864 assert!(matches!(
865 validator.validate("♥️♥️♥️♥️♥️")?,
866 Validation::Valid
867 ));
868 assert!(matches!(
869 validator.validate("♥️♥️♥️♥️♥️♥️")?,
870 Validation::Valid
871 ));
872 assert!(matches!(
873 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
874 Validation::Valid
875 ));
876 assert!(matches!(
877 validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
878 Validation::Valid
879 ));
880
881 Ok(())
882 }
883
884 #[test]
885 fn slice_min_length() -> Result<(), CustomUserError> {
886 let validator = MinLengthValidator::new(5);
887 let validator: &dyn MultiOptionValidator<str> = &validator;
888
889 assert!(matches!(
890 validator.validate(&build_option_vec(0))?,
891 Validation::Invalid(_)
892 ));
893 assert!(matches!(
894 validator.validate(&build_option_vec(1))?,
895 Validation::Invalid(_)
896 ));
897 assert!(matches!(
898 validator.validate(&build_option_vec(2))?,
899 Validation::Invalid(_)
900 ));
901 assert!(matches!(
902 validator.validate(&build_option_vec(3))?,
903 Validation::Invalid(_)
904 ));
905 assert!(matches!(
906 validator.validate(&build_option_vec(4))?,
907 Validation::Invalid(_)
908 ));
909 assert!(matches!(
910 validator.validate(&build_option_vec(5))?,
911 Validation::Valid
912 ));
913 assert!(matches!(
914 validator.validate(&build_option_vec(6))?,
915 Validation::Valid
916 ));
917 assert!(matches!(
918 validator.validate(&build_option_vec(7))?,
919 Validation::Valid
920 ));
921 assert!(matches!(
922 validator.validate(&build_option_vec(8))?,
923 Validation::Valid
924 ));
925
926 Ok(())
927 }
928}