object_cache/
lib.rs

1use std::fmt::Debug;
2use std::fmt::format;
3use std::fs::OpenOptions;
4use std::io::Write;
5
6use env_logger::{Builder, WriteStyle};
7use env_logger::fmt::Formatter;
8use log::{debug, info, Level, LevelFilter, Record};
9use serde::de::DeserializeOwned;
10use serde::Serialize;
11use sqlx::{Pool, Sqlite, SqlitePool};
12use sqlx::{Execute, Executor, QueryBuilder, Row};
13
14use crate::cache_error::{CacheError, MapError};
15use 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};
16use crate::map_data::{json_to_obj, obj_to_json};
17
18pub mod cache_error;
19
20
21fn init_log() {
22    let result = Builder::from_default_env()
23        .filter(None, LevelFilter::Debug)
24        .format(format_log_fn)
25        .format_timestamp(None)
26        .write_style(WriteStyle::Always)
27        .try_init();
28    debug!("logger init - {:?}", result)
29}
30
31fn format_log_fn(buf: &mut Formatter, record: &Record) -> std::io::Result<()> {
32    if record.level() == Level::Info {
33        return Ok(());
34    }
35    writeln!(buf, "{}", record.args())
36}
37
38mod map_data;
39mod crud_ops;
40
41/// Main Struct for Cache
42#[derive(Debug, Clone)]
43pub struct Cache {
44    conn_pool: Pool<Sqlite>,
45}
46
47impl Cache {
48    ///build cache storage
49    /// if in_memory = true, will create cache that will be erased after application closed
50    /// if in_memory = false, will create a file in the project folder itself to store data
51    pub async fn build(in_memory: bool, cache_file_name: &str) -> Self {
52        OpenOptions::new()
53            .write(true)  // Enable writing to the file.
54            .create(true) // Create the file if it doesn't exist.
55            .open(format!("{}.db", cache_file_name)).unwrap();
56
57        let conn_pool = if in_memory {
58            SqlitePool::connect("sqlite::memory:")
59                .await
60                .unwrap()
61        } else {
62            SqlitePool::connect(&format!("sqlite://{}.db", cache_file_name))
63                .await
64                .unwrap()
65        };
66
67        create_table(&conn_pool).await.expect("Could not able to create the Cache table");
68        Cache { conn_pool }
69    }
70
71    pub async fn build_simple(cache_file_name: Option<String>) -> Result<Cache, CacheError> {
72        let conn_pool: Pool<Sqlite> = match cache_file_name {
73            None => {
74                SqlitePool::connect("sqlite::memory:")
75                    .await
76                    .unwrap()
77            }
78            Some(name) => {
79                let file_path = format!("{}.db", name);
80                let error_msg = format!("Couldn't create or open file: {}", &file_path);
81                OpenOptions::new()
82                    .write(true)  // Enable writing to the file.
83                    .create(true) // Create the file if it doesn't exist.
84                    .open(&file_path)
85                    .map_to_cache_error(&error_msg)?;
86
87                SqlitePool::connect(&format!("sqlite://{}", file_path))
88                    .await
89                    .unwrap()
90            }
91        };
92        create_table(&conn_pool).await.expect("Could not able to create the Cache table");
93        Ok(Cache { conn_pool })
94    }
95
96    /// save object with key, if already exist this will replace
97    pub async fn save_obj<T>(&self, key: &str, obj: &T) -> Result<(), CacheError>
98        where T: ?Sized + Serialize + DeserializeOwned + Debug {
99        let content = obj_to_json(obj)?;
100        insert_into_db(&self.conn_pool, key, &content).await?;
101        Ok(())
102    }
103
104    /// save object with key, if already exist this will be ignored
105    pub async fn save_obj_if_not_exist<T>(&self, key: &str, obj: &T) -> Result<(), CacheError>
106        where T: ?Sized + Serialize + DeserializeOwned + Debug {
107        let content = obj_to_json(obj)?;
108        insert_into_db_if_not_exist(&self.conn_pool, key, &content).await?;
109        Ok(())
110    }
111
112    /// will retrieve the object for the key
113    pub async fn get_obj<T>(&self, key: &str) -> Result<T, CacheError>
114        where T: ?Sized + Serialize + DeserializeOwned + Debug {
115        let cache: CacheData = select_from_db(&self.conn_pool, key).await?;
116        let res = json_to_obj::<T>(key, &cache.content)?;
117        Ok(res)
118    }
119
120    /// get all saved objects from the Cache
121    pub async fn get_all_objs(&self) -> Result<Vec<CacheData>, CacheError> {
122        let cache_list = select_all_from_cache(&self.conn_pool).await?;
123        Ok(cache_list)
124    }
125
126    pub async fn pretty_print_all_cache(&self) {
127        let cache_list = self.get_all_objs().await.unwrap();
128        print_all_cache(&cache_list);
129    }
130    /// clears all cache data
131    pub async fn clear_cache(&self) {
132        drop_table(&self.conn_pool).await.expect("Could not drop table to clear previous server session");
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use log::debug;
139    use serde::{Deserialize, Serialize};
140
141    use super::*;
142
143    #[derive(Serialize, Deserialize, Debug)]
144    struct TestStruct {
145        name: String,
146        email: String,
147        ph_no: u64,
148    }
149
150    #[tokio::test]
151    async fn insert_and_retrieve_from_cache() {
152        init_log();
153
154        let cache = Cache::build_simple(None).await.unwrap();
155
156        let data = TestStruct {
157            name: "dinesh".to_owned(),
158            email: "dinesh".to_owned(),
159            ph_no: 9999999999u64,
160        };
161
162        cache.save_obj("TestData", &data).await.unwrap();
163
164        cache.pretty_print_all_cache();
165
166        let cached_data: TestStruct = cache.get_obj("TestData").await.unwrap();
167        debug!("\n\ndeserialized data 'TestStruct' from cache ->\n\n{:?}\n", cached_data);
168        cache.clear_cache();
169    }
170
171    #[tokio::test]
172    async fn test_cache_retrieval() {
173        init_log();
174
175        let cache = Cache::build_simple(None).await.unwrap();
176
177        let data = TestStruct {
178            name: "dinesh".to_owned(),
179            email: "dinesh".to_owned(),
180            ph_no: 9999999999u64,
181        };
182
183        cache.save_obj("TestData", &data).await.unwrap();
184
185        cache.pretty_print_all_cache();
186
187        let cached_data: TestStruct = cache.get_obj("TestData").await.unwrap();
188        debug!("\n\ndeserialized data 'TestStruct' from cache ->\n\n{:?}\n", cached_data);
189        cache.clear_cache();
190    }
191}