just_orm/
lib.rs

1use lazy_static::lazy_static;
2use serde::{Deserialize, Serialize};
3use serde_json::{from_str, to_string_pretty, Value};
4use std::fs::{self, File};
5use std::io::{Read, Write};
6use std::path::{Path, PathBuf};
7use std::sync::Mutex;
8
9lazy_static! {
10    static ref BASE_DIR: Mutex<PathBuf> = Mutex::new(PathBuf::new());
11}
12
13/// A simple JSON file-based database ORM for Rust.
14#[derive(Debug, Clone)]
15pub struct JsonDatabase<T> {
16    current_model_name: Option<String>,
17    _marker: std::marker::PhantomData<T>,
18}
19
20impl<T> JsonDatabase<T>
21where
22    T: Serialize + for<'de> Deserialize<'de> + Clone + Identifiable,
23{
24    /// Creates a new `JsonDatabase` instance.
25    ///
26    /// # Arguments
27    ///
28    /// * `model_name` - Optional model name to initialize the database with.
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// let db: JsonDatabase<User> = JsonDatabase::new(Some("users"));
34    /// ```
35    pub fn new(model_name: Option<&str>) -> Self {
36        JsonDatabase {
37            current_model_name: model_name.map(String::from),
38            _marker: std::marker::PhantomData,
39        }
40    }
41
42    /// Sets the model name for the database and ensures the directory exists.
43    ///
44    /// # Arguments
45    ///
46    /// * `model_name` - The name of the model to use.
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// let mut db: JsonDatabase<User> = JsonDatabase::new(None);
52    /// db.model("users");
53    /// ```
54    pub fn model(&mut self, model_name: &str) -> &mut Self {
55        self.current_model_name = Some(model_name.to_string());
56        create_directory_if_not_exists(&self.get_model_path(model_name));
57        self
58    }
59
60    /// Returns the base path for the database.
61    fn base_path() -> PathBuf {
62        BASE_DIR.lock().unwrap().clone()
63    }
64
65    /// Returns the path to the model directory.
66    ///
67    /// # Arguments
68    ///
69    /// * `model_name` - The name of the model.
70    fn get_model_path(&self, model_name: &str) -> PathBuf {
71        Self::base_path().join(model_name)
72    }
73
74    /// Returns the path to a specific file in the model directory.
75    ///
76    /// # Arguments
77    ///
78    /// * `id` - The ID of the file.
79    fn get_file_path(&self, id: &str) -> PathBuf {
80        let model_name = self
81            .current_model_name
82            .as_ref()
83            .expect("Model name is not specified");
84        self.get_model_path(model_name).join(format!("{}.json", id))
85    }
86
87    /// Returns a list of all JSON files in the model directory.
88    fn get_all_files(&self) -> Vec<String> {
89        let model_name = self
90            .current_model_name
91            .as_ref()
92            .expect("Model name is not specified");
93        let model_path = self.get_model_path(model_name);
94        create_directory_if_not_exists(&model_path);
95
96        fs::read_dir(model_path)
97            .expect("Unable to read directory")
98            .filter_map(|entry| {
99                let entry = entry.expect("Unable to get directory entry");
100                let path = entry.path();
101                if path.extension().map_or(false, |ext| ext == "json") {
102                    path.file_name()
103                        .map(|name| name.to_string_lossy().into_owned())
104                } else {
105                    None
106                }
107            })
108            .collect()
109    }
110
111    /// Checks if a JSON object matches a given condition.
112    ///
113    /// # Arguments
114    ///
115    /// * `item` - The JSON object to check.
116    /// * `condition` - The condition to match against.
117    fn matches_condition(&self, item: &Value, condition: &Value) -> bool {
118        if !condition.is_object() || condition.is_null() {
119            return item == condition;
120        }
121        if !item.is_object() || item.is_null() {
122            return false;
123        }
124
125        condition.as_object().unwrap().iter().all(|(key, value)| {
126            let keys: Vec<&str> = key.split('.').collect();
127            let nested_value = self.get_nested_property(item, &keys);
128            if nested_value.is_object() && value.is_object() {
129                self.matches_condition(nested_value, value)
130            } else {
131                nested_value == value
132            }
133        })
134    }
135
136    /// Gets a nested property from a JSON object.
137    ///
138    /// # Arguments
139    ///
140    /// * `obj` - The JSON object.
141    /// * `keys` - The keys to the nested property.
142    fn get_nested_property<'a>(&self, obj: &'a Value, keys: &[&str]) -> &'a Value {
143        keys.iter()
144            .fold(obj, |acc, key| acc.get(*key).unwrap_or(&Value::Null))
145    }
146
147    /// Sets a nested property in a JSON object.
148    ///
149    /// # Arguments
150    ///
151    /// * `obj` - The JSON object.
152    /// * `keys` - The keys to the nested property.
153    /// * `value` - The value to set.
154    fn set_nested_property(&self, obj: &mut Value, keys: &[&str], value: Value) {
155        if keys.len() == 1 {
156            obj[keys[0]] = value;
157        } else {
158            let key = keys[0];
159            let next_obj = obj
160                .as_object_mut()
161                .unwrap()
162                .entry(key)
163                .or_insert_with(|| Value::Object(Default::default()));
164            self.set_nested_property(next_obj, &keys[1..], value);
165        }
166    }
167
168    /// Updates a nested property in a JSON object.
169    ///
170    /// # Arguments
171    ///
172    /// * `target` - The JSON object to update.
173    /// * `source` - The source JSON object containing updates.
174    fn update_nested_object(&self, target: &mut Value, source: &Value) {
175        for (key, value) in source.as_object().unwrap().iter() {
176            let keys: Vec<&str> = key.split('.').collect();
177            self.set_nested_property(target, &keys, value.clone());
178        }
179    }
180
181    /// Creates a new model in the database.
182    ///
183    /// # Arguments
184    ///
185    /// * `data` - The data to create.
186    ///
187    /// # Panics
188    ///
189    /// Panics if the data does not have an ID field.
190    pub fn create_model(&self, data: T) {
191        let id = data.get_id();
192        if id.is_empty() {
193            panic!("Data must have an id field");
194        }
195        self.create(&id, data);
196    }
197
198    /// Creates a new record in the database.
199    ///
200    /// # Arguments
201    ///
202    /// * `id` - The ID of the record.
203    /// * `data` - The data to create.
204    pub fn create(&self, id: &str, data: T) {
205        let file_path = self.get_file_path(id);
206        create_directory_if_not_exists(file_path.parent().unwrap()); // Ensure the directory exists
207        write_json_file(&file_path, &data);
208    }
209
210    /// Finds a record by ID.
211    ///
212    /// # Arguments
213    ///
214    /// * `id` - The ID of the record to find.
215    ///
216    /// # Returns
217    ///
218    /// Returns `Some(T)` if the record is found, or `None` if not.
219    pub fn find_by_id(&self, id: &str) -> Option<T> {
220        let file_path = self.get_file_path(id);
221        read_json_file(&file_path)
222    }
223
224    /// Updates a record by ID.
225    ///
226    /// # Arguments
227    ///
228    /// * `id` - The ID of the record to update.
229    /// * `data` - The data to update.
230    pub fn update_by_id(&self, id: &str, data: Value) {
231        let file_path = self.get_file_path(id);
232
233        if let Some(mut existing_data) = self.find_by_id(id) {
234            let mut existing_json = serde_json::to_value(&existing_data).unwrap();
235
236            self.update_nested_object(&mut existing_json, &data);
237            let updated_data: T = serde_json::from_value(existing_json).unwrap();
238            write_json_file(&file_path, &updated_data);
239        }
240    }
241
242    /// Deletes a record by ID.
243    ///
244    /// # Arguments
245    ///
246    /// * `id` - The ID of the record to delete.
247    pub fn delete_by_id(&self, id: &str) {
248        let file_path = self.get_file_path(id);
249        if file_path.exists() {
250            fs::remove_file(file_path).expect("Unable to delete file");
251        }
252    }
253
254    /// Finds all records.
255    ///
256    /// # Returns
257    ///
258    /// Returns a vector of all records.
259    pub fn find_all(&self) -> Vec<T> {
260        let files = self.get_all_files();
261        files
262            .into_iter()
263            .filter_map(|file| {
264                read_json_file(
265                    &self
266                        .get_model_path(self.current_model_name.as_ref().unwrap())
267                        .join(file),
268                )
269            })
270            .collect()
271    }
272
273    /// Finds records matching a condition.
274    ///
275    /// # Arguments
276    ///
277    /// * `condition` - The condition to match.
278    ///
279    /// # Returns
280    ///
281    /// Returns a vector of matching records.
282    pub fn find(&self, condition: &Value) -> Vec<T> {
283        self.find_all()
284            .into_iter()
285            .filter(|item| self.matches_condition(&serde_json::to_value(item).unwrap(), condition))
286            .collect()
287    }
288
289    /// Finds the first record matching a condition.
290    ///
291    /// # Arguments
292    ///
293    /// * `condition` - The condition to match.
294    ///
295    /// # Returns
296    ///
297    /// Returns `Some(T)` if a matching record is found, or `None` if not.
298    pub fn find_one(&self, condition: &Value) -> Option<T> {
299        self.find_all()
300            .into_iter()
301            .find(|item| self.matches_condition(&serde_json::to_value(item).unwrap(), condition))
302    }
303
304    /// Counts the number of records matching a condition.
305    ///
306    /// # Arguments
307    ///
308    /// * `condition` - The condition to match.
309    ///
310    /// # Returns
311    ///
312    /// Returns the number of matching records.
313    pub fn count(&self, condition: &Value) -> usize {
314        self.find(condition).len()
315    }
316
317    /// Updates multiple records matching a condition.
318    ///
319    /// # Arguments
320    ///
321    /// * `condition` - The condition to match.
322    /// * `data` - The data to update.
323    pub fn update_many(&self, condition: &Value, data: &Value) {
324        let items = self.find(condition);
325        for item in items {
326            let id = item.get_id();
327            self.update_by_id(&id, data.clone());
328        }
329    }
330
331    /// Deletes multiple records matching a condition.
332    ///
333    /// # Arguments
334    ///
335    /// * `condition` - The condition to match.
336    pub fn delete_many(&self, condition: &Value) {
337        let items = self.find(condition);
338        for item in items {
339            let id = item.get_id();
340            self.delete_by_id(&id);
341        }
342    }
343
344    /// Adds an element to an array in records matching a condition.
345    ///
346    /// # Arguments
347    ///
348    /// * `condition` - The condition to match.
349    /// * `array_path` - The path to the array.
350    /// * `element` - The element to add.
351    pub fn push(&self, condition: &Value, array_path: &str, element: &Value) {
352        let items = self.find(condition);
353        for item in items {
354            let id = item.get_id();
355            if let Some(data) = self.find_by_id(&id) {
356                let mut data_json = serde_json::to_value(&data).unwrap();
357                let keys: Vec<&str> = array_path.split('.').collect();
358                let array = self.get_nested_property(&data_json, &keys);
359                if array.is_array() {
360                    let mut array = array.as_array().unwrap().clone();
361                    array.push(element.clone());
362                    self.set_nested_property(&mut data_json, &keys, Value::Array(array));
363                    let updated_data: T = serde_json::from_value(data_json).unwrap();
364                    self.update_by_id(&id, serde_json::to_value(updated_data).unwrap());
365                }
366            }
367        }
368    }
369
370    /// Removes elements from an array in records matching a condition.
371    ///
372    /// # Arguments
373    ///
374    /// * `condition` - The condition to match.
375    /// * `array_path` - The path to the array.
376    /// * `pull_condition` - The condition to match elements to remove.
377    pub fn pull(&self, condition: &Value, array_path: &str, pull_condition: &Value) {
378        let items = self.find(condition);
379        for item in items {
380            let id = item.get_id();
381            if let Some(data) = self.find_by_id(&id) {
382                let mut data_json = serde_json::to_value(&data).unwrap();
383                let keys: Vec<&str> = array_path.split('.').collect();
384                let array = self.get_nested_property(&data_json, &keys);
385                if array.is_array() {
386                    let new_array: Vec<Value> = array
387                        .as_array()
388                        .unwrap()
389                        .iter()
390                        .cloned()
391                        .filter(|elem| !self.matches_condition(elem, pull_condition))
392                        .collect();
393                    self.set_nested_property(&mut data_json, &keys, Value::Array(new_array));
394                    let updated_data: T = serde_json::from_value(data_json).unwrap();
395                    self.update_by_id(&id, serde_json::to_value(updated_data).unwrap());
396                }
397            }
398        }
399    }
400
401    /// Updates elements in an array in records matching a condition.
402    ///
403    /// # Arguments
404    ///
405    /// * `condition` - The condition to match.
406    /// * `array_path` - The path to the array.
407    /// * `array_condition` - The condition to match array elements.
408    /// * `updates` - The updates to apply to matching elements.
409    pub fn update_array(
410        &self,
411        condition: &Value,
412        array_path: &str,
413        array_condition: &Value,
414        updates: &Value,
415    ) {
416        let items = self.find(condition);
417        for item in items {
418            let id = item.get_id();
419            if let Some(data) = self.find_by_id(&id) {
420                let mut data_json = serde_json::to_value(&data).unwrap();
421                let keys: Vec<&str> = array_path.split('.').collect();
422                let array = self.get_nested_property(&data_json, &keys);
423                if array.is_array() {
424                    let new_array: Vec<Value> = array
425                        .as_array()
426                        .unwrap()
427                        .iter()
428                        .cloned()
429                        .map(|elem| {
430                            if self.matches_condition(&elem, array_condition) {
431                                let mut updated_elem = elem.clone();
432                                self.update_nested_object(&mut updated_elem, updates);
433                                updated_elem
434                            } else {
435                                elem
436                            }
437                        })
438                        .collect();
439                    self.set_nested_property(&mut data_json, &keys, Value::Array(new_array));
440                    let updated_data: T = serde_json::from_value(data_json).unwrap();
441                    self.update_by_id(&id, serde_json::to_value(updated_data).unwrap());
442                }
443            }
444        }
445    }
446}
447
448impl JsonDatabase<()> {
449    /// Sets the base directory for the database.
450    ///
451    /// # Arguments
452    ///
453    /// * `base_dir` - The base directory for the database.
454    ///
455    /// # Examples
456    ///
457    /// ```
458    /// JsonDatabase::set_base_dir("custom-dir");
459    /// ```
460    pub fn set_base_dir(base_dir: &str) {
461        let mut base_path = BASE_DIR.lock().unwrap();
462        *base_path = Path::new(base_dir).to_path_buf();
463        create_directory_if_not_exists(&base_path);
464    }
465}
466
467/// Creates a directory if it does not exist.
468///
469/// # Arguments
470///
471/// * `path` - The path of the directory to create.
472fn create_directory_if_not_exists(path: &Path) {
473    if !path.exists() {
474        fs::create_dir_all(path).expect("Unable to create directory");
475    }
476}
477
478/// Reads a JSON file and deserializes it into a Rust struct.
479///
480/// # Arguments
481///
482/// * `path` - The path of the JSON file to read.
483///
484/// # Returns
485///
486/// Returns `Some(T)` if successful, or `None` if there is an error.
487fn read_json_file<T>(path: &Path) -> Option<T>
488where
489    T: for<'de> Deserialize<'de>,
490{
491    let mut file = File::open(path).ok()?;
492    let mut contents = String::new();
493    file.read_to_string(&mut contents).ok()?;
494    from_str(&contents).ok()
495}
496
497/// Serializes a Rust struct into JSON and writes it to a file.
498///
499/// # Arguments
500///
501/// * `path` - The path of the file to write.
502/// * `data` - The data to serialize and write.
503fn write_json_file<T>(path: &Path, data: &T)
504where
505    T: Serialize,
506{
507    let mut file = File::create(path).expect("Unable to create file");
508    let contents = to_string_pretty(data).expect("Unable to serialize data");
509    file.write_all(contents.as_bytes())
510        .expect("Unable to write to file");
511}
512
513/// A trait for types that have an ID.
514pub trait Identifiable {
515    /// Returns the ID of the object.
516    fn get_id(&self) -> String;
517}