r2d2_jfs/
lib.rs

1//! # r2d2-jfs
2//!
3//! [JSON file store (`jfs`)](https://crates.io/crates/jfs) support for the
4//! [`r2d2`](https://crates.io/crates/r2d2) connection pool.
5//!
6//! ## Example
7//!
8//! ```rust,no_run
9//! use std::thread;
10//! use serde::{Serialize, Deserialize};
11//! use r2d2_jfs::JfsConnectionManager;
12//!
13//! #[derive(Serialize, Deserialize)]
14//! struct Data { x: i32 }
15//!
16//!let manager = JfsConnectionManager::file("file.json").unwrap();
17//!let pool = r2d2::Pool::builder().max_size(5).build(manager).unwrap();
18//!let mut threads = vec![];
19//!for i in 0..10 {
20//!    let pool = pool.clone();
21//!    threads.push(thread::spawn(move || {
22//!        let d = Data { x: i };
23//!        let conn = pool.get().unwrap();
24//!        conn.save(&d).unwrap();
25//!    }));
26//!}
27//!for c in threads {
28//!    c.join().unwrap();
29//!}
30//! ```
31
32use jfs::{self, Store, IN_MEMORY};
33use std::{io, path::Path};
34
35/// An `r2d2::ManageConnection` for `jfs::Store`s.
36pub struct JfsConnectionManager(Store);
37
38impl JfsConnectionManager {
39    /// Creates a new `JfsConnectionManager` for a single json file.
40    pub fn file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
41        let cfg = jfs::Config {
42            single: true,
43            ..Default::default()
44        };
45        Self::new_with_cfg(path, cfg)
46    }
47    /// Creates a new `JfsConnectionManager` for a directory with json files.
48    pub fn dir<P: AsRef<Path>>(path: P) -> io::Result<Self> {
49        let cfg = jfs::Config {
50            single: false,
51            ..Default::default()
52        };
53        Self::new_with_cfg(path, cfg)
54    }
55    /// Creates a new `JfsConnectionManager` with a in-memory store.
56    pub fn memory() -> Self {
57        Self(Store::new(IN_MEMORY).expect("Unable to initialize in-memory store"))
58    }
59
60    /// Creates a new `JfsConnectionManager` with the given path and jfs::Config
61    pub fn new_with_cfg<P: AsRef<Path>>(path: P, cfg: jfs::Config) -> io::Result<Self> {
62        let store = Store::new_with_cfg(path, cfg)?;
63        Ok(Self(store))
64    }
65}
66
67impl r2d2::ManageConnection for JfsConnectionManager {
68    type Connection = jfs::Store;
69    type Error = io::Error;
70
71    fn connect(&self) -> Result<Store, Self::Error> {
72        Ok(self.0.clone())
73    }
74
75    fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> {
76        Ok(())
77    }
78
79    fn has_broken(&self, _: &mut Self::Connection) -> bool {
80        false
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use serde::{Deserialize, Serialize};
88    use std::thread;
89    use tempdir::TempDir;
90
91    #[test]
92    fn multi_threading() {
93        #[derive(Serialize, Deserialize)]
94        struct Data {
95            x: i32,
96        }
97        let dir = TempDir::new("r2d2-jfs-test").expect("Could not create temporary directory");
98        let file = dir.path().join("db.json");
99        let manager = JfsConnectionManager::file(file).unwrap();
100        let pool = r2d2::Pool::builder().max_size(5).build(manager).unwrap();
101        let mut threads: Vec<thread::JoinHandle<()>> = vec![];
102        for i in 0..20 {
103            let pool = pool.clone();
104            let x = Data { x: i };
105            threads.push(thread::spawn(move || {
106                let db = pool.get().unwrap();
107                db.save_with_id(&x, &i.to_string()).unwrap();
108            }));
109        }
110        for t in threads {
111            t.join().unwrap();
112        }
113        let db = pool.get().unwrap();
114        let all = db.all::<Data>().unwrap();
115        assert_eq!(all.len(), 20);
116        for (id, data) in all {
117            assert_eq!(data.x.to_string(), id);
118        }
119    }
120}