Skip to main content

gen_file_database/
persistence.rs

1use std::collections::BTreeSet;
2use std::io;
3use std::path::PathBuf;
4use convert_case::Case;
5use convert_case::Casing;
6use gen_file::folder;
7use serde::Deserialize;
8use serde::Serialize;
9use uuid::Uuid;
10use crate::index;
11use crate::temporary;
12
13pub fn init_db_folder(target_folder: &TargetFolder) -> io::Result<()> {
14    let path: PathBuf = target_folder.path.join(temporary::FOLDER_NAME);
15    folder::remove_if_exists(&path)?;
16    folder::create(&path)?;
17    Ok(())
18}
19
20pub fn delete_db_folder(target_folder: &TargetFolder) -> io::Result<()> {
21    folder::remove_if_exists(&target_folder.path)?;
22    Ok(())
23}
24
25#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct TargetFolder {
27    pub path: PathBuf
28}
29
30impl TargetFolder {
31    pub fn from(path: &PathBuf) -> TargetFolder {
32        TargetFolder {
33            path: path.to_path_buf()
34        }
35    }
36}
37
38#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
39pub struct Capsule<T> {
40    pub inner: T,
41    pub id: Uuid,
42    pub key: String,
43    pub folder: TargetFolder,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Persist<T> {
48    pub inner: T,
49}
50
51pub trait Indexable {
52    fn index_keys(&self) -> std::collections::BTreeSet<String>;
53}
54
55pub trait FileDbKey {
56    fn file_db_key() -> String;
57}
58
59pub fn save_capsule<T: Serialize + Clone + for<'a> Deserialize<'a> + FileDbKey>(capsule: Capsule<T>, index_keys: BTreeSet<String>) -> io::Result<Capsule<T>> {
60    if let Some(id) = if !index_keys.is_empty()
61    { index::index(index_keys, &capsule.key, &capsule.id, &capsule.folder)? } else { None } {
62        Capsule::<T>::get_by_id(&id, &capsule.folder)
63    } else {
64        temporary::save(capsule)
65    }
66}
67
68pub fn upsert_capsule<T: Serialize + Clone + for<'a> Deserialize<'a> + FileDbKey>(capsule: Capsule<T>, index_keys: BTreeSet<String>) -> io::Result<Capsule<T>> {
69    if let Some(id) = if !index_keys.is_empty()
70    { index::index(index_keys, &capsule.key, &capsule.id, &capsule.folder)? } else { None } {
71        let mut found = Capsule::<T>::get_by_id(&id, &capsule.folder)?;
72        found.inner = capsule.inner;
73        temporary::save(found)
74    } else {
75        temporary::save(capsule)
76    }
77}
78
79pub fn save_object<T: Serialize + Clone + FileDbKey>(data: &T, target_folder: &TargetFolder) -> io::Result<T> {
80    temporary::create(target_folder, &T::file_db_key().to_case(Case::Snake), data)?;
81    Ok(data.clone())
82}
83
84
85impl <T: Serialize + for<'a> Deserialize<'a> + Clone + FileDbKey> Capsule<T> {
86    pub fn update(&self) -> io::Result<Capsule<T>>
87    where
88        T: Indexable,
89    {
90        let capsule = self.clone();
91        if !self.inner.index_keys().is_empty() {
92            let _ = index::update(self.inner.index_keys(), &capsule.key, &capsule.id, &capsule.folder);
93        }
94        temporary::save(capsule)
95    }
96
97
98    pub fn get_by_id(id: &Uuid, target_folder: &TargetFolder) -> io::Result<Capsule<T>> {
99        temporary::select::<Capsule<T>>(target_folder, &format!("{}_{}", T::file_db_key().to_case(Case::Snake), id))
100            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("cannot find {} {} in database", T::file_db_key(), id)))
101    }
102
103    pub fn get_all(target_folder: &TargetFolder) -> io::Result<Vec<Capsule<T>>> {
104        let id_vec = index::get_all_by_type(&T::file_db_key().to_case(Case::Snake), target_folder)?;
105
106        Ok(id_vec
107            .into_iter()
108            .flat_map(|id| Capsule::<T>::get_by_id(&id, target_folder))
109            .collect())
110    }
111
112    pub fn find_by_index(keys: &[&str], target_folder: &TargetFolder) -> io::Result<Vec<Capsule<T>>> {
113        let id_vec = index::find_by_type(&T::file_db_key().to_case(Case::Snake), keys, target_folder)?;
114
115        Ok(id_vec
116            .into_iter()
117            .flat_map(|id| Capsule::<T>::get_by_id(&id, target_folder))
118            .collect())
119    }
120}
121
122impl <T: Serialize + for<'a> Deserialize<'a> + Clone + FileDbKey> Persist<T> {
123    pub fn get(target_folder: &TargetFolder) -> io::Result<T> {
124        temporary::select::<T>(target_folder, &T::file_db_key().to_case(Case::Snake))
125            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("cannot find {} in database", T::file_db_key())))
126    }
127}
128
129#[cfg(test)]
130#[path = "../tests/unit_tests/persistence.rs"]
131pub mod test;
132