use super::errors::{ValidationError, ValidationResult};
use std::future::Future;
use std::pin::Pin;
pub type ExistenceCheckFn =
Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync>;
pub struct ExistsValidator {
field_name: String,
table_name: String,
check_fn: ExistenceCheckFn,
}
impl ExistsValidator {
pub fn new(
field_name: impl Into<String>,
table_name: impl Into<String>,
check_fn: ExistenceCheckFn,
) -> Self {
Self {
field_name: field_name.into(),
table_name: table_name.into(),
check_fn,
}
}
pub async fn validate_async(&self, value: impl Into<String>) -> ValidationResult<()> {
let value_str = value.into();
let exists = (self.check_fn)(value_str.clone()).await;
if exists {
Ok(())
} else {
Err(ValidationError::ForeignKeyNotFound {
field: self.field_name.clone(),
value: value_str,
table: self.table_name.clone(),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
#[tokio::test]
async fn test_exists_validator_value_exists() {
let existing_ids = Arc::new(Mutex::new(HashSet::from([1, 2, 3])));
let validator = ExistsValidator::new(
"user_id",
"users",
Box::new(move |value| {
let existing = existing_ids.clone();
Box::pin(async move {
if let Ok(id) = value.parse::<i32>() {
let ids = existing.lock().unwrap();
ids.contains(&id)
} else {
false
}
})
}),
);
let result = validator.validate_async("2").await;
assert!(result.is_ok());
let result = validator.validate_async("99").await;
assert!(result.is_err());
if let Err(ValidationError::ForeignKeyNotFound {
field,
value,
table,
}) = result
{
assert_eq!(field, "user_id");
assert_eq!(value, "99");
assert_eq!(table, "users");
} else {
panic!("Expected ForeignKeyNotFound error");
}
}
#[tokio::test]
async fn test_exists_validator_empty_database() {
let validator = ExistsValidator::new(
"product_id",
"products",
Box::new(|_value| Box::pin(async move { false })),
);
let result = validator.validate_async("1").await;
assert!(result.is_err());
if let Err(ValidationError::ForeignKeyNotFound {
field,
value,
table,
}) = result
{
assert_eq!(field, "product_id");
assert_eq!(value, "1");
assert_eq!(table, "products");
}
}
#[tokio::test]
async fn test_exists_validator_invalid_value() {
let validator = ExistsValidator::new(
"user_id",
"users",
Box::new(|value| {
Box::pin(async move {
value.parse::<i32>().is_ok()
})
}),
);
let result = validator.validate_async("abc").await;
assert!(result.is_err());
}
}