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 `Message` 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::message::Message;
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: Message) -> 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<Message, 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<Message, 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: Message) -> 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<Message, CamelError>;
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use crate::body::Body;
60    use std::sync::Arc;
61
62    /// Stub that returns empty bodies for all read operations.
63    #[derive(Debug)]
64    struct StubRepo;
65
66    #[async_trait::async_trait]
67    impl ClaimCheckRepository for StubRepo {
68        fn name(&self) -> &str {
69            "stub"
70        }
71
72        async fn set(&self, _key: &str, _payload: Message) -> Result<(), CamelError> {
73            Ok(())
74        }
75
76        async fn get(&self, _key: &str) -> Result<Message, CamelError> {
77            Ok(Message::new(Body::Empty))
78        }
79
80        async fn get_and_remove(&self, _key: &str) -> Result<Message, CamelError> {
81            Ok(Message::new(Body::Empty))
82        }
83
84        async fn remove(&self, _key: &str) -> Result<(), CamelError> {
85            Ok(())
86        }
87
88        async fn push(&self, _key: &str, _payload: Message) -> Result<(), CamelError> {
89            Ok(())
90        }
91
92        async fn pop(&self, _key: &str) -> Result<Message, CamelError> {
93            Ok(Message::new(Body::Empty))
94        }
95    }
96
97    /// Stub compiles and can be stored as `Arc<dyn ClaimCheckRepository>`.
98    #[test]
99    fn stub_is_object_safe() {
100        let repo: Arc<dyn ClaimCheckRepository> = Arc::new(StubRepo);
101        assert_eq!(repo.name(), "stub");
102    }
103
104    #[tokio::test]
105    async fn stub_set_and_get() {
106        let repo = StubRepo;
107        repo.set("k", Message::new(Body::Text("v".into())))
108            .await
109            .unwrap();
110        let body = repo.get("k").await.unwrap();
111        assert!(body.body.is_empty());
112    }
113
114    #[tokio::test]
115    async fn stub_get_and_remove() {
116        let repo = StubRepo;
117        let body = repo.get_and_remove("k").await.unwrap();
118        assert!(body.body.is_empty());
119    }
120
121    #[tokio::test]
122    async fn stub_remove_is_ok() {
123        let repo = StubRepo;
124        repo.remove("k").await.unwrap();
125    }
126
127    #[tokio::test]
128    async fn stub_push_pop() {
129        let repo = StubRepo;
130        repo.push("k", Message::new(Body::Text("v".into())))
131            .await
132            .unwrap();
133        let body = repo.pop("k").await.unwrap();
134        assert!(body.body.is_empty());
135    }
136}