use camel_api::CamelError;
use camel_api::ClaimCheckRepository;
use camel_api::body::Body;
use dashmap::DashMap;
use std::collections::VecDeque;
#[derive(Debug)]
pub struct MemoryClaimCheckRepository {
name: String,
keys: DashMap<String, Body>,
stacks: DashMap<String, VecDeque<Body>>,
}
impl MemoryClaimCheckRepository {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
keys: DashMap::new(),
stacks: DashMap::new(),
}
}
}
#[async_trait::async_trait]
impl ClaimCheckRepository for MemoryClaimCheckRepository {
fn name(&self) -> &str {
&self.name
}
async fn set(&self, key: &str, payload: Body) -> Result<(), CamelError> {
self.keys.insert(key.to_string(), payload);
Ok(())
}
async fn get(&self, key: &str) -> Result<Body, CamelError> {
self.keys
.get(key)
.map(|r| r.clone())
.ok_or_else(|| CamelError::RouteError(format!("Claim check key not found: {key}")))
}
async fn get_and_remove(&self, key: &str) -> Result<Body, CamelError> {
self.keys
.remove(key)
.map(|(_, v)| v)
.ok_or_else(|| CamelError::RouteError(format!("Claim check key not found: {key}")))
}
async fn remove(&self, key: &str) -> Result<(), CamelError> {
self.keys.remove(key);
Ok(())
}
async fn push(&self, key: &str, payload: Body) -> Result<(), CamelError> {
self.stacks
.entry(key.to_string())
.or_default()
.push_back(payload);
Ok(())
}
async fn pop(&self, key: &str) -> Result<Body, CamelError> {
self.stacks
.entry(key.to_string())
.or_default()
.pop_back()
.ok_or_else(|| {
CamelError::RouteError(format!("Claim check stack empty for key: {key}"))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn new_repo() -> MemoryClaimCheckRepository {
MemoryClaimCheckRepository::new("test")
}
#[tokio::test]
async fn set_then_get() {
let repo = new_repo();
let payload = Body::Text("hello".to_string());
repo.set("key-1", payload.clone()).await.unwrap();
let result = repo.get("key-1").await.unwrap();
assert_eq!(result, payload);
}
#[tokio::test]
async fn get_and_remove_removes() {
let repo = new_repo();
let payload = Body::Text("will-be-removed".to_string());
repo.set("key-1", payload.clone()).await.unwrap();
let retrieved = repo.get_and_remove("key-1").await.unwrap();
assert_eq!(retrieved, payload);
let err = repo.get("key-1").await.unwrap_err();
assert!(
matches!(&err, CamelError::RouteError(msg) if msg.contains("not found")),
"expected RouteError with 'not found', got: {err:?}"
);
}
#[tokio::test]
async fn push_pop_lifo() {
let repo = new_repo();
let first = Body::Text("first".to_string());
let second = Body::Text("second".to_string());
repo.push("stack-1", first).await.unwrap();
repo.push("stack-1", second.clone()).await.unwrap();
let popped = repo.pop("stack-1").await.unwrap();
assert_eq!(popped, second);
let popped = repo.pop("stack-1").await.unwrap();
assert_eq!(popped, Body::Text("first".to_string()));
}
#[tokio::test]
async fn get_missing_returns_err() {
let repo = new_repo();
let err = repo.get("nonexistent").await.unwrap_err();
assert!(
matches!(&err, CamelError::RouteError(msg) if msg.contains("not found")),
"expected RouteError with 'not found', got: {err:?}"
);
}
#[tokio::test]
async fn pop_empty_stack_returns_err() {
let repo = new_repo();
let err = repo.pop("empty-stack").await.unwrap_err();
assert!(
matches!(&err, CamelError::RouteError(msg) if msg.contains("empty")),
"expected RouteError with 'empty', got: {err:?}"
);
}
#[tokio::test]
async fn remove_nonexistent_is_ok() {
let repo = new_repo();
repo.remove("never-set").await.unwrap();
}
#[tokio::test]
async fn set_overwrites() {
let repo = new_repo();
repo.set("k", Body::Text("old".into())).await.unwrap();
repo.set("k", Body::Text("new".into())).await.unwrap();
let body = repo.get("k").await.unwrap();
assert_eq!(body, Body::Text("new".into()));
}
#[test]
fn name_returns_configured_name() {
let repo = MemoryClaimCheckRepository::new("my-check");
assert_eq!(repo.name(), "my-check");
}
}