validation/
validation.rs

1//! To validate a value during input, either
2//! * specify a 'validate' attribute (like in the 'address' member), or
3//! * specify one of or both of:
4//!   - 'validate_on_key' (validate on each input for user feedback), and/or
5//!   - 'validate_on_submit' (validate on the value submission, possibly displaying an error message)
6
7use derive_wizard::Wizard;
8
9#[derive(Debug, Wizard)]
10#[allow(dead_code)]
11struct ServerConfig {
12    #[prompt("Enter server address (host:port):")]
13    #[validate("validate_address")]
14    address: String,
15
16    #[prompt("Enter admin username:")]
17    #[validate_on_key("validate_username")]
18    username: String,
19
20    #[prompt("Enter admin email:")]
21    #[validate_on_submit("validate_email")]
22    email: String,
23}
24
25/// Validates that the address is in host:port format
26pub fn validate_address(input: &str, _answers: &derive_wizard::Answers) -> Result<(), String> {
27    if input.contains(':') && input.len() >= 3 && !input.starts_with(':') && !input.ends_with(':') {
28        let parts: Vec<&str> = input.split(':').collect();
29        if parts.len() == 2 && !parts[0].is_empty() && parts[1].parse::<u16>().is_ok() {
30            return Ok(());
31        }
32    }
33    Err("Address must be in format 'host:port' (e.g., 'localhost:8080')".to_string())
34}
35
36/// Validates username: 3-20 chars, alphanumeric and underscores only
37pub fn validate_username(input: &str, _answers: &derive_wizard::Answers) -> Result<(), String> {
38    if input.len() < 3 {
39        return Err("Username must be at least 3 characters long".to_string());
40    }
41    if input.len() > 20 {
42        return Err("Username must be at most 20 characters long".to_string());
43    }
44    if !input.chars().all(|c| c.is_alphanumeric() || c == '_') {
45        return Err("Username can only contain letters, numbers, and underscores".to_string());
46    }
47    Ok(())
48}
49
50/// Validates email address format
51pub fn validate_email(input: &str, _answers: &derive_wizard::Answers) -> Result<(), String> {
52    if !input.contains('@') {
53        return Err("Email must contain an @ symbol".to_string());
54    }
55    let parts: Vec<&str> = input.split('@').collect();
56    if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
57        return Err("Email must be in format 'user@domain'".to_string());
58    }
59    if !parts[1].contains('.') {
60        return Err("Email domain must contain a dot (e.g., example.com)".to_string());
61    }
62    Ok(())
63}
64
65fn main() {
66    let config = ServerConfig::wizard_builder().build();
67    println!("{config:#?}");
68}