use log::debug;
use toml::{Table, Value};
mod allowed_values;
mod length;
mod max;
mod min;
mod not_empty;
mod regex_match;
mod type_check;
pub use allowed_values::allowed_values;
pub use length::{length_max, length_min};
pub use max::max;
pub use min::min;
pub use not_empty::not_empty;
pub use regex_match::regex_match;
pub use type_check::type_check;
pub fn validate_input(input: &str, key: &str, validation_table: &Table) -> Result<(), String> {
debug!("Validating input '{}' for key '{}'", input, key);
let key_prefix = format!("{key}.");
debug!("Looking for validation rules with prefix '{}'", key_prefix);
let validation_rules: Vec<(&String, &Value)> = validation_table
.iter()
.filter(|(rule_key, _)| rule_key.starts_with(&key_prefix))
.collect();
if validation_rules.is_empty() {
debug!("No validation rules found for key '{}'", key);
return Ok(());
}
debug!(
"Found {} validation rules for key '{}'",
validation_rules.len(),
key
);
for (rule_key, rule_value) in validation_rules {
let method = rule_key.strip_prefix(&key_prefix).unwrap();
debug!(
"Applying validation rule '{}' with value {:?}",
method, rule_value
);
match method {
"allowed_values" => {
debug!("Validating against allowed values");
allowed_values(input, rule_value)?;
debug!("Input passed allowed_values validation");
}
"min" => {
debug!("Validating minimum value");
min(input, rule_value)?;
debug!("Input passed min validation");
}
"max" => {
debug!("Validating maximum value");
max(input, rule_value)?;
debug!("Input passed max validation");
}
"regex_match" => {
debug!("Validating regex pattern match");
regex_match(input, rule_value)?;
debug!("Input passed regex_match validation");
}
"not_empty" => {
debug!("Validating non-empty input");
not_empty(input, rule_value)?;
debug!("Input passed not_empty validation");
}
"type" => {
debug!("Validating type check");
type_check(input, rule_value)?;
debug!("Input passed type validation");
}
"length_min" => {
debug!("Validating minimum length");
length_min(input, rule_value)?;
debug!("Input passed length_min validation");
}
"length_max" => {
debug!("Validating maximum length");
length_max(input, rule_value)?;
debug!("Input passed length_max validation");
}
_ => {
debug!("Unknown validation method: {}", method);
}
}
}
debug!("All validation rules passed for key '{}'", key);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use toml::Value;
#[test]
fn test_validate_input_allowed_values() {
let mut validation_table = Table::new();
validation_table.insert(
"role.allowed_values".to_string(),
Value::Array(vec![
Value::String("admin".to_string()),
Value::String("user".to_string()),
Value::String("guest".to_string()),
]),
);
assert!(validate_input("admin", "role", &validation_table).is_ok());
let result = validate_input("manager", "role", &validation_table);
assert!(result.is_err());
assert!(validate_input("anything", "name", &validation_table).is_ok());
}
#[test]
fn test_validate_input_min_max() {
let mut validation_table = Table::new();
validation_table.insert("age.min".to_string(), Value::Integer(18));
validation_table.insert("age.max".to_string(), Value::Integer(65));
assert!(validate_input("21", "age", &validation_table).is_ok());
assert!(validate_input("18", "age", &validation_table).is_ok());
assert!(validate_input("65", "age", &validation_table).is_ok());
let result = validate_input("17", "age", &validation_table);
assert!(result.is_err());
let result = validate_input("66", "age", &validation_table);
assert!(result.is_err());
}
#[test]
fn test_validate_input_regex() {
let mut validation_table = Table::new();
validation_table.insert(
"email.regex_match".to_string(),
Value::String(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$".to_string()),
);
assert!(validate_input("user@example.com", "email", &validation_table).is_ok());
let result = validate_input("not-an-email", "email", &validation_table);
assert!(result.is_err());
}
#[test]
fn test_validate_input_multiple_rules() {
let mut validation_table = Table::new();
validation_table.insert("score.min".to_string(), Value::Integer(0));
validation_table.insert("score.max".to_string(), Value::Integer(100));
validation_table.insert(
"score.allowed_values".to_string(),
Value::Array(vec![
Value::Integer(0),
Value::Integer(25),
Value::Integer(50),
Value::Integer(75),
Value::Integer(100),
]),
);
assert!(validate_input("50", "score", &validation_table).is_ok());
let result = validate_input("51", "score", &validation_table);
assert!(result.is_err());
let result = validate_input("-1", "score", &validation_table);
assert!(result.is_err());
}
#[test]
fn test_validate_not_empty() {
let mut validation_table = Table::new();
validation_table.insert("name.not_empty".to_string(), Value::Boolean(true));
assert!(validate_input("John", "name", &validation_table).is_ok());
let result = validate_input("", "name", &validation_table);
assert!(result.is_err());
assert!(result.clone().unwrap_err().contains("cannot be empty"));
}
#[test]
fn test_validate_type() {
let mut validation_table = Table::new();
validation_table.insert("age.type".to_string(), Value::String("integer".to_string()));
validation_table.insert("price.type".to_string(), Value::String("float".to_string()));
validation_table.insert(
"active.type".to_string(),
Value::String("boolean".to_string()),
);
assert!(validate_input("25", "age", &validation_table).is_ok());
assert!(validate_input("19.99", "price", &validation_table).is_ok());
assert!(validate_input("true", "active", &validation_table).is_ok());
let result = validate_input("twenty", "age", &validation_table);
assert!(result.is_err());
let result = validate_input("nineteen", "price", &validation_table);
assert!(result.is_err());
let result = validate_input("yes", "active", &validation_table);
assert!(result.is_err());
}
#[test]
fn test_validate_length() {
let mut validation_table = Table::new();
validation_table.insert("username.length_min".to_string(), Value::Integer(3));
validation_table.insert("username.length_max".to_string(), Value::Integer(20));
validation_table.insert("code.length_min".to_string(), Value::Integer(6));
validation_table.insert("code.length_max".to_string(), Value::Integer(6));
assert!(validate_input("user123", "username", &validation_table).is_ok());
assert!(validate_input("123456", "code", &validation_table).is_ok());
let result = validate_input("us", "username", &validation_table);
assert!(result.is_err());
assert!(result
.clone()
.unwrap_err()
.contains("at least 3 characters"));
let result = validate_input("a_very_long_username_1234", "username", &validation_table);
assert!(result.is_err());
assert!(result
.clone()
.unwrap_err()
.contains("not exceed 20 characters"));
let result = validate_input("12345", "code", &validation_table);
assert!(result.is_err());
let result = validate_input("1234567", "code", &validation_table);
assert!(result.is_err());
}
}