skp_validator_rules/string/
ascii.rs

1//! ASCII validation rule.
2
3use skp_validator_core::{Rule, ValidationContext, ValidationErrors, ValidationError, ValidationResult};
4
5/// ASCII validation rule - value must contain only ASCII characters.
6///
7/// # Example
8///
9/// ```rust
10/// use skp_validator_rules::string::ascii::AsciiRule;
11/// use skp_validator_core::{Rule, ValidationContext};
12///
13/// let rule = AsciiRule::new();
14/// let ctx = ValidationContext::default();
15///
16/// assert!(rule.validate("hello123", &ctx).is_ok());
17/// assert!(rule.validate("héllo", &ctx).is_err()); // Contains non-ASCII
18/// ```
19#[derive(Debug, Clone, Default)]
20pub struct AsciiRule {
21    /// Allow only printable ASCII (32-126)
22    pub printable_only: bool,
23    /// Custom error message
24    pub message: Option<String>,
25}
26
27impl AsciiRule {
28    /// Create a new ASCII rule.
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Only allow printable ASCII characters (32-126).
34    pub fn printable(mut self) -> Self {
35        self.printable_only = true;
36        self
37    }
38
39    /// Set custom error message.
40    pub fn message(mut self, msg: impl Into<String>) -> Self {
41        self.message = Some(msg.into());
42        self
43    }
44
45    fn get_message(&self) -> String {
46        self.message.clone().unwrap_or_else(|| {
47            if self.printable_only {
48                "Must contain only printable ASCII characters".to_string()
49            } else {
50                "Must contain only ASCII characters".to_string()
51            }
52        })
53    }
54}
55
56impl Rule<str> for AsciiRule {
57    fn validate(&self, value: &str, _ctx: &ValidationContext) -> ValidationResult<()> {
58        if value.is_empty() {
59            return Ok(());
60        }
61
62        let valid = if self.printable_only {
63            value.chars().all(|c| c.is_ascii() && (' '..='~').contains(&c))
64        } else {
65            value.is_ascii()
66        };
67
68        if valid {
69            Ok(())
70        } else {
71            Err(ValidationErrors::from_iter([
72                ValidationError::root("ascii", self.get_message())
73            ]))
74        }
75    }
76
77    fn name(&self) -> &'static str {
78        "ascii"
79    }
80
81    fn default_message(&self) -> String {
82        self.get_message()
83    }
84}
85
86impl Rule<String> for AsciiRule {
87    fn validate(&self, value: &String, ctx: &ValidationContext) -> ValidationResult<()> {
88        <Self as Rule<str>>::validate(self, value.as_str(), ctx)
89    }
90
91    fn name(&self) -> &'static str {
92        "ascii"
93    }
94
95    fn default_message(&self) -> String {
96        self.get_message()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_ascii() {
106        let rule = AsciiRule::new();
107        let ctx = ValidationContext::default();
108
109        assert!(rule.validate("hello123", &ctx).is_ok());
110        assert!(rule.validate("Hello, World!", &ctx).is_ok());
111        assert!(rule.validate("héllo", &ctx).is_err()); // é is non-ASCII
112        assert!(rule.validate("你好", &ctx).is_err());
113    }
114
115    #[test]
116    fn test_printable_only() {
117        let rule = AsciiRule::new().printable();
118        let ctx = ValidationContext::default();
119
120        assert!(rule.validate("hello", &ctx).is_ok());
121        assert!(rule.validate("hello\t", &ctx).is_err()); // Tab is not printable
122        assert!(rule.validate("hello\n", &ctx).is_err()); // Newline is not printable
123    }
124
125    #[test]
126    fn test_empty_is_valid() {
127        let rule = AsciiRule::new();
128        let ctx = ValidationContext::default();
129
130        assert!(rule.validate("", &ctx).is_ok());
131    }
132}