Skip to content

Commit

Permalink
fix: use new InquireLength trait to properly calculate string lengths…
Browse files Browse the repository at this point in the history
… on builtin validators

Closes #3
  • Loading branch information
mikaelmello committed Aug 19, 2021
1 parent 65cb62a commit 7b4bf0f
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 24 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ If the input is not valid, your validator should return `Err(String)`, where the

The validators are typed as a reference to `dyn Fn`. This allows both functions and closures to be used as validators, but it also means that the functions can not hold any mutable references.

Finally, `inquire` has a feature called `builtin_validators` that is included by default. When the feature is on, several built-in validators are exported at the root-level of the library in the form of macros, check their documentation to see more details.

The docs provide full-featured examples.
Finally, `inquire` has a feature called `builtin_validators` that is included by default. When the feature is on, several built-in validators are exported at the root-level of the library in the form of macros. Check their documentation to see more details, they provide full-featured examples.

In the [demo](#Demo) you can see the behavior of an input not passing the requirements in the _amount_ prompt, when the error message "Please type a valid number" is displayed. _Full disclosure, this error message was displayed due to a parsing, not validation, error, but the user experience is the same for both cases._

Expand Down
4 changes: 3 additions & 1 deletion examples/form.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use inquire::{min_length, Confirm, DateSelect, MultiSelect, Password, Select, Text};
use inquire::{
min_length, validator::InquireLength, Confirm, DateSelect, MultiSelect, Password, Select, Text,
};

fn main() {
let fruits = vec![
Expand Down
4 changes: 2 additions & 2 deletions examples/text_options.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use inquire::{required, ui::RenderConfig, Text};
use inquire::{length, required, ui::RenderConfig, validator::InquireLength, Text};

fn main() {
let answer = Text::new("What's your name?")
.with_suggester(&suggester)
.with_validators(&[required!()])
.with_validators(&[required!(), length!(10)])
.prompt()
.unwrap();

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
//! # Simple Example
//!
//! ```rust no_run
//! use inquire::{max_length, Text};
//! use inquire::{max_length, validator::InquireLength, Text};
//!
//! fn main() {
//! let status = Text::new("What are you thinking about?")
Expand Down
176 changes: 159 additions & 17 deletions src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
//!
//! This module also provides several built-in validators generated through macros,
//! exported with the `builtin_validators` feature.

use crate::option_answer::OptionAnswer;

/// Type alias for validators that receive a string slice as the input,
Expand Down Expand Up @@ -111,6 +110,34 @@ pub type DateValidator<'a> = &'a dyn Fn(chrono::NaiveDate) -> Result<(), String>
/// ```
pub type MultiOptionValidator<'a> = &'a dyn Fn(&[OptionAnswer]) -> Result<(), String>;

/// Custom trait to call correct method to retrieve input length.
///
/// The method can vary depending on the type of input.

/// String inputs should count the number of graphemes, via
/// `.graphemes(true).count()`, instead of the number of bytes
/// via `.len()`. While simple slices should keep using `.len()`
pub trait InquireLength {
/// String inputs should count the number of graphemes, via
/// `.graphemes(true).count()`, instead of the number of bytes
/// via `.len()`. While simple slices keep using `.len()`
fn inquire_length(&self) -> usize;
}

impl InquireLength for &str {
fn inquire_length(&self) -> usize {
use unicode_segmentation::UnicodeSegmentation;

self.graphemes(true).count()
}
}

impl<T> InquireLength for &[T] {
fn inquire_length(&self) -> usize {
self.len()
}
}

/// Built-in validator that checks whether the answer is not empty.
///
/// # Arguments
Expand All @@ -121,7 +148,7 @@ pub type MultiOptionValidator<'a> = &'a dyn Fn(&[OptionAnswer]) -> Result<(), St
/// # Examples
///
/// ```
/// use inquire::{required, validator::StringValidator};
/// use inquire::{required, validator::{InquireLength, StringValidator}};
///
/// let validator: StringValidator = required!();
/// assert_eq!(Ok(()), validator("Generic input"));
Expand Down Expand Up @@ -149,9 +176,12 @@ macro_rules! required {
/// Built-in validator that checks whether the answer length is smaller than
/// or equal to the specified threshold.
///
/// Be careful when using this as a StringValidator. The `len()` method used
/// in this validator is not the best tool for that. See this
/// [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust)
/// When using this macro, you **must** also import the [`InquireLength`]
/// trait. The validator uses a custom-built length function that
/// has a special implementation for strings, as we can't rely on a generic
/// `.len()` for them. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
///
/// [`InquireLength`]: crate::validator::InquireLength
///
/// # Arguments
///
Expand All @@ -162,7 +192,7 @@ macro_rules! required {
/// # Examples
///
/// ```
/// use inquire::{max_length, validator::StringValidator};
/// use inquire::{max_length, validator::{InquireLength, StringValidator}};
///
/// let validator: StringValidator = max_length!(5);
/// assert_eq!(Ok(()), validator("Good"));
Expand All @@ -181,7 +211,7 @@ macro_rules! max_length {

($length:expr, $message:expr) => {
{
&|a| match a.len() {
&|a| match a.inquire_length() {
_len if _len <= $length => Ok(()),
_ => Err(String::from($message)),
}
Expand All @@ -193,9 +223,12 @@ macro_rules! max_length {
/// Built-in validator that checks whether the answer length is larger than
/// or equal to the specified threshold.
///
/// Be careful when using this as a StringValidator. The `len()` method used
/// in this validator is not the best tool for that. See this
/// [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust)
/// When using this macro, you **must** also import the [`InquireLength`]
/// trait. The validator uses a custom-built length function that
/// has a special implementation for strings, as we can't rely on a generic
/// `.len()` for them. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
///
/// [`InquireLength`]: crate::validator::InquireLength
///
/// # Arguments
///
Expand All @@ -206,7 +239,7 @@ macro_rules! max_length {
/// # Examples
///
/// ```
/// use inquire::{min_length, validator::StringValidator};
/// use inquire::{min_length, validator::{InquireLength, StringValidator}};
///
/// let validator: StringValidator = min_length!(3);
/// assert_eq!(Ok(()), validator("Yes"));
Expand All @@ -225,7 +258,7 @@ macro_rules! min_length {

($length:expr, $message:expr) => {
{
&|a| match a.len() {
&|a| match a.inquire_length() {
_len if _len >= $length => Ok(()),
_ => Err(String::from($message)),
}
Expand All @@ -236,9 +269,12 @@ macro_rules! min_length {
/// Built-in validator that checks whether the answer length is equal to
/// the specified value.
///
/// Be careful when using this as a StringValidator. The `len()` method used
/// in this validator is not the best tool for that. See this
/// [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust)
/// When using this macro, you **must** also import the [`InquireLength`]
/// trait. The validator uses a custom-built length function that
/// has a special implementation for strings, as we can't rely on a generic
/// `.len()` for them. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
///
/// [`InquireLength`]: crate::validator::InquireLength
///
/// # Arguments
///
Expand All @@ -249,7 +285,7 @@ macro_rules! min_length {
/// # Examples
///
/// ```
/// use inquire::{length, validator::StringValidator};
/// use inquire::{length, validator::{InquireLength, StringValidator}};
///
/// let validator: StringValidator = length!(3);
/// assert_eq!(Ok(()), validator("Yes"));
Expand All @@ -267,9 +303,115 @@ macro_rules! length {
};

($length:expr, $message:expr) => {{
&|a| match a.len() {
&|a| match a.inquire_length() {
_len if _len == $length => Ok(()),
_ => Err(String::from($message)),
}
}};
}

#[cfg(test)]
mod test {
use crate::{
option_answer::OptionAnswer,
validator::{InquireLength, MultiOptionValidator, StringValidator},
};

fn build_option_vec(len: usize) -> Vec<OptionAnswer> {
let mut options = Vec::new();

for i in 0..len {
options.push(OptionAnswer::new(i, ""));
}

options
}

#[test]
fn string_length_counts_graphemes() {
let validator: StringValidator = length!(5);

assert!(matches!(validator("five!"), Ok(_)));
assert!(matches!(validator("♥️♥️♥️♥️♥️"), Ok(_)));
assert!(matches!(validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"), Ok(_)));

assert!(matches!(validator("five!!!"), Err(_)));
assert!(matches!(validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"), Err(_)));
assert!(matches!(
validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"),
Err(_)
));
}

#[test]
fn slice_length() {
let validator: MultiOptionValidator = length!(5);

assert!(matches!(validator(&build_option_vec(5)), Ok(_)));
assert!(matches!(validator(&build_option_vec(4)), Err(_)));
assert!(matches!(validator(&build_option_vec(6)), Err(_)));
}

#[test]
fn string_max_length_counts_graphemes() {
let validator: StringValidator = max_length!(5);

assert!(matches!(validator(""), Ok(_)));
assert!(matches!(validator("five!"), Ok(_)));
assert!(matches!(validator("♥️♥️♥️♥️♥️"), Ok(_)));
assert!(matches!(validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"), Ok(_)));

assert!(matches!(validator("five!!!"), Err(_)));
assert!(matches!(validator("♥️♥️♥️♥️♥️♥️"), Err(_)));
assert!(matches!(
validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"),
Err(_)
));
}

#[test]
fn slice_max_length() {
let validator: MultiOptionValidator = max_length!(5);

assert!(matches!(validator(&build_option_vec(0)), Ok(_)));
assert!(matches!(validator(&build_option_vec(1)), Ok(_)));
assert!(matches!(validator(&build_option_vec(2)), Ok(_)));
assert!(matches!(validator(&build_option_vec(3)), Ok(_)));
assert!(matches!(validator(&build_option_vec(4)), Ok(_)));
assert!(matches!(validator(&build_option_vec(5)), Ok(_)));
assert!(matches!(validator(&build_option_vec(6)), Err(_)));
assert!(matches!(validator(&build_option_vec(7)), Err(_)));
assert!(matches!(validator(&build_option_vec(8)), Err(_)));
}

#[test]
fn string_min_length_counts_graphemes() {
let validator: StringValidator = min_length!(5);

assert!(matches!(validator(""), Err(_)));
assert!(matches!(validator("♥️♥️♥️♥️"), Err(_)));
assert!(matches!(validator("mike"), Err(_)));

assert!(matches!(validator("five!"), Ok(_)));
assert!(matches!(validator("five!!!"), Ok(_)));
assert!(matches!(validator("♥️♥️♥️♥️♥️"), Ok(_)));
assert!(matches!(validator("♥️♥️♥️♥️♥️♥️"), Ok(_)));
assert!(matches!(validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"), Ok(_)));
assert!(matches!(validator("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️"), Ok(_)));
}

#[test]
fn slice_min_length() {
let validator: MultiOptionValidator = min_length!(5);

assert!(matches!(validator(&build_option_vec(0)), Err(_)));
assert!(matches!(validator(&build_option_vec(1)), Err(_)));
assert!(matches!(validator(&build_option_vec(2)), Err(_)));
assert!(matches!(validator(&build_option_vec(3)), Err(_)));
assert!(matches!(validator(&build_option_vec(4)), Err(_)));
assert!(matches!(validator(&build_option_vec(5)), Ok(_)));
assert!(matches!(validator(&build_option_vec(6)), Ok(_)));
assert!(matches!(validator(&build_option_vec(7)), Ok(_)));
assert!(matches!(validator(&build_option_vec(8)), Ok(_)));
}
}

0 comments on commit 7b4bf0f

Please sign in to comment.