use crate::core::error::QuarantineError;
use crate::core::FileInput;
use crate::quarantine::record::{QuarantineFilter, QuarantineId, QuarantineRecord};
use async_trait::async_trait;
use std::fmt::Debug;
#[async_trait]
pub trait QuarantineStore: Send + Sync + Debug {
async fn store(
&self,
input: &FileInput,
record: QuarantineRecord,
) -> Result<QuarantineId, QuarantineError>;
async fn retrieve(
&self,
id: &QuarantineId,
) -> Result<(Vec<u8>, QuarantineRecord), QuarantineError>;
async fn delete(&self, id: &QuarantineId) -> Result<(), QuarantineError>;
async fn list(
&self,
filter: QuarantineFilter,
) -> Result<Vec<QuarantineRecord>, QuarantineError>;
async fn count(&self) -> Result<usize, QuarantineError> {
let records = self.list(QuarantineFilter::new()).await?;
Ok(records.len())
}
async fn contains_hash(&self, hash: &str) -> Result<bool, QuarantineError> {
let filter = QuarantineFilter::new().with_file_hash(hash);
let records = self.list(filter).await?;
Ok(!records.is_empty())
}
async fn get_record(&self, id: &QuarantineId) -> Result<QuarantineRecord, QuarantineError> {
let (_, record) = self.retrieve(id).await?;
Ok(record)
}
async fn cleanup_expired(&self) -> Result<usize, QuarantineError> {
let filter = QuarantineFilter::new().with_include_expired(true);
let records = self.list(filter).await?;
let mut deleted = 0;
for record in records {
if record.is_expired() {
if self.delete(&record.id).await.is_ok() {
deleted += 1;
}
}
}
Ok(deleted)
}
}
#[derive(Debug, Default)]
pub struct NoOpQuarantineStore;
impl NoOpQuarantineStore {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl QuarantineStore for NoOpQuarantineStore {
async fn store(
&self,
_input: &FileInput,
record: QuarantineRecord,
) -> Result<QuarantineId, QuarantineError> {
tracing::debug!(id = %record.id, "NoOp quarantine store: file not actually stored");
Ok(record.id)
}
async fn retrieve(
&self,
id: &QuarantineId,
) -> Result<(Vec<u8>, QuarantineRecord), QuarantineError> {
Err(QuarantineError::NotFound { id: id.to_string() })
}
async fn delete(&self, _id: &QuarantineId) -> Result<(), QuarantineError> {
Ok(())
}
async fn list(
&self,
_filter: QuarantineFilter,
) -> Result<Vec<QuarantineRecord>, QuarantineError> {
Ok(Vec::new())
}
async fn count(&self) -> Result<usize, QuarantineError> {
Ok(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_noop_store() {
let store = NoOpQuarantineStore::new();
assert_eq!(store.count().await.unwrap(), 0);
let result = store.retrieve(&QuarantineId::new()).await;
assert!(matches!(result, Err(QuarantineError::NotFound { .. })));
let records = store.list(QuarantineFilter::new()).await.unwrap();
assert!(records.is_empty());
}
}