use async_trait::async_trait;
use crate::context::ReceiveContext;
use crate::decision::DeliveryDecision;
#[derive(Debug, Clone, PartialEq)]
pub enum StageOutcome {
Continue,
Decide(DeliveryDecision),
}
#[async_trait]
pub trait Stage: Send + Sync {
fn name(&self) -> &str;
async fn evaluate(&self, ctx: &mut ReceiveContext) -> StageOutcome;
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr};
use super::*;
struct NoopStage;
#[async_trait]
impl Stage for NoopStage {
fn name(&self) -> &str {
"noop"
}
async fn evaluate(&self, _ctx: &mut ReceiveContext) -> StageOutcome {
StageOutcome::Continue
}
}
struct AlwaysRejectStage;
#[async_trait]
impl Stage for AlwaysRejectStage {
fn name(&self) -> &str {
"always_reject"
}
async fn evaluate(&self, _ctx: &mut ReceiveContext) -> StageOutcome {
StageOutcome::Decide(DeliveryDecision::Reject {
code: 550,
message: "5.7.1 nope".into(),
})
}
}
fn ctx() -> ReceiveContext {
ReceiveContext::new(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
"ehlo",
"a@x",
"b@x",
b"body".to_vec(),
"mx",
)
}
#[tokio::test]
async fn noop_stage_returns_continue() {
let s = NoopStage;
let mut c = ctx();
assert_eq!(s.evaluate(&mut c).await, StageOutcome::Continue);
}
#[tokio::test]
async fn always_reject_returns_decide() {
let s = AlwaysRejectStage;
let mut c = ctx();
match s.evaluate(&mut c).await {
StageOutcome::Decide(DeliveryDecision::Reject { code, .. }) => {
assert_eq!(code, 550);
}
other => panic!("expected reject, got {other:?}"),
}
}
#[test]
fn stage_name_is_callable_on_trait_object() {
let s: Box<dyn Stage> = Box::new(NoopStage);
assert_eq!(s.name(), "noop");
}
}