1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::fmt::Debug;
use std::fmt::format;
use std::fs::OpenOptions;
use std::io::Write;

use env_logger::{Builder, WriteStyle};
use env_logger::fmt::Formatter;
use log::{info, Level, LevelFilter, Record};
use serde::de::DeserializeOwned;
use serde::Serialize;
use sqlx::{Pool, Sqlite, SqlitePool};
use sqlx::{Execute, Executor, QueryBuilder, Row};

use crate::cache_error::{CacheError, MapError};
use crate::crud_ops::{CacheData, create_table, drop_table, insert_into_db, insert_into_db_if_not_exist, print_all_cache, select_all_from_cache, select_from_db};
use crate::map_data::{json_to_obj, obj_to_json};

pub mod cache_error;


fn init_log() {
    Builder::from_default_env()
        .filter(None, LevelFilter::Debug)
        .format(format_log_fn)
        .format_timestamp(None)
        .write_style(WriteStyle::Always)
        .init();
}

fn format_log_fn(buf: &mut Formatter, record: &Record) -> std::io::Result<()> {
    if record.level() == Level::Info {
        return Ok(());
    }
    writeln!(buf, "{}", record.args())
}

mod map_data;
mod crud_ops;

/// Main Struct for Cache
#[derive(Debug, Clone)]
pub struct Cache {
    conn_pool: Pool<Sqlite>,
}

impl Cache {
    ///build cache storage
    /// if in_memory = true, will create cache that will be erased after application closed
    /// if in_memory = false, will create a file in the project folder itself to store data
    pub async fn build(in_memory: bool, cache_file_name: &str) -> Self {
        OpenOptions::new()
            .write(true)  // Enable writing to the file.
            .create(true) // Create the file if it doesn't exist.
            .open(format!("{}.db", cache_file_name)).unwrap();

        let conn_pool = if in_memory {
            SqlitePool::connect("sqlite::memory:")
                .await
                .unwrap()
        } else {
            SqlitePool::connect(&format!("sqlite://{}.db", cache_file_name))
                .await
                .unwrap()
        };

        create_table(&conn_pool).await.expect("Could not able to create the Cache table");
        Cache { conn_pool }
    }

    /// save object with key, if already exist this will replace
    pub async fn save_obj<T>(&self, key: &str, obj: &T) -> Result<(), CacheError>
        where T: ?Sized + Serialize + DeserializeOwned + Debug {
        let content = obj_to_json(obj)?;
        insert_into_db(&self.conn_pool, key, &content).await?;
        Ok(())
    }

    /// save object with key, if already exist this will be ignored
    pub async fn save_obj_if_not_exist<T>(&self, key: &str, obj: &T) -> Result<(), CacheError>
        where T: ?Sized + Serialize + DeserializeOwned + Debug {
        let content = obj_to_json(obj)?;
        insert_into_db_if_not_exist(&self.conn_pool, key, &content).await?;
        Ok(())
    }


    /// will retrieve the object for the key
    pub async fn get_obj<T>(&self, key: &str) -> Result<T, CacheError>
        where T: ?Sized + Serialize + DeserializeOwned + Debug {
        let cache: CacheData = select_from_db(&self.conn_pool, key).await?;
        let res = json_to_obj::<T>(key, &cache.content)?;
        Ok(res)
    }

    /// get all saved objects from the Cache
    pub async fn get_all_objs(&self) -> Result<Vec<CacheData>, CacheError> {
        let cache_list = select_all_from_cache(&self.conn_pool).await?;
        Ok(cache_list)
    }

    pub async fn pretty_print_all_cache(&self) {
        let cache_list = self.get_all_objs().await.unwrap();
        print_all_cache(&cache_list);
    }
    /// clears all cache data
    pub async fn clear_cache(&self) {
        drop_table(&self.conn_pool).await.expect("Could not drop table to clear previous server session");
    }
}

#[cfg(test)]
mod tests {
    use log::debug;
    use serde::{Deserialize, Serialize};

    use super::*;

    #[derive(Serialize, Deserialize, Debug)]
    struct TestStruct {
        name: String,
        email: String,
        ph_no: u64,
    }

    #[tokio::test]
    async fn insert_and_retrieve_from_cache() {
        init_log();

        let cache = Cache::build(true, "obj_cache").await;

        let data = TestStruct {
            name: "dinesh".to_owned(),
            email: "dinesh".to_owned(),
            ph_no: 9999999999u64,
        };

        cache.save_obj("TestData", &data).await.unwrap();

        cache.pretty_print_all_cache();

        let cached_data: TestStruct = cache.get_obj("TestData").await.unwrap();
        debug!("\n\ndeserialized data 'TestStruct' from cache ->\n\n{:?}\n", cached_data);
        cache.clear_cache();
    }
}