use crate::storage::Storage;
use async_trait::async_trait;
use std::sync::Arc;
use tap_msg::didcomm::PlainMessage;
pub mod agent_validator;
pub mod timestamp_validator;
pub mod uniqueness_validator;
#[derive(Debug, Clone)]
pub enum ValidationResult {
Accept,
Reject(String),
}
#[async_trait]
pub trait MessageValidator: Send + Sync {
async fn validate(&self, message: &PlainMessage) -> ValidationResult;
}
pub struct CompositeValidator {
validators: Vec<Box<dyn MessageValidator>>,
}
impl CompositeValidator {
pub fn new(validators: Vec<Box<dyn MessageValidator>>) -> Self {
Self { validators }
}
}
#[async_trait]
impl MessageValidator for CompositeValidator {
async fn validate(&self, message: &PlainMessage) -> ValidationResult {
for validator in &self.validators {
match validator.validate(message).await {
ValidationResult::Accept => continue,
ValidationResult::Reject(reason) => return ValidationResult::Reject(reason),
}
}
ValidationResult::Accept
}
}
pub struct StandardValidatorConfig {
pub max_timestamp_drift_secs: i64,
pub storage: Arc<Storage>,
}
pub async fn create_standard_validator(config: StandardValidatorConfig) -> CompositeValidator {
let validators: Vec<Box<dyn MessageValidator>> = vec![
Box::new(timestamp_validator::TimestampValidator::new(
config.max_timestamp_drift_secs,
)),
Box::new(uniqueness_validator::UniquenessValidator::new(
config.storage.clone(),
)),
Box::new(agent_validator::AgentAuthorizationValidator::new(
config.storage.clone(),
)),
];
CompositeValidator::new(validators)
}
#[cfg(test)]
mod tests {
use super::*;
struct AlwaysAcceptValidator;
#[async_trait]
impl MessageValidator for AlwaysAcceptValidator {
async fn validate(&self, _message: &PlainMessage) -> ValidationResult {
ValidationResult::Accept
}
}
struct AlwaysRejectValidator {
reason: String,
}
#[async_trait]
impl MessageValidator for AlwaysRejectValidator {
async fn validate(&self, _message: &PlainMessage) -> ValidationResult {
ValidationResult::Reject(self.reason.clone())
}
}
#[tokio::test]
async fn test_composite_validator_all_accept() {
let validators: Vec<Box<dyn MessageValidator>> = vec![
Box::new(AlwaysAcceptValidator),
Box::new(AlwaysAcceptValidator),
];
let composite = CompositeValidator::new(validators);
let message = PlainMessage::new(
"test_msg_1".to_string(),
"test_type".to_string(),
serde_json::json!({}),
"did:example:sender".to_string(),
)
.with_recipient("did:example:receiver");
match composite.validate(&message).await {
ValidationResult::Accept => {} ValidationResult::Reject(reason) => panic!("Expected accept, got reject: {}", reason),
}
}
#[tokio::test]
async fn test_composite_validator_one_reject() {
let validators: Vec<Box<dyn MessageValidator>> = vec![
Box::new(AlwaysAcceptValidator),
Box::new(AlwaysRejectValidator {
reason: "Test rejection".to_string(),
}),
Box::new(AlwaysAcceptValidator),
];
let composite = CompositeValidator::new(validators);
let message = PlainMessage::new(
"test_msg_1".to_string(),
"test_type".to_string(),
serde_json::json!({}),
"did:example:sender".to_string(),
)
.with_recipient("did:example:receiver");
match composite.validate(&message).await {
ValidationResult::Accept => panic!("Expected reject, got accept"),
ValidationResult::Reject(reason) => assert_eq!(reason, "Test rejection"),
}
}
}