Skip to main content

puerto_core/events/
handler.rs

1use async_trait::async_trait;
2
3use super::EventEnvelope;
4
5/// The payload is not the shape the handler reads โ€” retrying cannot fix it.
6pub const REASON_PAYLOAD_INVALID: &str = "payload_invalid";
7/// The handler kept answering `Retry` until the delivery cap.
8pub const REASON_MAX_DELIVERIES_EXCEEDED: &str = "max_deliveries_exceeded";
9/// The handler judged the failure permanent.
10pub const REASON_PERMANENT_ERROR: &str = "permanent_error";
11
12/// A consumer: what the worker hands every event it subscribed to. Delivery is at-least-once, so
13/// a handler must be idempotent โ€” the same `event.id` can arrive twice (ADR-0018 ยง 6).
14#[async_trait]
15pub trait EventHandler: Send + Sync {
16    async fn handle(&self, event: &EventEnvelope) -> HandlerOutcome;
17}
18
19/// What a handler answers. The worker turns it into a [`super::delivery::Delivery`] with
20/// [`super::delivery::decide`].
21#[non_exhaustive]
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum HandlerOutcome {
24    /// Done: never deliver it again.
25    Ack,
26    /// Failed for now: deliver it again later, until the cap.
27    Retry,
28    /// Never deliver it again, and keep a dead letter saying why.
29    DeadLetter {
30        /// One of the `REASON_*` codes.
31        reason: &'static str,
32        detail: String,
33    },
34}
35
36impl HandlerOutcome {
37    pub fn dead_letter(reason: &'static str, detail: impl Into<String>) -> Self {
38        Self::DeadLetter {
39            reason,
40            detail: detail.into(),
41        }
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn should_keep_reason_codes_stable_when_stored_in_dead_letters() {
51        assert_eq!(REASON_PAYLOAD_INVALID, "payload_invalid");
52        assert_eq!(REASON_MAX_DELIVERIES_EXCEEDED, "max_deliveries_exceeded");
53        assert_eq!(REASON_PERMANENT_ERROR, "permanent_error");
54    }
55
56    #[test]
57    fn should_carry_reason_and_detail_when_dead_lettered() {
58        assert_eq!(
59            HandlerOutcome::dead_letter(REASON_PERMANENT_ERROR, "card declined"),
60            HandlerOutcome::DeadLetter {
61                reason: "permanent_error",
62                detail: "card declined".into()
63            }
64        );
65    }
66}