daybreak/backend/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! The persistence backends of the Database.
6//!
7//! A file is a `Backend` through the `FileBackend`, so is a `Vec<u8>` with a
8//! `MemoryBackend`.
9//!
10//! Implementing your own Backend should be straightforward. Check the `Backend`
11//! documentation for details.
12
13use crate::error;
14
15/// The Backend Trait.
16///
17/// It should always read and save in full the data that it is passed. This
18/// means that a write to the backend followed by a read __must__ return the
19/// same dataset.
20///
21/// **Important**: You can only return custom errors if the `other_errors` feature is enabled
22pub trait Backend {
23    /// Read the all data from the backend.
24    fn get_data(&mut self) -> error::BackendResult<Vec<u8>>;
25
26    /// Write the whole slice to the backend.
27    fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()>;
28}
29
30impl Backend for Box<dyn Backend> {
31    fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
32        use std::ops::DerefMut;
33        self.deref_mut().get_data()
34    }
35
36    fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
37        use std::ops::DerefMut;
38        self.deref_mut().put_data(data)
39    }
40}
41
42impl<T: Backend> Backend for Box<T> {
43    fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
44        use std::ops::DerefMut;
45        self.deref_mut().get_data()
46    }
47
48    fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
49        use std::ops::DerefMut;
50        self.deref_mut().put_data(data)
51    }
52}
53
54#[cfg(feature = "mmap")]
55mod mmap;
56#[cfg(feature = "mmap")]
57pub use mmap::MmapStorage;
58
59mod path;
60pub use path::PathBackend;
61
62/// A backend using a file.
63#[derive(Debug)]
64pub struct FileBackend(std::fs::File);
65
66impl Backend for FileBackend {
67    fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
68        use std::io::{Read, Seek, SeekFrom};
69
70        let mut buffer = vec![];
71        self.0.seek(SeekFrom::Start(0))?;
72        self.0.read_to_end(&mut buffer)?;
73        Ok(buffer)
74    }
75
76    fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
77        use std::io::{Seek, SeekFrom, Write};
78
79        self.0.seek(SeekFrom::Start(0))?;
80        self.0.set_len(0)?;
81        self.0.write_all(data)?;
82        self.0.sync_all()?;
83        Ok(())
84    }
85}
86
87impl FileBackend {
88    /// Use an already open [`File`](std::fs::File) as the backend.
89    #[must_use]
90    pub fn from_file(file: std::fs::File) -> Self {
91        Self(file)
92    }
93
94    /// Return the inner File.
95    #[must_use]
96    pub fn into_inner(self) -> std::fs::File {
97        self.0
98    }
99}
100
101impl FileBackend {
102    /// Opens a new [`FileBackend`] for a given path.
103    /// Errors when the file doesn't yet exist.
104    pub fn from_path_or_fail<P: AsRef<std::path::Path>>(path: P) -> error::BackendResult<Self> {
105        use std::fs::OpenOptions;
106
107        Ok(Self(OpenOptions::new().read(true).write(true).open(path)?))
108    }
109
110    /// Opens a new [`FileBackend`] for a given path.
111    /// Creates a file if it doesn't yet exist.
112    ///
113    /// Returns the [`FileBackend`] and whether the file already existed.
114    pub fn from_path_or_create<P: AsRef<std::path::Path>>(
115        path: P,
116    ) -> error::BackendResult<(Self, bool)> {
117        use std::fs::OpenOptions;
118
119        let exists = path.as_ref().is_file();
120        Ok((
121            Self(
122                OpenOptions::new()
123                    .read(true)
124                    .write(true)
125                    .create(true)
126                    .open(path)?,
127            ),
128            exists,
129        ))
130    }
131
132    /// Opens a new [`FileBackend`] for a given path.
133    /// Creates a file if it doesn't yet exist, and calls `closure` with it.
134    pub fn from_path_or_create_and<P, C>(path: P, closure: C) -> error::BackendResult<Self>
135    where
136        C: FnOnce(&mut std::fs::File),
137        P: AsRef<std::path::Path>,
138    {
139        Self::from_path_or_create(path).map(|(mut b, exists)| {
140            if !exists {
141                closure(&mut b.0)
142            }
143            b
144        })
145    }
146}
147
148/// An in memory backend.
149///
150/// It is backed by a byte vector (`Vec<u8>`).
151#[derive(Debug, Default)]
152pub struct MemoryBackend(Vec<u8>);
153
154impl MemoryBackend {
155    /// Construct a new Memory Database.
156    #[must_use]
157    pub fn new() -> Self {
158        Self::default()
159    }
160}
161
162impl Backend for MemoryBackend {
163    fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
164        println!("Returning data: {:?}", &self.0);
165        Ok(self.0.clone())
166    }
167
168    fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
169        println!("Writing data: {:?}", data);
170        self.0 = data.to_owned();
171        Ok(())
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::{Backend, FileBackend, MemoryBackend};
178    use std::io::{Read, Seek, SeekFrom, Write};
179    use tempfile::NamedTempFile;
180
181    #[test]
182    fn test_memory_backend() {
183        let mut backend = MemoryBackend::new();
184        let data = [4, 5, 1, 6, 8, 1];
185
186        backend.put_data(&data).expect("could not put data");
187        assert_eq!(backend.get_data().expect("could not get data"), data);
188    }
189
190    #[test]
191    #[cfg_attr(miri, ignore)]
192    fn test_file_backend_from_file() {
193        let file = tempfile::tempfile().expect("could not create temporary file");
194        let mut backend = FileBackend::from_file(file);
195        let data = [4, 5, 1, 6, 8, 1];
196        let data2 = [3, 99, 127, 6];
197
198        backend.put_data(&data).expect("could not put data");
199        assert_eq!(backend.get_data().expect("could not get data"), data);
200
201        backend.put_data(&data2).expect("could not put data");
202        assert_eq!(backend.get_data().expect("could not get data"), data2);
203    }
204
205    #[test]
206    #[cfg_attr(miri, ignore)]
207    fn test_file_backend_from_path_existing() {
208        let file = NamedTempFile::new().expect("could not create temporary file");
209        let (mut backend, existed) =
210            FileBackend::from_path_or_create(file.path()).expect("could not create backend");
211        assert!(existed);
212        let data = [4, 5, 1, 6, 8, 1];
213
214        backend.put_data(&data).expect("could not put data");
215        assert_eq!(backend.get_data().expect("could not get data"), data);
216    }
217
218    #[test]
219    #[cfg_attr(miri, ignore)]
220    fn test_file_backend_from_path_new() {
221        let dir = tempfile::tempdir().expect("could not create temporary directory");
222        let mut file_path = dir.path().to_owned();
223        file_path.push("rustbreak_path_db.db");
224        let (mut backend, existed) =
225            FileBackend::from_path_or_create(file_path).expect("could not create backend");
226        assert!(!existed);
227        let data = [4, 5, 1, 6, 8, 1];
228
229        backend.put_data(&data).expect("could not put data");
230        assert_eq!(backend.get_data().expect("could not get data"), data);
231        dir.close().expect("Error while deleting temp directory!");
232    }
233
234    #[test]
235    #[cfg_attr(miri, ignore)]
236    fn test_file_backend_from_path_nofail() {
237        let file = NamedTempFile::new().expect("could not create temporary file");
238        let file_path = file.path().to_owned();
239        let mut backend = FileBackend::from_path_or_fail(file_path).expect("should not fail");
240        let data = [4, 5, 1, 6, 8, 1];
241
242        backend.put_data(&data).expect("could not put data");
243        assert_eq!(backend.get_data().expect("could not get data"), data);
244    }
245
246    #[test]
247    #[cfg_attr(miri, ignore)]
248    fn test_file_backend_from_path_fail_notfound() {
249        let dir = tempfile::tempdir().expect("could not create temporary directory");
250        let mut file_path = dir.path().to_owned();
251        file_path.push("rustbreak_path_db.db");
252        let err =
253            FileBackend::from_path_or_fail(file_path).expect_err("should fail with file not found");
254        if let crate::error::BackendError::Io(io_err) = &err {
255            assert_eq!(std::io::ErrorKind::NotFound, io_err.kind());
256        } else {
257            panic!("Wrong kind of error returned: {}", err);
258        };
259        dir.close().expect("Error while deleting temp directory!");
260    }
261
262    #[test]
263    #[cfg_attr(miri, ignore)]
264    fn test_file_backend_into_inner() {
265        let file = tempfile::tempfile().expect("could not create temporary file");
266        let mut backend = FileBackend::from_file(file);
267        let data = [4, 5, 1, 6, 8, 1];
268
269        backend.put_data(&data).expect("could not put data");
270        assert_eq!(backend.get_data().expect("could not get data"), data);
271
272        let mut file = backend.into_inner();
273        file.seek(SeekFrom::Start(0)).unwrap();
274        let mut contents = Vec::new();
275        assert_eq!(file.read_to_end(&mut contents).unwrap(), 6);
276        assert_eq!(&contents[..], &data[..]);
277    }
278
279    #[test]
280    fn allow_boxed_backends() {
281        let mut backend = Box::new(MemoryBackend::new());
282        let data = [4, 5, 1, 6, 8, 1];
283
284        backend.put_data(&data).unwrap();
285        assert_eq!(backend.get_data().unwrap(), data);
286    }
287
288    // If the file already exists, the closure shouldn't be called.
289    #[test]
290    #[cfg_attr(miri, ignore)]
291    fn test_file_backend_create_and_existing_nocall() {
292        let file = NamedTempFile::new().expect("could not create temporary file");
293        let mut backend = FileBackend::from_path_or_create_and(file.path(), |_| {
294            panic!("Closure called but file already existed");
295        })
296        .expect("could not create backend");
297        let data = [4, 5, 1, 6, 8, 1];
298
299        backend.put_data(&data).expect("could not put data");
300        assert_eq!(backend.get_data().expect("could not get data"), data);
301    }
302
303    // If the file does not yet exist, the closure should be called.
304    #[test]
305    #[cfg_attr(miri, ignore)]
306    fn test_file_backend_create_and_new() {
307        let dir = tempfile::tempdir().expect("could not create temporary directory");
308        let mut file_path = dir.path().to_owned();
309        file_path.push("rustbreak_path_db.db");
310        let mut backend = FileBackend::from_path_or_create_and(file_path, |f| {
311            f.write_all(b"this is a new file")
312                .expect("could not write to file")
313        })
314        .expect("could not create backend");
315        assert_eq!(
316            backend.get_data().expect("could not get data"),
317            b"this is a new file"
318        );
319        let data = [4, 5, 1, 6, 8, 1];
320
321        backend.put_data(&data).expect("could not put data");
322        assert_eq!(backend.get_data().expect("could not get data"), data);
323        dir.close().expect("Error while deleting temp directory!");
324    }
325}