Skip to main content

eventbus_core/contract/
delivery.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::error::EventBusError;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum DeliveryOutcome {
9    Acked,
10    Nacked,
11    Retried,
12    DeadLetter,
13}
14
15/// Backend-supplied delivery facts for an in-flight message.
16///
17/// Backends emit this from `read_new` / `reclaim_idle`; the bus layer pairs
18/// it with the subscription's `max_retry` to construct the full
19/// [`DeliveryState`] that handlers see via [`DeliveryInspector::state`].
20///
21/// Splitting the type makes the protocol explicit at the type level: a
22/// backend can't accidentally fabricate `max_attempt`, and the bus can't
23/// accidentally forget to set it.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct PartialDeliveryState {
26    pub attempt: u32,
27    pub first_received: DateTime<Utc>,
28    pub last_received: DateTime<Utc>,
29    pub redelivered: bool,
30}
31
32impl PartialDeliveryState {
33    /// Pair with the subscription-level retry budget to produce the full
34    /// [`DeliveryState`] visible to handlers.
35    #[must_use]
36    pub fn with_max_attempt(self, max_attempt: u32) -> DeliveryState {
37        DeliveryState {
38            attempt: self.attempt,
39            max_attempt,
40            first_received: self.first_received,
41            last_received: self.last_received,
42            redelivered: self.redelivered,
43        }
44    }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct DeliveryState {
49    pub attempt: u32,
50    pub max_attempt: u32,
51    pub first_received: DateTime<Utc>,
52    pub last_received: DateTime<Utc>,
53    pub redelivered: bool,
54}
55
56pub trait DeliveryInspector: Send + Sync {
57    fn state(&self) -> crate::BoxFuture<'_, Result<DeliveryState, EventBusError>>;
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn delivery_outcome_uses_go_wire_names() {
66        assert_eq!(
67            serde_json::to_string(&DeliveryOutcome::Acked).unwrap(),
68            "\"acked\""
69        );
70        assert_eq!(
71            serde_json::to_string(&DeliveryOutcome::Nacked).unwrap(),
72            "\"nacked\""
73        );
74        assert_eq!(
75            serde_json::to_string(&DeliveryOutcome::Retried).unwrap(),
76            "\"retried\""
77        );
78        assert_eq!(
79            serde_json::to_string(&DeliveryOutcome::DeadLetter).unwrap(),
80            "\"dead-letter\"",
81        );
82
83        assert_eq!(
84            serde_json::from_str::<DeliveryOutcome>("\"dead-letter\"").unwrap(),
85            DeliveryOutcome::DeadLetter,
86        );
87    }
88}