use super::input::{CharsetRule, FormatRule, InputValidationError, StringValidator};
use crate::utils::error::Result;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum RuleDefinition {
Length {
field: String,
min: usize,
max: usize,
},
Pattern { field: String, pattern: String },
Charset { field: String, charset: String },
Format { field: String, format: String },
Whitelist { field: String, allowed: Vec<String> },
Blacklist {
field: String,
forbidden: Vec<String>,
},
And {
left: Box<RuleDefinition>,
right: Box<RuleDefinition>,
},
Or {
left: Box<RuleDefinition>,
right: Box<RuleDefinition>,
},
Not { inner: Box<RuleDefinition> },
}
impl RuleDefinition {
pub fn length(field: &str, min: usize, max: usize) -> Self {
Self::Length {
field: field.to_string(),
min,
max,
}
}
pub fn pattern(field: &str, pattern: &str) -> Self {
Self::Pattern {
field: field.to_string(),
pattern: pattern.to_string(),
}
}
pub fn charset(field: &str, charset: &str) -> Self {
Self::Charset {
field: field.to_string(),
charset: charset.to_string(),
}
}
pub fn format(field: &str, format: &str) -> Self {
Self::Format {
field: field.to_string(),
format: format.to_string(),
}
}
pub fn whitelist(field: &str, allowed: Vec<String>) -> Self {
Self::Whitelist {
field: field.to_string(),
allowed,
}
}
pub fn blacklist(field: &str, forbidden: Vec<String>) -> Self {
Self::Blacklist {
field: field.to_string(),
forbidden,
}
}
pub fn and(self, other: RuleDefinition) -> Self {
Self::And {
left: Box::new(self),
right: Box::new(other),
}
}
pub fn or(self, other: RuleDefinition) -> Self {
Self::Or {
left: Box::new(self),
right: Box::new(other),
}
}
pub fn negate(self) -> Self {
Self::Not {
inner: Box::new(self),
}
}
pub fn field_name(&self) -> Option<&str> {
match self {
Self::Length { field, .. }
| Self::Pattern { field, .. }
| Self::Charset { field, .. }
| Self::Format { field, .. }
| Self::Whitelist { field, .. }
| Self::Blacklist { field, .. } => Some(field),
Self::And { left, .. } => left.field_name(),
Self::Or { left, .. } => left.field_name(),
Self::Not { inner } => inner.field_name(),
}
}
}
pub struct CompiledValidator {
validators: HashMap<String, StringValidator>,
}
impl CompiledValidator {
pub fn new(validators: HashMap<String, StringValidator>) -> Self {
Self { validators }
}
pub fn validate_field(&self, field: &str, value: &str) -> Result<String> {
if let Some(validator) = self.validators.get(field) {
validator.validate(value).map_err(|e| {
InputValidationError::FormatViolation {
field: field.to_string(),
reason: format!("Validation failed: {}", e),
}
.into()
})
} else {
Err(InputValidationError::EmptyInput {
field: format!("No validator defined for field: {}", field),
}
.into())
}
}
pub fn validate_fields(
&self, fields: &HashMap<String, String>,
) -> Result<HashMap<String, String>> {
let mut validated = HashMap::new();
for (field, value) in fields {
let validated_value = self.validate_field(field, value)?;
validated.insert(field.clone(), validated_value);
}
Ok(validated)
}
}
pub struct RuleCompiler {
}
impl RuleCompiler {
pub fn new() -> Self {
Self {}
}
pub fn compile(&self, rules: &[RuleDefinition]) -> Result<CompiledValidator> {
let mut validators: HashMap<String, StringValidator> = HashMap::new();
let mut rules_by_field: HashMap<String, Vec<&RuleDefinition>> = HashMap::new();
for rule in rules {
if let Some(field) = rule.field_name() {
rules_by_field
.entry(field.to_string())
.or_default()
.push(rule);
}
}
for (field, field_rules) in rules_by_field {
let mut validator = StringValidator::new();
for rule in field_rules {
validator = self.apply_rule(validator, rule)?;
}
validators.insert(field, validator);
}
Ok(CompiledValidator::new(validators))
}
fn apply_rule(
&self, mut validator: StringValidator, rule: &RuleDefinition,
) -> Result<StringValidator> {
match rule {
RuleDefinition::Length { min, max, .. } => {
validator = validator.with_length(*min, *max);
}
RuleDefinition::Pattern { pattern, .. } => {
validator = validator.with_pattern(pattern);
}
RuleDefinition::Charset { charset, .. } => {
let charset_rule = match charset.as_str() {
"alphanumeric" => CharsetRule::alphanumeric(),
"identifier" => CharsetRule::identifier(),
"ascii_printable" => CharsetRule::ascii_printable(),
_ => {
return Err(InputValidationError::FormatViolation {
field: "charset".to_string(),
reason: format!("Unknown charset type: {}", charset),
}
.into())
}
};
validator = validator.with_charset(charset_rule);
}
RuleDefinition::Format { format, .. } => {
let format_rule = match format.as_str() {
"email" => FormatRule::Email,
"uuid" => FormatRule::Uuid,
"semver" => FormatRule::Semver,
"ip" => FormatRule::IpAddress,
"hostname" => FormatRule::Hostname,
_ => {
return Err(InputValidationError::FormatViolation {
field: "format".to_string(),
reason: format!("Unknown format type: {}", format),
}
.into())
}
};
validator = validator.with_format(format_rule);
}
RuleDefinition::Whitelist { allowed, .. } => {
validator = validator.with_whitelist(allowed.clone());
}
RuleDefinition::Blacklist { forbidden, .. } => {
validator = validator.with_blacklist(forbidden.clone());
}
RuleDefinition::And { .. } | RuleDefinition::Or { .. } | RuleDefinition::Not { .. } => {
return Err(InputValidationError::CompositeViolation {
reason: "Composite rules (AND/OR/NOT) not yet supported in compiler"
.to_string(),
}
.into());
}
}
Ok(validator)
}
}
impl Default for RuleCompiler {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_definition_length() {
let rule = RuleDefinition::length("username", 3, 32);
assert_eq!(rule.field_name(), Some("username"));
}
#[test]
fn test_rule_definition_pattern() {
let rule = RuleDefinition::pattern("email", r"^.+@.+$");
assert_eq!(rule.field_name(), Some("email"));
}
#[test]
fn test_rule_compiler_single_rule() {
let compiler = RuleCompiler::new();
let rules = vec![RuleDefinition::length("username", 3, 32)];
let validator = compiler.compile(&rules).expect("compilation succeeds");
let result = validator.validate_field("username", "alice");
assert!(result.is_ok());
}
#[test]
fn test_rule_compiler_multiple_rules_same_field() {
let compiler = RuleCompiler::new();
let rules = vec![
RuleDefinition::length("username", 3, 32),
RuleDefinition::charset("username", "identifier"),
];
let validator = compiler.compile(&rules).expect("compilation succeeds");
assert!(validator.validate_field("username", "alice_123").is_ok());
assert!(validator.validate_field("username", "ab").is_err());
assert!(validator.validate_field("username", "alice@123").is_err());
}
#[test]
fn test_rule_compiler_multiple_fields() {
let compiler = RuleCompiler::new();
let rules = vec![
RuleDefinition::length("username", 3, 32),
RuleDefinition::length("email", 5, 254),
RuleDefinition::format("email", "email"),
];
let validator = compiler.compile(&rules).expect("compilation succeeds");
assert!(validator.validate_field("username", "alice").is_ok());
assert!(validator
.validate_field("email", "alice@example.com")
.is_ok());
}
#[test]
fn test_rule_compiler_format_email() {
let compiler = RuleCompiler::new();
let rules = vec![RuleDefinition::format("email", "email")];
let validator = compiler.compile(&rules).expect("compilation succeeds");
assert!(validator
.validate_field("email", "alice@example.com")
.is_ok());
assert!(validator.validate_field("email", "not-an-email").is_err());
}
#[test]
fn test_rule_compiler_whitelist() {
let compiler = RuleCompiler::new();
let rules = vec![RuleDefinition::whitelist(
"role",
vec!["admin".to_string(), "user".to_string()],
)];
let validator = compiler.compile(&rules).expect("compilation succeeds");
assert!(validator.validate_field("role", "admin").is_ok());
assert!(validator.validate_field("role", "user").is_ok());
assert!(validator.validate_field("role", "guest").is_err());
}
#[test]
fn test_rule_compiler_blacklist() {
let compiler = RuleCompiler::new();
let rules = vec![RuleDefinition::blacklist(
"username",
vec!["admin".to_string(), "root".to_string()],
)];
let validator = compiler.compile(&rules).expect("compilation succeeds");
assert!(validator.validate_field("username", "alice").is_ok());
assert!(validator.validate_field("username", "admin").is_err());
assert!(validator.validate_field("username", "root").is_err());
}
#[test]
fn test_compiled_validator_validate_fields() {
let compiler = RuleCompiler::new();
let rules = vec![
RuleDefinition::length("username", 3, 32),
RuleDefinition::format("email", "email"),
];
let validator = compiler.compile(&rules).expect("compilation succeeds");
let mut fields = HashMap::new();
fields.insert("username".to_string(), "alice".to_string());
fields.insert("email".to_string(), "alice@example.com".to_string());
let result = validator.validate_fields(&fields);
assert!(result.is_ok());
let validated = result.unwrap();
assert_eq!(validated.get("username"), Some(&"alice".to_string()));
assert_eq!(
validated.get("email"),
Some(&"alice@example.com".to_string())
);
}
#[test]
fn test_compiled_validator_unknown_field() {
let compiler = RuleCompiler::new();
let rules = vec![RuleDefinition::length("username", 3, 32)];
let validator = compiler.compile(&rules).expect("compilation succeeds");
let result = validator.validate_field("unknown", "value");
assert!(result.is_err());
}
#[test]
fn test_rule_definition_and() {
let rule = RuleDefinition::length("field", 5, 10)
.and(RuleDefinition::charset("field", "alphanumeric"));
assert_eq!(rule.field_name(), Some("field"));
}
#[test]
fn test_rule_definition_or() {
let rule =
RuleDefinition::length("field", 5, 10).or(RuleDefinition::length("field", 20, 30));
assert_eq!(rule.field_name(), Some("field"));
}
#[test]
fn test_rule_definition_negate() {
let rule = RuleDefinition::length("field", 5, 10).negate();
assert_eq!(rule.field_name(), Some("field"));
}
}