Skip to main content

camel_api/
claim_check.rs

1//! ClaimCheckRepository trait — pluggable payload store for Claim Check EIP.
2//!
3//! Contract C1: `set()` and `get()` work with `Body` payloads (NOT key-only
4//! like `IdempotentRepository`). The repository owns the payload until
5//! `remove()` or `get_and_remove()` is called.
6
7use crate::CamelError;
8use crate::body::Body;
9
10/// Pluggable payload store for the Claim Check EIP.
11///
12/// Stashes large message payloads by key so the Exchange carries only a
13/// lightweight reference (the key). Supports single-value keys and LIFO
14/// stacks (for push/pop operations).
15///
16/// # Contract (C1)
17///
18/// - `set` stores or overwrites a payload by key.
19/// - `get` returns a clone of the payload without removing it. Returns
20///   `Err(CamelError::NotFound(...))` if the key does not exist.
21/// - `get_and_remove` returns and removes in one atomic step.
22/// - `remove` succeeds even if the key does not exist (no-op).
23/// - `push` appends to a LIFO stack for the given key.
24/// - `pop` removes and returns the top of the LIFO stack. Returns
25///   `Err(CamelError::NotFound(...))` if the stack is empty.
26#[async_trait::async_trait]
27pub trait ClaimCheckRepository: Send + Sync + std::fmt::Debug + 'static {
28    /// Human-readable name for diagnostics.
29    fn name(&self) -> &str;
30
31    /// Store `payload` under `key`. Overwrites any existing value.
32    async fn set(&self, key: &str, payload: Body) -> Result<(), CamelError>;
33
34    /// Retrieve payload by key without removing it.
35    ///
36    /// Returns `Err(CamelError::NotFound(...))` if the key does not exist.
37    async fn get(&self, key: &str) -> Result<Body, CamelError>;
38
39    /// Retrieve and remove in one atomic step.
40    ///
41    /// Returns `Err(CamelError::NotFound(...))` if the key does not exist.
42    async fn get_and_remove(&self, key: &str) -> Result<Body, CamelError>;
43
44    /// Remove payload by key. Succeeds even if the key does not exist.
45    async fn remove(&self, key: &str) -> Result<(), CamelError>;
46
47    /// Push payload onto a LIFO stack for `key`.
48    async fn push(&self, key: &str, payload: Body) -> Result<(), CamelError>;
49
50    /// Pop payload from a LIFO stack for `key`.
51    ///
52    /// Returns `Err(CamelError::NotFound(...))` if the stack is empty.
53    async fn pop(&self, key: &str) -> Result<Body, CamelError>;
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use std::sync::Arc;
60
61    /// Stub that returns empty bodies for all read operations.
62    #[derive(Debug)]
63    struct StubRepo;
64
65    #[async_trait::async_trait]
66    impl ClaimCheckRepository for StubRepo {
67        fn name(&self) -> &str {
68            "stub"
69        }
70
71        async fn set(&self, _key: &str, _payload: Body) -> Result<(), CamelError> {
72            Ok(())
73        }
74
75        async fn get(&self, _key: &str) -> Result<Body, CamelError> {
76            Ok(Body::Empty)
77        }
78
79        async fn get_and_remove(&self, _key: &str) -> Result<Body, CamelError> {
80            Ok(Body::Empty)
81        }
82
83        async fn remove(&self, _key: &str) -> Result<(), CamelError> {
84            Ok(())
85        }
86
87        async fn push(&self, _key: &str, _payload: Body) -> Result<(), CamelError> {
88            Ok(())
89        }
90
91        async fn pop(&self, _key: &str) -> Result<Body, CamelError> {
92            Ok(Body::Empty)
93        }
94    }
95
96    /// Stub compiles and can be stored as `Arc<dyn ClaimCheckRepository>`.
97    #[test]
98    fn stub_is_object_safe() {
99        let repo: Arc<dyn ClaimCheckRepository> = Arc::new(StubRepo);
100        assert_eq!(repo.name(), "stub");
101    }
102
103    #[tokio::test]
104    async fn stub_set_and_get() {
105        let repo = StubRepo;
106        repo.set("k", Body::Text("v".into())).await.unwrap();
107        let body = repo.get("k").await.unwrap();
108        assert!(body.is_empty());
109    }
110
111    #[tokio::test]
112    async fn stub_get_and_remove() {
113        let repo = StubRepo;
114        let body = repo.get_and_remove("k").await.unwrap();
115        assert!(body.is_empty());
116    }
117
118    #[tokio::test]
119    async fn stub_remove_is_ok() {
120        let repo = StubRepo;
121        repo.remove("k").await.unwrap();
122    }
123
124    #[tokio::test]
125    async fn stub_push_pop() {
126        let repo = StubRepo;
127        repo.push("k", Body::Text("v".into())).await.unwrap();
128        let body = repo.pop("k").await.unwrap();
129        assert!(body.is_empty());
130    }
131}