nexo_memory_snapshot/
path_resolver.rs1use std::path::PathBuf;
22
23pub trait PathResolver: Send + Sync + 'static {
27 fn memdir(&self, agent_id: &str, tenant: &str) -> PathBuf;
31
32 fn sqlite_dir(&self, agent_id: &str, tenant: &str) -> PathBuf;
37}
38
39#[derive(Debug, Clone)]
44pub struct DefaultPathResolver {
45 memdir_root: PathBuf,
46 sqlite_root: PathBuf,
47}
48
49impl DefaultPathResolver {
50 pub fn new(memdir_root: PathBuf, sqlite_root: PathBuf) -> Self {
51 Self {
52 memdir_root,
53 sqlite_root,
54 }
55 }
56}
57
58impl PathResolver for DefaultPathResolver {
59 fn memdir(&self, agent_id: &str, _tenant: &str) -> PathBuf {
60 self.memdir_root.join(agent_id)
61 }
62
63 fn sqlite_dir(&self, agent_id: &str, _tenant: &str) -> PathBuf {
64 self.sqlite_root.join(agent_id)
65 }
66}
67
68pub struct ClosureResolver<F1, F2>
73where
74 F1: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
75 F2: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
76{
77 memdir_fn: F1,
78 sqlite_fn: F2,
79}
80
81impl<F1, F2> ClosureResolver<F1, F2>
82where
83 F1: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
84 F2: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
85{
86 pub fn new(memdir_fn: F1, sqlite_fn: F2) -> Self {
87 Self {
88 memdir_fn,
89 sqlite_fn,
90 }
91 }
92}
93
94impl<F1, F2> PathResolver for ClosureResolver<F1, F2>
95where
96 F1: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
97 F2: Fn(&str, &str) -> PathBuf + Send + Sync + 'static,
98{
99 fn memdir(&self, agent_id: &str, tenant: &str) -> PathBuf {
100 (self.memdir_fn)(agent_id, tenant)
101 }
102
103 fn sqlite_dir(&self, agent_id: &str, tenant: &str) -> PathBuf {
104 (self.sqlite_fn)(agent_id, tenant)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use std::sync::Arc;
112
113 #[test]
114 fn default_resolver_joins_agent_id_under_root() {
115 let r = DefaultPathResolver::new(
116 PathBuf::from("/var/lib/memdir"),
117 PathBuf::from("/var/lib/sqlite"),
118 );
119 assert_eq!(
120 r.memdir("ana", "default"),
121 PathBuf::from("/var/lib/memdir/ana")
122 );
123 assert_eq!(
124 r.sqlite_dir("ana", "default"),
125 PathBuf::from("/var/lib/sqlite/ana")
126 );
127 }
128
129 #[test]
130 fn default_resolver_ignores_tenant_for_paths() {
131 let r = DefaultPathResolver::new(PathBuf::from("/x"), PathBuf::from("/y"));
134 assert_eq!(r.memdir("ana", "acme"), r.memdir("ana", "globex"));
135 }
136
137 #[test]
138 fn closure_resolver_routes_per_tenant() {
139 let r = ClosureResolver::new(
140 |agent: &str, tenant: &str| PathBuf::from(format!("/var/{tenant}/memdir/{agent}")),
141 |agent: &str, tenant: &str| PathBuf::from(format!("/var/{tenant}/sqlite/{agent}")),
142 );
143 assert_eq!(
144 r.memdir("ana", "acme"),
145 PathBuf::from("/var/acme/memdir/ana")
146 );
147 assert_eq!(
148 r.memdir("ana", "globex"),
149 PathBuf::from("/var/globex/memdir/ana")
150 );
151 }
152
153 #[test]
154 fn dyn_path_resolver_can_be_held_as_arc() {
155 let r: Arc<dyn PathResolver> = Arc::new(DefaultPathResolver::new(
156 PathBuf::from("/a"),
157 PathBuf::from("/b"),
158 ));
159 assert_eq!(r.memdir("x", "default"), PathBuf::from("/a/x"));
160 }
161}