1use std::sync::{Arc, Mutex};
5
6use crate::{Error, Path, PathPattern, Reader, Record, Value, Writer};
7
8pub struct ReadOnly<S>(S);
14
15impl<S> ReadOnly<S> {
16 pub fn new(inner: S) -> Self {
18 Self(inner)
19 }
20
21 pub fn into_inner(self) -> S {
23 self.0
24 }
25}
26
27impl<S: Reader> Reader for ReadOnly<S> {
28 fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
29 self.0.read(from)
30 }
31
32 fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
33 self.0.read_children(from)
34 }
35}
36
37impl<S: Reader> Writer for ReadOnly<S> {
38 fn write(&mut self, to: &Path, _data: Record) -> Result<Path, Error> {
39 Err(Error::permission_denied(format!(
40 "store is read-only (write to {})",
41 to
42 )))
43 }
44}
45
46pub struct Cascade<A, B> {
53 primary: A,
54 fallback: B,
55}
56
57impl<A, B> Cascade<A, B> {
58 pub fn new(primary: A, fallback: B) -> Self {
60 Self { primary, fallback }
61 }
62
63 pub fn into_inner(self) -> (A, B) {
65 (self.primary, self.fallback)
66 }
67}
68
69impl<A: Reader, B: Reader> Reader for Cascade<A, B> {
70 fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
71 match self.primary.read(from)? {
72 Some(record) => Ok(Some(record)),
73 None => self.fallback.read(from),
74 }
75 }
76
77 fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
78 match self.primary.read_children(from)? {
79 Some(children) => Ok(Some(children)),
80 None => self.fallback.read_children(from),
81 }
82 }
83}
84
85impl<A: Writer, B: Send + Sync> Writer for Cascade<A, B> {
86 fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
87 self.primary.write(to, data)
88 }
89}
90
91pub struct Shared<S> {
99 inner: Arc<Mutex<S>>,
100}
101
102impl<S> Shared<S> {
103 pub fn new(inner: S) -> Self {
105 Self {
106 inner: Arc::new(Mutex::new(inner)),
107 }
108 }
109
110 pub fn lock(&self) -> std::sync::MutexGuard<'_, S> {
112 self.inner
113 .lock()
114 .unwrap_or_else(|poisoned| poisoned.into_inner())
115 }
116}
117
118impl<S> Clone for Shared<S> {
119 fn clone(&self) -> Self {
120 Self {
121 inner: self.inner.clone(),
122 }
123 }
124}
125
126impl<S: Reader> Reader for Shared<S> {
127 fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
128 self.lock().read(from)
129 }
130
131 fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
132 self.lock().read_children(from)
133 }
134}
135
136impl<S: Writer> Writer for Shared<S> {
137 fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
138 self.lock().write(to, data)
139 }
140}
141
142pub struct Masked<S> {
154 inner: S,
155 patterns: Vec<PathPattern>,
156 mask: Value,
157}
158
159impl<S> Masked<S> {
160 pub fn new(inner: S, patterns: Vec<PathPattern>) -> Self {
162 Self::with_mask(inner, patterns, Value::from("[masked]"))
163 }
164
165 pub fn with_mask(inner: S, patterns: Vec<PathPattern>, mask: Value) -> Self {
167 Self {
168 inner,
169 patterns,
170 mask,
171 }
172 }
173
174 pub fn into_inner(self) -> S {
176 self.inner
177 }
178
179 fn is_masked(&self, path: &Path) -> bool {
180 self.patterns.iter().any(|pattern| pattern.matches(path))
181 }
182}
183
184impl<S: Reader> Reader for Masked<S> {
185 fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
186 if self.is_masked(from) {
187 return Ok(self
189 .inner
190 .read(from)?
191 .map(|_| Record::parsed(self.mask.clone())));
192 }
193 self.inner.read(from)
194 }
195
196 fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
197 self.inner.read_children(from)
200 }
201}
202
203impl<S: Writer> Writer for Masked<S> {
204 fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
205 self.inner.write(to, data)
206 }
207}
208
209pub struct Rooted<S> {
216 root: Path,
217 inner: S,
218}
219
220impl<S> Rooted<S> {
221 pub fn new(root: Path, inner: S) -> Self {
223 Self { root, inner }
224 }
225
226 pub fn into_inner(self) -> S {
228 self.inner
229 }
230}
231
232impl<S: Reader> Reader for Rooted<S> {
233 fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
234 self.inner.read(&self.root.join(from))
235 }
236
237 fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
238 self.inner.read_children(&self.root.join(from))
239 }
240}
241
242impl<S: Writer> Writer for Rooted<S> {
243 fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
244 let result = self.inner.write(&self.root.join(to), data)?;
245 result.strip_prefix(&self.root).ok_or_else(|| {
246 Error::store(
247 "rooted",
248 "write",
249 format!("inner store returned path outside root: {}", result),
250 )
251 })
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::{path, Value};
259 use std::collections::HashMap;
260
261 struct MapStore {
262 data: HashMap<Path, Record>,
263 }
264
265 impl MapStore {
266 fn new() -> Self {
267 Self {
268 data: HashMap::new(),
269 }
270 }
271 }
272
273 impl Reader for MapStore {
274 fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
275 Ok(self.data.get(from).cloned())
276 }
277 }
278
279 impl Writer for MapStore {
280 fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
281 self.data.insert(to.clone(), data);
282 Ok(to.clone())
283 }
284 }
285
286 #[test]
287 fn read_only_passes_reads_rejects_writes() {
288 let mut inner = MapStore::new();
289 inner
290 .write(&path!("key"), Record::parsed(Value::from("v")))
291 .unwrap();
292
293 let mut ro = ReadOnly::new(inner);
294 assert!(ro.read(&path!("key")).unwrap().is_some());
295
296 let err = ro
297 .write(&path!("key"), Record::parsed(Value::from("w")))
298 .unwrap_err();
299 assert!(matches!(err, Error::PermissionDenied { .. }));
300
301 let mut inner = ro.into_inner();
303 assert_eq!(
304 inner.read(&path!("key")).unwrap().unwrap().as_value(),
305 Some(&Value::from("v"))
306 );
307 }
308
309 #[test]
310 fn cascade_layers_reads_and_writes_to_primary() {
311 let mut fallback = MapStore::new();
312 fallback
313 .write(&path!("base"), Record::parsed(Value::from("default")))
314 .unwrap();
315 fallback
316 .write(&path!("both"), Record::parsed(Value::from("under")))
317 .unwrap();
318
319 let mut primary = MapStore::new();
320 primary
321 .write(&path!("both"), Record::parsed(Value::from("over")))
322 .unwrap();
323
324 let mut cascade = Cascade::new(primary, fallback);
325
326 assert_eq!(
328 cascade.read(&path!("base")).unwrap().unwrap().as_value(),
329 Some(&Value::from("default"))
330 );
331 assert_eq!(
333 cascade.read(&path!("both")).unwrap().unwrap().as_value(),
334 Some(&Value::from("over"))
335 );
336 assert!(cascade.read(&path!("missing")).unwrap().is_none());
338
339 cascade
341 .write(&path!("new"), Record::parsed(Value::from("x")))
342 .unwrap();
343 let (mut primary, mut fallback) = cascade.into_inner();
344 assert!(primary.read(&path!("new")).unwrap().is_some());
345 assert!(fallback.read(&path!("new")).unwrap().is_none());
346 }
347
348 #[test]
349 fn shared_clones_access_same_store() {
350 let shared = Shared::new(MapStore::new());
351 let mut a = shared.clone();
352 let mut b = shared;
353
354 a.write(&path!("key"), Record::parsed(Value::from("v")))
355 .unwrap();
356 assert!(b.read(&path!("key")).unwrap().is_some());
357 }
358
359 #[test]
360 fn shared_is_send_and_usable_across_threads() {
361 let shared = Shared::new(MapStore::new());
362 let mut clone = shared.clone();
363 let handle = std::thread::spawn(move || {
364 clone
365 .write(&path!("from_thread"), Record::parsed(Value::from(1i64)))
366 .unwrap();
367 });
368 handle.join().unwrap();
369 assert!(shared.lock().read(&path!("from_thread")).unwrap().is_some());
370 }
371
372 #[test]
373 fn masked_redacts_component_wise() {
374 let mut inner = MapStore::new();
375 inner
376 .write(
377 &path!("gate/api_key"),
378 Record::parsed(Value::from("s3cret")),
379 )
380 .unwrap();
381 inner
382 .write(
383 &path!("gate/api_key_other"),
384 Record::parsed(Value::from("visible")),
385 )
386 .unwrap();
387 inner
388 .write(&path!("gate/model"), Record::parsed(Value::from("gpt-oss")))
389 .unwrap();
390
391 let mut masked = Masked::new(inner, vec![PathPattern::prefix(path!("gate/api_key"))]);
392
393 assert_eq!(
395 masked
396 .read(&path!("gate/api_key"))
397 .unwrap()
398 .unwrap()
399 .as_value(),
400 Some(&Value::from("[masked]"))
401 );
402 assert_eq!(
404 masked
405 .read(&path!("gate/api_key_other"))
406 .unwrap()
407 .unwrap()
408 .as_value(),
409 Some(&Value::from("visible"))
410 );
411 assert_eq!(
413 masked
414 .read(&path!("gate/model"))
415 .unwrap()
416 .unwrap()
417 .as_value(),
418 Some(&Value::from("gpt-oss"))
419 );
420 assert!(masked.read(&path!("gate/api_key/sub")).unwrap().is_none());
422 }
423
424 #[test]
425 fn masked_passes_writes_through() {
426 let inner = MapStore::new();
427 let mut masked = Masked::with_mask(
428 inner,
429 vec![PathPattern::exact(path!("secret"))],
430 Value::Null,
431 );
432 masked
433 .write(&path!("secret"), Record::parsed(Value::from("v")))
434 .unwrap();
435 assert_eq!(
437 masked.read(&path!("secret")).unwrap().unwrap().as_value(),
438 Some(&Value::Null)
439 );
440 let mut inner = masked.into_inner();
442 assert_eq!(
443 inner.read(&path!("secret")).unwrap().unwrap().as_value(),
444 Some(&Value::from("v"))
445 );
446 }
447
448 #[test]
449 fn rooted_confines_and_strips() {
450 let mut rooted = Rooted::new(path!("export/v1"), MapStore::new());
451
452 let result = rooted
453 .write(&path!("users/alice"), Record::parsed(Value::from("a")))
454 .unwrap();
455 assert_eq!(result, path!("users/alice"));
457
458 let mut inner = rooted.into_inner();
460 assert!(inner
461 .read(&path!("export/v1/users/alice"))
462 .unwrap()
463 .is_some());
464 }
465
466 #[test]
467 fn rooted_reads_under_root() {
468 let mut inner = MapStore::new();
469 inner
470 .write(&path!("jail/key"), Record::parsed(Value::from("v")))
471 .unwrap();
472
473 let mut rooted = Rooted::new(path!("jail"), inner);
474 assert!(rooted.read(&path!("key")).unwrap().is_some());
475 assert!(rooted.read(&path!("jail/key")).unwrap().is_none());
477 }
478
479 #[test]
480 fn rooted_escaping_write_result_is_error() {
481 struct EscapingStore;
483
484 impl Reader for EscapingStore {
485 fn read(&mut self, _from: &Path) -> Result<Option<Record>, Error> {
486 Ok(None)
487 }
488 }
489
490 impl Writer for EscapingStore {
491 fn write(&mut self, _to: &Path, _data: Record) -> Result<Path, Error> {
492 Ok(path!("elsewhere/entirely"))
493 }
494 }
495
496 let mut rooted = Rooted::new(path!("jail"), EscapingStore);
497 let err = rooted
498 .write(&path!("key"), Record::parsed(Value::Null))
499 .unwrap_err();
500 assert!(err.to_string().contains("outside root"));
501 }
502}