validation/
validation.rs

1//! To validate a value during input, specify a 'validate' attribute.
2//! The validation function should have the signature:
3//! fn(&str, &Answers) -> Result<(), String>
4//!
5//! Backends will decide when to show validation errors (e.g., on submit, on each keystroke, etc.)
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("validate_username")]
18    username: String,
19
20    #[prompt("Enter admin email:")]
21    #[validate("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().unwrap();
67    println!("{config:#?}");
68}