Skip to content

Commit

Permalink
Merge pull request #219 from qt2/password_validation
Browse files Browse the repository at this point in the history
Add `validate_with` function to password prompt
  • Loading branch information
pksunkara committed Mar 13, 2023
2 parents f6f6e26 + 4b92988 commit d588da3
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 17 deletions.
7 changes: 7 additions & 0 deletions examples/password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ fn main() {
let password = Password::with_theme(&ColorfulTheme::default())
.with_prompt("Password")
.with_confirmation("Repeat password", "Error: the passwords don't match.")
.validate_with(|input: &String| -> Result<(), &str> {
if input.len() > 3 {
Ok(())
} else {
Err("Password must be longer than 3")
}
})
.interact()
.unwrap();

Expand Down
84 changes: 67 additions & 17 deletions src/prompts/password.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use std::io;

use crate::theme::{SimpleTheme, TermThemeRenderer, Theme};
use crate::{
theme::{SimpleTheme, TermThemeRenderer, Theme},
validate::PasswordValidator,
};

use console::Term;
use zeroize::Zeroizing;

type PasswordValidatorCallback<'a> = Box<dyn Fn(&String) -> Option<String> + 'a>;

/// Renders a password input prompt.
///
/// ## Example usage
Expand All @@ -25,6 +30,7 @@ pub struct Password<'a> {
theme: &'a dyn Theme,
allow_empty_password: bool,
confirmation_prompt: Option<(String, String)>,
validator: Option<PasswordValidatorCallback<'a>>,
}

impl Default for Password<'static> {
Expand All @@ -40,7 +46,7 @@ impl Password<'static> {
}
}

impl Password<'_> {
impl<'a> Password<'a> {
/// Sets the password input prompt.
pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
self.prompt = prompt.into();
Expand Down Expand Up @@ -73,6 +79,47 @@ impl Password<'_> {
self
}

/// Registers a validator.
///
/// # Example
///
/// ```no_run
/// # use dialoguer::Password;
/// let password: String = Password::new()
/// .with_prompt("Enter password")
/// .validate_with(|input: &String| -> Result<(), &str> {
/// if input.len() > 8 {
/// Ok(())
/// } else {
/// Err("Password must be longer than 8")
/// }
/// })
/// .interact()
/// .unwrap();
/// ```
pub fn validate_with<V>(&mut self, validator: V) -> &mut Self
where
V: PasswordValidator + 'a,
V::Err: ToString,
{
let old_validator_func = self.validator.take();

self.validator = Some(Box::new(move |value: &String| -> Option<String> {
if let Some(old) = &old_validator_func {
if let Some(err) = old(value) {
return Some(err);
}
}

match validator.validate(value) {
Ok(()) => None,
Err(err) => Some(err.to_string()),
}
}));

self
}

/// Enables user interaction and returns the result.
///
/// If the user confirms the result is `true`, `false` otherwise.
Expand All @@ -89,28 +136,30 @@ impl Password<'_> {
loop {
let password = Zeroizing::new(self.prompt_password(&mut render, &self.prompt)?);

if let Some(ref validator) = self.validator {
if let Some(err) = validator(&password) {
render.error(&err)?;
continue;
}
}

if let Some((ref prompt, ref err)) = self.confirmation_prompt {
let pw2 = Zeroizing::new(self.prompt_password(&mut render, prompt)?);

if *password == *pw2 {
render.clear()?;
if self.report {
render.password_prompt_selection(&self.prompt)?;
}
term.flush()?;
return Ok((*password).clone());
if *password != *pw2 {
render.error(err)?;
continue;
}
}

render.error(err)?;
} else {
render.clear()?;
if self.report {
render.password_prompt_selection(&self.prompt)?;
}
term.flush()?;
render.clear()?;

return Ok((*password).clone());
if self.report {
render.password_prompt_selection(&self.prompt)?;
}
term.flush()?;

return Ok((*password).clone());
}
}

Expand Down Expand Up @@ -139,6 +188,7 @@ impl<'a> Password<'a> {
theme,
allow_empty_password: false,
confirmation_prompt: None,
validator: None,
}
}
}
23 changes: 23 additions & 0 deletions src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,26 @@ where
self(input)
}
}

/// Trait for password validators.
#[allow(clippy::ptr_arg)]
pub trait PasswordValidator {
type Err;

/// Invoked with the value to validate.
///
/// If this produces `Ok(())` then the value is used and parsed, if
/// an error is returned validation fails with that error.
fn validate(&self, input: &String) -> Result<(), Self::Err>;
}

impl<F, E> PasswordValidator for F
where
F: Fn(&String) -> Result<(), E>,
{
type Err = E;

fn validate(&self, input: &String) -> Result<(), Self::Err> {
self(input)
}
}

0 comments on commit d588da3

Please sign in to comment.