gen-file-database 1.0.6

File based minimal database
Documentation
use std::collections::BTreeSet;
use std::collections::BTreeMap;
use std::io;
use convert_case::Case;
use convert_case::Casing;
use serde::Deserialize;
use serde::Serialize;
use uuid::Uuid;
use crate::persistence::save_object;
use crate::persistence::FileDbKey;
use crate::persistence::Persist;
use crate::persistence::TargetFolder;
use crate::serde_multi_key;

fn type_format(name: &str) -> String {
    format!("__#{}", name.to_case(Case::Snake))
}

pub fn index(mut index_keys: BTreeSet<String>, name: &str, id: &Uuid, target_folder: &TargetFolder) -> io::Result<Option<Uuid>> {
    if !index_keys.is_empty() && index_keys.iter().any(|k| k.is_empty()) {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("unauthorized empty index: {:?}", index_keys),
        ));
    }
    
    index_keys.insert(type_format(name));
    if let Ok(mut multi_key_index) = Persist::<MultiKeyIndex>::get(target_folder) {
        if let Some(existing_id) = multi_key_index.get(&index_keys) {
            return Ok(Some(existing_id.clone()));
        }
        multi_key_index.insert(index_keys, id);
        save_object::<MultiKeyIndex>(&multi_key_index, target_folder)?;
    } else {
        let multi_key_index = MultiKeyIndex::new().insert(index_keys, id);
        save_object::<MultiKeyIndex>(&multi_key_index, target_folder)?;
    }
    Ok(None)
}

pub fn update(mut index_keys: BTreeSet<String>, name: &str, id: &Uuid, target_folder: &TargetFolder) -> io::Result<()> {
    index_keys.insert(type_format(name));
    let mut multi_key_index = Persist::<MultiKeyIndex>::get(target_folder)?;
    multi_key_index.remove(id);
    multi_key_index.insert(index_keys, id);
    save_object::<MultiKeyIndex>(&multi_key_index, target_folder)?;
    Ok(())
}

pub fn get_all_by_type(name: &str, target_folder: &TargetFolder) -> io::Result<Vec<Uuid>> {
    Ok(if let Ok(multi_key_index) = Persist::<MultiKeyIndex>::get(target_folder) {
        let r#type = type_format(name);
        multi_key_index.find(&[&r#type])
    } else {
        Vec::new()
    })
}

pub fn find_by_type(name: &str, keys: &[&str], target_folder: &TargetFolder) -> io::Result<Vec<Uuid>> {
    Ok(if let Ok(multi_key_index) = Persist::<MultiKeyIndex>::get(target_folder) {
        let mut all_keys = keys.to_vec();
        let r#type = type_format(name);
        all_keys.push(&r#type);
        multi_key_index.find(&all_keys)
    } else {
        Vec::new()
    })
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
struct MultiKeyIndex {
    #[serde(with = "serde_multi_key")]
    index: BTreeMap<BTreeSet<String>, Uuid>,
}

impl MultiKeyIndex {
    fn new() -> Self {
        Self {
            index: BTreeMap::new(),
        }
    }

    fn insert(&mut self, keys: BTreeSet<String>, id: &Uuid) -> Self {
        self.index.insert(keys, *id);
        self.clone()
    }

    fn get<K: IntoIterator<Item = impl Into<String>>>(&self, keys: K) -> Option<Uuid> {
        let key_set: BTreeSet<String> = keys.into_iter().map(Into::into).collect();
        self.index.get(&key_set).copied()
    }

    pub fn find(&self, keys: &[&str]) -> Vec<Uuid> {
        self.index
            .iter()
            .filter(|(key_set, _)| {
                keys.iter().all(|k| key_set.contains(*k))
            })
            .map(|(_, uuid)| *uuid)
            .collect()
    }

    pub fn remove(&mut self, target_id: &Uuid) {
        let keys_to_remove: Vec<_> = self.index.iter()
            .filter(|(_, id)| id.eq(&target_id))
            .map(|(key, _)| key.clone())
            .collect();

        for key in keys_to_remove {
            self.index.remove(&key);
        }
    }
}

impl FileDbKey for MultiKeyIndex {
    fn file_db_key() -> String {
        String::from("index")
    }
}

#[cfg(test)]
#[path = "../tests/unit_tests/index.rs"]
pub mod test;