use crate::guardian::error::{GuardianError, Result};
use redb::{Database, ReadableDatabase, ReadableTable, TableDefinition};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
const REGISTRY_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("store_registry");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StoreSpec {
pub kind: String,
#[serde(default)]
pub replicate: bool,
#[serde(default)]
pub local_only: bool,
#[serde(default)]
pub read_only: bool,
#[serde(default)]
pub acl_address: Option<String>,
#[serde(default)]
pub doc_ticket: Option<String>,
}
#[derive(Debug)]
pub struct StoreRegistry {
db: Database,
}
unsafe impl Send for StoreRegistry {}
unsafe impl Sync for StoreRegistry {}
impl StoreRegistry {
pub fn open(path: PathBuf) -> Result<Self> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| GuardianError::Other(format!("store registry dir: {e}")))?;
}
let db = Database::create(&path)
.map_err(|e| GuardianError::Other(format!("store registry open: {e}")))?;
{
let w = db
.begin_write()
.map_err(|e| GuardianError::Other(format!("store registry txn: {e}")))?;
{
let _ = w
.open_table(REGISTRY_TABLE)
.map_err(|e| GuardianError::Other(format!("store registry table: {e}")))?;
}
w.commit()
.map_err(|e| GuardianError::Other(format!("store registry commit: {e}")))?;
}
Ok(Self { db })
}
pub fn put(&self, name: &str, spec: &StoreSpec) -> Result<()> {
let bytes = serde_json::to_vec(spec)
.map_err(|e| GuardianError::Other(format!("store spec encode: {e}")))?;
let w = self
.db
.begin_write()
.map_err(|e| GuardianError::Other(format!("store registry txn: {e}")))?;
{
let mut t = w
.open_table(REGISTRY_TABLE)
.map_err(|e| GuardianError::Other(format!("store registry table: {e}")))?;
t.insert(name, &bytes[..])
.map_err(|e| GuardianError::Other(format!("store registry insert: {e}")))?;
}
w.commit()
.map_err(|e| GuardianError::Other(format!("store registry commit: {e}")))?;
Ok(())
}
pub fn remove(&self, name: &str) -> Result<()> {
let w = self
.db
.begin_write()
.map_err(|e| GuardianError::Other(format!("store registry txn: {e}")))?;
{
let mut t = w
.open_table(REGISTRY_TABLE)
.map_err(|e| GuardianError::Other(format!("store registry table: {e}")))?;
t.remove(name)
.map_err(|e| GuardianError::Other(format!("store registry remove: {e}")))?;
}
w.commit()
.map_err(|e| GuardianError::Other(format!("store registry commit: {e}")))?;
Ok(())
}
pub fn contains(&self, name: &str) -> Result<bool> {
Ok(self.get(name)?.is_some())
}
pub fn get(&self, name: &str) -> Result<Option<StoreSpec>> {
let r = self
.db
.begin_read()
.map_err(|e| GuardianError::Other(format!("store registry read: {e}")))?;
let t = match r.open_table(REGISTRY_TABLE) {
Ok(t) => t,
Err(_) => return Ok(None),
};
match t.get(name) {
Ok(Some(v)) => Ok(serde_json::from_slice(v.value()).ok()),
Ok(None) => Ok(None),
Err(e) => Err(GuardianError::Other(format!("store registry get: {e}"))),
}
}
pub fn list(&self) -> Result<Vec<(String, StoreSpec)>> {
let r = self
.db
.begin_read()
.map_err(|e| GuardianError::Other(format!("store registry read: {e}")))?;
let t = match r.open_table(REGISTRY_TABLE) {
Ok(t) => t,
Err(_) => return Ok(Vec::new()),
};
let mut out = Vec::new();
for item in t
.iter()
.map_err(|e| GuardianError::Other(format!("store registry iter: {e}")))?
{
let (k, v) =
item.map_err(|e| GuardianError::Other(format!("store registry entry: {e}")))?;
if let Ok(spec) = serde_json::from_slice::<StoreSpec>(v.value()) {
out.push((k.value().to_string(), spec));
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_roundtrips_and_persists() {
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("store_registry");
let spec = StoreSpec {
kind: "keyvalue".into(),
replicate: true,
local_only: false,
read_only: false,
acl_address: None,
doc_ticket: None,
};
{
let reg = StoreRegistry::open(path.clone()).unwrap();
reg.put("settings", &spec).unwrap();
assert!(reg.contains("settings").unwrap());
assert_eq!(reg.list().unwrap().len(), 1);
}
let reg2 = StoreRegistry::open(path).unwrap();
assert_eq!(reg2.get("settings").unwrap().as_ref(), Some(&spec));
reg2.remove("settings").unwrap();
assert!(reg2.list().unwrap().is_empty());
}
}