Skip to main content

koit_toml/
lib.rs

1//! Koit is a simple, asynchronous, pure-Rust, structured, embedded database.
2//!
3//! # Examples
4//!
5//! ```
6//! use std::default::Default;
7//!
8//! use koit::{FileDatabase, format::Toml};
9//! use serde::{Deserialize, Serialize};
10//!
11//! #[derive(Default, Deserialize, Serialize)]
12//! struct Data {
13//!     cats: u64,
14//!     yaks: u64,
15//! }
16//!
17//! #[async_std::main]
18//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
19//!     let db = FileDatabase::<Data, Toml>::load_from_path_or_default("./db.toml").await?;
20//!   
21//!     db.write(|data| {
22//!         data.cats = 10;
23//!         data.yaks = 32;
24//!     }).await;
25//!     
26//!     assert_eq!(db.read(|data| data.cats + data.yaks).await, 42);
27//!
28//!     db.save().await?;
29//!
30//!     Ok(())
31//! }
32//! ```
33//!
34//! # Features
35//!
36//! Koit comes with a [file-backed database](crate::FileDatabase) and [JSON](crate::format::Json)
37//! and [Bincode](crate::format::Bincode) formatters. You can also define your own storage
38//! [format](crate::format) or [backend](crate::backend).
39//!
40//! Note that the file-backed database requires the Tokio 0.3 runtime to function.
41
42#![cfg_attr(docsrs, feature(doc_cfg))]
43
44use async_std::sync::{Mutex, RwLock};
45use std::future::Future;
46use std::marker::PhantomData;
47
48mod error;
49pub use error::KoitError;
50
51pub mod backend;
52pub use backend::Backend;
53
54pub mod format;
55pub use format::Format;
56
57/// The Koit database.
58///
59/// The database provides reading, writing, saving and reloading functionality.
60/// It uses a reader-writer lock on the internal data structure, allowing
61/// concurrent access by readers, while writers are given exclusive access.
62///
63/// It requires a [`Format`](crate::format::Format) marker type
64#[derive(Debug)]
65pub struct Database<D, B, F> {
66  data: RwLock<D>,
67  backend: Mutex<B>,
68  _format: PhantomData<F>,
69}
70
71impl<D, B, F> Database<D, B, F>
72where
73  B: Backend,
74  F: Format<D>,
75{
76  /// Create a database from its constituents.
77  pub fn from_parts(data: D, backend: B) -> Self {
78    Self {
79      data: RwLock::new(data),
80      backend: Mutex::new(backend),
81      _format: PhantomData,
82    }
83  }
84
85  /// Write to the data contained in the database.  This gives exclusive access to the underlying
86  /// data structure. The value your closure returns will be passed on as the return value of this
87  /// function.
88  ///
89  /// This write-locks the data structure.
90  pub async fn write<T, R>(&self, task: T) -> R
91  where
92    T: FnOnce(&mut D) -> R,
93  {
94    let mut data = self.data.write().await;
95    task(&mut data)
96  }
97
98  /// Same as [`crate::Database::write`], except the task returns a future.
99  pub async fn write_and_then<T, Fut, R>(&self, task: T) -> R
100  where
101    T: FnOnce(&mut D) -> Fut,
102    Fut: Future<Output = R>,
103  {
104    let mut data = self.data.write().await;
105    task(&mut data).await
106  }
107
108  /// Read the data contained in the database. Many readers can read in parallel.
109  /// The value your closure returns will be passed on as the return value of this function.
110  ///
111  /// This read-locks the data structure.
112  pub async fn read<T, R>(&self, task: T) -> R
113  where
114    T: FnOnce(&D) -> R,
115  {
116    let data = self.data.read().await;
117    task(&data)
118  }
119
120  /// Same as [`crate::Database::read`], except the task returns a future.
121  pub async fn read_and_then<T, Fut, R>(&self, task: T) -> R
122  where
123    T: FnOnce(&D) -> Fut,
124    Fut: Future<Output = R>,
125  {
126    let data = self.data.read().await;
127    task(&data).await
128  }
129
130  /// Replace the actual data in the database by the given data in the parameter, returning the
131  /// old data.
132  ///
133  /// This write-locks the data structure.
134  pub async fn replace(&self, data: D) -> D {
135    self
136      .write(|actual_data| std::mem::replace(actual_data, data))
137      .await
138  }
139
140  /// Returns a reference to the underlying data lock.
141  ///
142  /// It is recommended to use the `read` and `write` methods instead of this, to ensure
143  /// locks are only held for as long as needed.
144  ///
145  /// # Examples
146  ///
147  /// ```
148  /// use koit::{Database, format::Json, backend::Memory};
149  ///
150  /// type Messages = Vec<String>;
151  /// let db: Database<_, _, Json> = Database::from_parts(1, Memory::default());
152  ///
153  /// futures::executor::block_on(async move {
154  ///     let data_lock = db.get_data_lock();
155  ///     let mut data = data_lock.write().await;
156  ///     *data = 42;
157  ///     drop(data);
158  ///
159  ///     db.read(|n| assert_eq!(*n, 42)).await;
160  /// });
161  /// ```
162  pub fn get_data_lock(&self) -> &RwLock<D> {
163    &self.data
164  }
165
166  /// Returns a mutable reference to the underlying data.
167  ///
168  /// This borrows `Database` mutably; no locking takes place.
169  ///
170  /// # Examples
171  ///
172  /// ```
173  /// use koit::{Database, format::Json, backend::Memory};
174  ///
175  /// let mut db: Database<_, _, Json> = Database::from_parts(1, Memory::default());
176  ///
177  /// let n = db.get_data_mut();
178  /// *n += 41;
179  ///
180  /// futures::executor::block_on(db.read(|n| assert_eq!(*n, 42)));
181  /// ```
182  pub fn get_data_mut(&mut self) -> &mut D {
183    self.data.get_mut()
184  }
185
186  /// Flush the data contained in the database to the backend.
187  ///
188  /// This read-locks the data structure.
189  ///
190  /// # Errors
191  ///
192  /// - If the data in the database failed to be encoded by the format, an error variant is returned.
193  /// - If the bytes failed to be written to the backend, an error variant is returned. This may mean
194  /// the backend is now corrupted.
195  ///
196  /// # Panics
197  ///
198  /// Some back-ends (such as [`crate::backend::File`]) might panic on some async runtimes.
199  pub async fn save(&self) -> Result<(), KoitError> {
200    let mut backend = self.backend.lock().await;
201    let data = self.data.read().await;
202    backend
203      .write(F::to_bytes(&data).map_err(|err| KoitError::ToFormat(err.into()))?)
204      .await
205      .map_err(|err| KoitError::BackendWrite(err.into()))?;
206    Ok(())
207  }
208
209  /// Load data from the backend.
210  async fn load_from_backend(&self) -> Result<D, KoitError> {
211    let mut backend = self.backend.lock().await;
212    let bytes = backend
213      .read()
214      .await
215      .map_err(|err| KoitError::BackendRead(err.into()))?;
216    Ok(F::from_bytes(bytes).map_err(|err| KoitError::FromFormat(err.into()))?)
217  }
218
219  /// Update this database with data from the backend, returning the old data.
220  ///
221  /// This will write-lock the internal data structure.
222  ///
223  /// # Errors
224  ///
225  /// - If the bytes from teh backend failed to be decoded by the format, an error variant is returned.
226  /// - If the bytes failed to be read by the backend, an error variant is returned.
227  ///
228  /// # Panics
229  ///
230  /// Some back-ends (such as [`crate::backend::File`]) might panic on some async runtimes.
231  pub async fn reload(&self) -> Result<D, KoitError> {
232    let new_data = self.load_from_backend().await?;
233    Ok(self.replace(new_data).await)
234  }
235
236  /// Consume the database and return its data and backend.
237  pub fn into_parts(self) -> (D, B) {
238    (self.data.into_inner(), self.backend.into_inner())
239  }
240}
241
242/// A file-backed database.
243///
244/// Note: this requires its futures to be executed on the Tokio 0.3 runtime.
245#[cfg(feature = "file-backend")]
246#[cfg_attr(docsrs, doc(cfg(feature = "file-backend")))]
247pub type FileDatabase<D, F> = Database<D, backend::File, F>;
248
249#[cfg(feature = "file-backend")]
250impl<D, F> FileDatabase<D, F>
251where
252  F: Format<D>,
253{
254  /// Construct the file-backed database from the given path. This attempts to load data
255  /// from the given file.
256  ///
257  /// # Errors
258  /// If the file cannot be read, or the [formatter](crate::format::Format) cannot decode the data,
259  /// an error variant will be returned.
260  pub async fn load_from_path<P>(path: P) -> Result<Self, KoitError>
261  where
262    P: AsRef<std::path::Path>,
263  {
264    let mut backend = backend::File::from_path(path)
265      .await
266      .map_err(|err| KoitError::BackendCreation(err.into()))?;
267
268    let bytes = backend
269      .read()
270      .await
271      .map_err(|err| KoitError::BackendRead(err.into()))?;
272    let data = F::from_bytes(bytes).map_err(|err| KoitError::FromFormat(err.into()))?;
273
274    Ok(Database {
275      data: RwLock::new(data),
276      backend: Mutex::new(backend),
277      _format: PhantomData,
278    })
279  }
280
281  /// Construct the file-backed database from the given path. If the file does not exist,
282  /// the file is created. Then `factory` is called and its return value is used as the initial value.
283  /// This data is immediately and saved to file.
284  pub async fn load_from_path_or_else<P, T>(path: P, factory: T) -> Result<Self, KoitError>
285  where
286    P: AsRef<std::path::Path>,
287    T: FnOnce() -> D,
288  {
289    let (mut backend, exists) = backend::File::from_path_or_create(path)
290      .await
291      .map_err(|e| KoitError::BackendCreation(e.into()))?;
292
293    let data = if exists {
294      let bytes = backend
295        .read()
296        .await
297        .map_err(|err| KoitError::BackendRead(err.into()))?;
298      F::from_bytes(bytes).map_err(|err| KoitError::FromFormat(err.into()))?
299    } else {
300      factory()
301    };
302
303    let db = Database {
304      data: RwLock::new(data),
305      backend: Mutex::new(backend),
306      _format: PhantomData,
307    };
308
309    db.save().await?;
310    Ok(db)
311  }
312
313  /// Same as `load_from_path_or_else`, except it uses [`Default`](`std::default::Default`) instead of a factory.
314  pub async fn load_from_path_or_default<P>(path: P) -> Result<Self, KoitError>
315  where
316    P: AsRef<std::path::Path>,
317    D: std::default::Default,
318  {
319    Self::load_from_path_or_else(path, std::default::Default::default).await
320  }
321}