Skip to main content

liminal/durability/
receipt.rs

1use std::fmt;
2
3/// Opaque processing outcome supplied by a consumer for dedup completion.
4#[derive(Clone, PartialEq, Eq)]
5pub struct ProcessingReceipt(Vec<u8>);
6
7impl ProcessingReceipt {
8    /// Records the durable position established by authoritative reconciliation.
9    #[must_use]
10    pub fn for_position(position: u64) -> Self {
11        let mut bytes = b"LPR1".to_vec();
12        bytes.extend_from_slice(&position.to_be_bytes());
13        Self(bytes)
14    }
15
16    /// Reads only the exact versioned position-receipt shape; other receipts stay opaque.
17    #[must_use]
18    pub fn durable_position(&self) -> Option<u64> {
19        if self.0.len() != 12 || !self.0.starts_with(b"LPR1") {
20            return None;
21        }
22        let bytes: [u8; 8] = self.0.get(4..)?.try_into().ok()?;
23        Some(u64::from_be_bytes(bytes))
24    }
25
26    /// Wraps consumer-defined receipt bytes without interpretation.
27    #[must_use]
28    pub const fn new(bytes: Vec<u8>) -> Self {
29        Self(bytes)
30    }
31
32    /// Returns the opaque receipt bytes.
33    #[must_use]
34    pub fn as_bytes(&self) -> &[u8] {
35        &self.0
36    }
37
38    /// Consumes the receipt and returns its opaque bytes.
39    #[must_use]
40    pub fn into_bytes(self) -> Vec<u8> {
41        self.0
42    }
43}
44
45impl fmt::Debug for ProcessingReceipt {
46    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(formatter, "ProcessingReceipt({} bytes)", self.0.len())
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::ProcessingReceipt;
54
55    #[test]
56    fn receipt_debug_reports_length_not_contents() {
57        let receipt = ProcessingReceipt::new(b"secret receipt".to_vec());
58
59        let debug = format!("{receipt:?}");
60
61        assert_eq!(debug, "ProcessingReceipt(14 bytes)");
62        assert!(!debug.contains("secret"));
63    }
64
65    #[test]
66    fn receipts_compare_by_bytes() {
67        let left = ProcessingReceipt::new(vec![1, 2, 3]);
68        let right = ProcessingReceipt::new(vec![1, 2, 3]);
69
70        assert_eq!(left, right);
71    }
72}