dinglebit_store/
collection.rs

1//! A storage system for objects (structs) stored by key.
2
3use std::marker::PhantomData;
4use std::str::from_utf8;
5
6use serde::{de::DeserializeOwned, Serialize};
7use serde_json::{from_str, to_string};
8
9type Result<T> = std::result::Result<T, Error>;
10
11/// The errors this package returns.
12#[derive(Debug)]
13pub enum Error {
14    /// This is more of a generic error that something bad happened.
15    SystemError(String),
16
17    /// Serailization/Deserialization failed for some reason.
18    Serialization(String),
19
20    /// The process did not have permissions to perform the operation.
21    PermissionDenied,
22}
23
24/// A Collection contains serializable objects which are accessible by
25/// a key.
26pub trait Collection<'a, T>
27where
28    T: Serialize + DeserializeOwned + 'a,
29{
30    /// Put the given value into the collection under the given
31    /// key. If the key already exists, it's replaced with the given
32    /// value and the old value is returned.
33    fn put(&mut self, key: &str, value: T) -> Result<Option<T>>;
34
35    // Get the value associated with the given key.
36    fn get(&mut self, key: &str) -> Result<Option<T>>;
37
38    /// Find all the files that have the given prefix and iterate over
39    /// them.
40    fn prefix(&mut self, prefix: &str) -> Box<dyn Iterator<Item = Result<T>> + 'a>;
41}
42
43/// Sled is an impementation of a Collection using Sled to store the objects.
44pub struct Sled {
45    db: sled::Db,
46}
47
48impl Sled {
49    /// Open the given sled database.
50    pub fn open(file: &str) -> Result<Self> {
51        let db = match sled::open(file) {
52            Ok(db) => db,
53            Err(e) => return Err(Error::SystemError(e.to_string())),
54        };
55
56        Ok(Self { db: db })
57    }
58}
59
60impl<'a, T> Collection<'a, T> for Sled
61where
62    T: Serialize + DeserializeOwned + 'a,
63{
64    fn put(&mut self, key: &str, value: T) -> Result<Option<T>> {
65        // Convert to JSON.
66        let json = match to_string(&value) {
67            Ok(str) => str,
68            Err(err) => return Err(Error::Serialization(err.to_string())),
69        };
70
71        // Insert into database.
72        let value = match self.db.insert(key.as_bytes(), json.as_bytes()) {
73            Ok(value) => match value {
74                Some(value) => value,
75                None => return Ok(None),
76            },
77            Err(err) => return Err(Error::SystemError(err.to_string())),
78        };
79
80        // If we replaced an existing value, convert it to it's type
81        // so we can return it.
82        let json = match from_utf8(&value) {
83            Ok(json) => json,
84            Err(err) => return Err(Error::Serialization(err.to_string())),
85        };
86        let obj: T = match from_str(json) {
87            Ok(json) => json,
88            Err(err) => return Err(Error::Serialization(err.to_string())),
89        };
90
91        Ok(Some(obj))
92    }
93
94    fn get(&mut self, key: &str) -> Result<Option<T>> {
95        // Get the value from the database.
96        let ivec = match self.db.get(key.as_bytes()) {
97            Err(err) => return Err(Error::SystemError(err.to_string())),
98            Ok(ivec) => match ivec {
99                Some(ivec) => ivec,
100                None => return Ok(None),
101            },
102        };
103
104        // Convert it to the given type and return it.
105        let json = match from_utf8(&ivec) {
106            Ok(json) => json,
107            Err(err) => return Err(Error::Serialization(err.to_string())),
108        };
109        let json: T = match from_str(json) {
110            Ok(json) => json,
111            Err(err) => return Err(Error::Serialization(err.to_string())),
112        };
113
114        Ok(Some(json))
115    }
116
117    fn prefix(&mut self, prefix: &str) -> Box<dyn Iterator<Item = Result<T>> + 'a> {
118        Box::new(SledIter {
119            p: PhantomData,
120            iter: self.db.scan_prefix(prefix.as_bytes()),
121        })
122    }
123}
124
125/// SledIter implements the std Iterator for the prefix method.
126pub struct SledIter<T> {
127    p: PhantomData<T>,
128    iter: sled::Iter,
129}
130
131impl<T> Iterator for SledIter<T>
132where
133    T: DeserializeOwned,
134{
135    type Item = Result<T>;
136    fn next(&mut self) -> Option<Self::Item> {
137        // Get the next value.
138        let data = match self.iter.next() {
139            Some(r) => match r {
140                Ok(data) => data,
141                Err(e) => return Some(Err(Error::SystemError(e.to_string()))),
142            },
143            None => return None,
144        };
145
146        // Convert it to the given type and return it.
147        let json = match from_utf8(&(data.1)) {
148            Ok(json) => json,
149            Err(err) => return Some(Err(Error::Serialization(err.to_string()))),
150        };
151        let json: T = match from_str(json) {
152            Ok(json) => json,
153            Err(err) => return Some(Err(Error::Serialization(err.to_string()))),
154        };
155        Some(Ok(json))
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use crate::collection::*;
162    use serde::{Deserialize, Serialize};
163    use tempfile::tempdir;
164
165    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
166    struct Person {
167        name: String,
168    }
169
170    #[test]
171    fn collection_get_put() {
172        let file = tempdir().expect("unable to make temp file");
173        let mut s: Box<dyn Collection<'static, Person>> =
174            Box::new(Sled::open(file.path().to_str().unwrap()).expect("failed to open with sled"));
175
176        let p = Person {
177            name: "foo".to_string(),
178        };
179        s.put(p.name.as_str(), p.clone())
180            .expect("failed to put foo");
181
182        let r = s.get(p.name.as_str()).expect("failed to get foo").unwrap();
183        assert_eq!(p, r);
184
185        assert_eq!(
186            s.get("i don't exist").expect("failed to get non-existant"),
187            None
188        );
189    }
190
191    #[test]
192    fn collection_prefix() {
193        let file = tempdir().expect("unable to make temp file");
194        let mut s: Box<dyn Collection<'static, Person>> =
195            Box::new(Sled::open(file.path().to_str().unwrap()).expect("failed to open with sled"));
196
197        let pp = vec![
198            Person {
199                name: "foo".to_string(),
200            },
201            Person {
202                name: "bar/foo0".to_string(),
203            },
204            Person {
205                name: "bar/foo1".to_string(),
206            },
207            Person {
208                name: "bar/foo2".to_string(),
209            },
210            Person {
211                name: "baz".to_string(),
212            },
213        ];
214
215        for p in pp {
216            s.put(p.name.as_str(), p.clone()).expect("failed to put");
217        }
218
219        let rr = s
220            .prefix("bar/")
221            .map(|r| r.expect("collecting"))
222            .collect::<Vec<Person>>();
223
224        let exp = vec![
225            Person {
226                name: "bar/foo0".to_string(),
227            },
228            Person {
229                name: "bar/foo1".to_string(),
230            },
231            Person {
232                name: "bar/foo2".to_string(),
233            },
234        ];
235
236        assert_eq!(rr, exp);
237    }
238}