use async_trait::async_trait;
use serde_json::Value;
use super::storage::StorageError;
#[async_trait]
pub trait ConfigStore: Send + Sync {
async fn get(&self, namespace: &str, id: &str) -> Result<Option<Value>, StorageError>;
async fn list(
&self,
namespace: &str,
offset: usize,
limit: usize,
) -> Result<Vec<(String, Value)>, StorageError>;
async fn put(&self, namespace: &str, id: &str, value: &Value) -> Result<(), StorageError>;
async fn delete(&self, namespace: &str, id: &str) -> Result<(), StorageError>;
async fn exists(&self, namespace: &str, id: &str) -> Result<bool, StorageError> {
Ok(self.get(namespace, id).await?.is_some())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfigChangeKind {
Put,
Delete,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ConfigChangeEvent {
pub namespace: String,
pub id: String,
pub kind: ConfigChangeKind,
}
#[async_trait]
pub trait ConfigChangeSubscriber: Send {
async fn next(&mut self) -> Result<ConfigChangeEvent, StorageError>;
}
#[async_trait]
pub trait ConfigChangeNotifier: Send + Sync {
async fn subscribe(&self) -> Result<Box<dyn ConfigChangeSubscriber>, StorageError>;
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use super::*;
#[derive(Debug, Default)]
struct MemoryConfigStore {
data: RwLock<HashMap<String, HashMap<String, Value>>>,
}
#[async_trait]
impl ConfigStore for MemoryConfigStore {
async fn get(&self, namespace: &str, id: &str) -> Result<Option<Value>, StorageError> {
let data = self.data.read().await;
Ok(data.get(namespace).and_then(|ns| ns.get(id)).cloned())
}
async fn list(
&self,
namespace: &str,
offset: usize,
limit: usize,
) -> Result<Vec<(String, Value)>, StorageError> {
let data = self.data.read().await;
let Some(namespace_data) = data.get(namespace) else {
return Ok(Vec::new());
};
let mut items: Vec<_> = namespace_data
.iter()
.map(|(id, value)| (id.clone(), value.clone()))
.collect();
items.sort_by(|left, right| left.0.cmp(&right.0));
Ok(items.into_iter().skip(offset).take(limit).collect())
}
async fn put(&self, namespace: &str, id: &str, value: &Value) -> Result<(), StorageError> {
let mut data = self.data.write().await;
data.entry(namespace.to_string())
.or_default()
.insert(id.to_string(), value.clone());
Ok(())
}
async fn delete(&self, namespace: &str, id: &str) -> Result<(), StorageError> {
let mut data = self.data.write().await;
if let Some(namespace_data) = data.get_mut(namespace) {
namespace_data.remove(id);
}
Ok(())
}
}
#[tokio::test]
async fn config_store_round_trip() {
let store: Arc<dyn ConfigStore> = Arc::new(MemoryConfigStore::default());
let value = serde_json::json!({"id": "alpha", "label": "first"});
store.put("tests", "alpha", &value).await.unwrap();
assert_eq!(store.get("tests", "alpha").await.unwrap(), Some(value));
}
#[tokio::test]
async fn config_store_lists_sorted_entries() {
let store: Arc<dyn ConfigStore> = Arc::new(MemoryConfigStore::default());
store
.put(
"tests",
"bravo",
&serde_json::json!({"id": "bravo", "label": "second"}),
)
.await
.unwrap();
store
.put(
"tests",
"alpha",
&serde_json::json!({"id": "alpha", "label": "first"}),
)
.await
.unwrap();
let items = store.list("tests", 0, 10).await.unwrap();
assert_eq!(items[0].0, "alpha");
assert_eq!(items[1].0, "bravo");
}
}