1use anyhow::{anyhow, Result};
2use std::collections::BTreeMap;
3use std::sync::Mutex;
4
5pub trait ObjectStore {
6 fn put(&self, key: &str, body: &[u8]) -> Result<()>;
7 fn get(&self, key: &str) -> Result<Vec<u8>>;
8 fn list(&self, prefix: &str) -> Result<Vec<String>>;
9}
10
11#[derive(Default)]
12pub struct InMemoryStore {
13 inner: Mutex<BTreeMap<String, Vec<u8>>>,
14}
15
16impl InMemoryStore {
17 pub fn new() -> Self {
18 Self::default()
19 }
20}
21
22impl ObjectStore for InMemoryStore {
23 fn put(&self, key: &str, body: &[u8]) -> Result<()> {
24 self.inner
25 .lock()
26 .unwrap()
27 .insert(key.to_string(), body.to_vec());
28 Ok(())
29 }
30 fn get(&self, key: &str) -> Result<Vec<u8>> {
31 self.inner
32 .lock()
33 .unwrap()
34 .get(key)
35 .cloned()
36 .ok_or_else(|| anyhow!("no such object: {key}"))
37 }
38 fn list(&self, prefix: &str) -> Result<Vec<String>> {
39 Ok(self
40 .inner
41 .lock()
42 .unwrap()
43 .keys()
44 .filter(|k| k.starts_with(prefix))
45 .cloned()
46 .collect())
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn put_get_list() {
56 let s = InMemoryStore::new();
57 s.put("git-ark/x/latest.age", b"a").unwrap();
58 s.put("git-ark/x/history/2026.age", b"b").unwrap();
59 assert_eq!(s.get("git-ark/x/latest.age").unwrap(), b"a");
60 let mut listed = s.list("git-ark/x/history/").unwrap();
61 listed.sort();
62 assert_eq!(listed, vec!["git-ark/x/history/2026.age".to_string()]);
63 }
64
65 #[test]
66 fn get_missing_errs() {
67 let s = InMemoryStore::new();
68 assert!(s.get("nope").is_err());
69 }
70}