diff --git a/promkit/src/preset/password.rs b/promkit/src/preset/password.rs index 403f698a..02a0b362 100644 --- a/promkit/src/preset/password.rs +++ b/promkit/src/preset/password.rs @@ -2,7 +2,6 @@ use crate::{ core::crossterm::style::ContentStyle, - validate::{ErrorMessageGenerator, Validator}, Prompt, }; @@ -55,11 +54,12 @@ impl Password { self } - /// Configures a validator for the password input with a function to validate the input and another to configure the error message. + /// Configures a validator for the password input with a function or closure to + /// validate the input and another to generate the error message. pub fn validator( mut self, - validator: Validator, - error_message_generator: ErrorMessageGenerator, + validator: impl Fn(&str) -> bool + Send + Sync + 'static, + error_message_generator: impl Fn(&str) -> String + Send + Sync + 'static, ) -> Self { self = Password(self.0.validator(validator, error_message_generator)); self diff --git a/promkit/src/preset/readline.rs b/promkit/src/preset/readline.rs index 69474d97..94202d33 100644 --- a/promkit/src/preset/readline.rs +++ b/promkit/src/preset/readline.rs @@ -14,7 +14,7 @@ use crate::{ }, preset::Evaluator, suggest::Suggest, - validate::{ErrorMessageGenerator, Validator, ValidatorManager}, + validate::ValidatorManager, widgets::{ listbox::{self, Listbox}, text::{self, Text}, @@ -261,11 +261,15 @@ impl Readline { self } - /// Configures a validator for the input with a function to validate the input and another to configure the error message. + /// Configures a validator for the input with a function or closure to validate + /// the input and another to generate the error message. + /// + /// Closures can capture external variables, enabling validation against + /// runtime state (e.g., checking if a filename already exists). pub fn validator( mut self, - validator: Validator, - error_message_generator: ErrorMessageGenerator, + validator: impl Fn(&str) -> bool + Send + Sync + 'static, + error_message_generator: impl Fn(&str) -> String + Send + Sync + 'static, ) -> Self { self.validator = Some(ValidatorManager::new(validator, error_message_generator)); self diff --git a/promkit/src/validate.rs b/promkit/src/validate.rs index fd84a5c3..4db2071b 100644 --- a/promkit/src/validate.rs +++ b/promkit/src/validate.rs @@ -1,6 +1,3 @@ -pub type Validator = fn(&T) -> bool; -pub type ErrorMessageGenerator = fn(&T) -> String; - /// A generic structure for validating inputs of any type. /// /// This structure allows for the definition of custom validation logic @@ -11,33 +8,36 @@ pub struct ValidatorManager { /// A function that takes a reference /// to an input of type `T` and returns a boolean /// indicating whether the input passes the validation. - validator: Validator, + validator: Box bool + Send + Sync>, /// A function that takes a reference /// to an input of type `T` and returns a `String` /// that describes the validation error. - error_message_generator: ErrorMessageGenerator, + error_message_generator: Box String + Send + Sync>, } impl ValidatorManager { - /// Constructs a new `Validator` instance + /// Constructs a new `ValidatorManager` instance /// with the specified validator and error message generator functions. /// /// # Arguments /// - /// * `validator` - A function that takes a reference + /// * `validator` - A function or closure that takes a reference /// to an input of type `T` and returns a boolean /// indicating whether the input passes the validation. - /// * `error_message_generator` - A function that takes a reference + /// * `error_message_generator` - A function or closure that takes a reference /// to an input of type `T` and returns a `String` /// that describes the validation error. /// /// # Returns /// - /// Returns a new instance of `Validator`. - pub fn new(validator: Validator, error_message_generator: ErrorMessageGenerator) -> Self { + /// Returns a new instance of `ValidatorManager`. + pub fn new( + validator: impl Fn(&T) -> bool + Send + Sync + 'static, + error_message_generator: impl Fn(&T) -> String + Send + Sync + 'static, + ) -> Self { Self { - validator, - error_message_generator, + validator: Box::new(validator), + error_message_generator: Box::new(error_message_generator), } } @@ -72,3 +72,48 @@ impl ValidatorManager { (self.error_message_generator)(input) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{Arc, Mutex}; + + #[test] + fn function_pointer_validator() { + let vm = ValidatorManager::new( + |text: &str| text.len() > 3, + |text: &str| format!("Too short: {}", text.len()), + ); + assert!(vm.validate("hello")); + assert!(!vm.validate("hi")); + assert_eq!(vm.generate_error_message("hi"), "Too short: 2"); + } + + #[test] + fn closure_captures_owned_data() { + let forbidden: Vec = vec!["admin".into(), "root".into()]; + let vm = ValidatorManager::new( + move |text: &str| !forbidden.contains(&text.to_string()), + |text: &str| format!("'{}' is not allowed", text), + ); + assert!(!vm.validate("admin")); + assert!(vm.validate("user")); + } + + #[test] + fn closure_captures_shared_state() { + let counter = Arc::new(Mutex::new(0u32)); + let counter_clone = Arc::clone(&counter); + let vm = ValidatorManager::new( + move |_text: &str| { + let mut c = counter_clone.lock().unwrap(); + *c += 1; + true + }, + |_text: &str| String::new(), + ); + vm.validate("a"); + vm.validate("b"); + assert_eq!(*counter.lock().unwrap(), 2); + } +}