use std::path::PathBuf;
pub trait PathResolver: Send + Sync + 'static {
fn memdir(&self, agent_id: &str, tenant: &str) -> PathBuf;
fn sqlite_dir(&self, agent_id: &str, tenant: &str) -> PathBuf;
}
#[derive(Debug, Clone)]
pub struct DefaultPathResolver {
memdir_root: PathBuf,
sqlite_root: PathBuf,
}
impl DefaultPathResolver {
pub fn new(memdir_root: PathBuf, sqlite_root: PathBuf) -> Self {
Self {
memdir_root,
sqlite_root,
}
}
}
impl PathResolver for DefaultPathResolver {
fn memdir(&self, agent_id: &str, _tenant: &str) -> PathBuf {
self.memdir_root.join(agent_id)
}
fn sqlite_dir(&self, agent_id: &str, _tenant: &str) -> PathBuf {
self.sqlite_root.join(agent_id)
}
}
pub struct ClosureResolver<F1, F2>
where
F1: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
F2: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
{
memdir_fn: F1,
sqlite_fn: F2,
}
impl<F1, F2> ClosureResolver<F1, F2>
where
F1: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
F2: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
{
pub fn new(memdir_fn: F1, sqlite_fn: F2) -> Self {
Self {
memdir_fn,
sqlite_fn,
}
}
}
impl<F1, F2> PathResolver for ClosureResolver<F1, F2>
where
F1: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
F2: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
{
fn memdir(&self, agent_id: &str, tenant: &str) -> PathBuf {
(self.memdir_fn)(agent_id, tenant)
}
fn sqlite_dir(&self, agent_id: &str, tenant: &str) -> PathBuf {
(self.sqlite_fn)(agent_id, tenant)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn default_resolver_joins_agent_id_under_root() {
let r = DefaultPathResolver::new(
PathBuf::from("/var/lib/memdir"),
PathBuf::from("/var/lib/sqlite"),
);
assert_eq!(
r.memdir("ana", "default"),
PathBuf::from("/var/lib/memdir/ana")
);
assert_eq!(
r.sqlite_dir("ana", "default"),
PathBuf::from("/var/lib/sqlite/ana")
);
}
#[test]
fn default_resolver_ignores_tenant_for_paths() {
let r = DefaultPathResolver::new(PathBuf::from("/x"), PathBuf::from("/y"));
assert_eq!(r.memdir("ana", "acme"), r.memdir("ana", "globex"));
}
#[test]
fn closure_resolver_routes_per_tenant() {
let r = ClosureResolver::new(
|agent: &str, tenant: &str| PathBuf::from(format!("/var/{tenant}/memdir/{agent}")),
|agent: &str, tenant: &str| PathBuf::from(format!("/var/{tenant}/sqlite/{agent}")),
);
assert_eq!(
r.memdir("ana", "acme"),
PathBuf::from("/var/acme/memdir/ana")
);
assert_eq!(
r.memdir("ana", "globex"),
PathBuf::from("/var/globex/memdir/ana")
);
}
#[test]
fn dyn_path_resolver_can_be_held_as_arc() {
let r: Arc<dyn PathResolver> = Arc::new(DefaultPathResolver::new(
PathBuf::from("/a"),
PathBuf::from("/b"),
));
assert_eq!(r.memdir("x", "default"), PathBuf::from("/a/x"));
}
}