use serde::Serialize;
use serde_json::Value;
use std::{path::PathBuf, sync::Arc};
use tempfile::tempdir;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::{
error::NanoDBError,
trees::{tree::Tree, tree_read_guarded::ReadGuardedTree, tree_write_guarded::WriteGuardedTree},
};
#[derive(Debug)]
pub struct NanoDB {
path: PathBuf,
data: Arc<RwLock<Value>>,
}
impl NanoDB {
pub fn open(path: impl Into<PathBuf>) -> Result<Self, NanoDBError> {
let path = path.into();
let data = if path.exists() {
let contents = std::fs::read_to_string(&path)?;
serde_json::from_str(&contents)?
} else {
Value::Object(Default::default())
};
Ok(Self {
path,
data: Arc::new(RwLock::new(data)),
})
}
pub fn new_from(path: impl Into<PathBuf>, contents: &str) -> Result<Self, NanoDBError> {
let data = serde_json::from_str(contents)?;
let _path: PathBuf;
if cfg!(test) {
let tmp_dir = tempdir()?;
_path = tmp_dir.path().join("my_file.json");
} else {
_path = path.into();
std::fs::write(&_path, contents)?;
}
Ok(Self {
path: _path,
data: Arc::new(RwLock::new(data)),
})
}
pub async fn data(&self) -> Tree {
let data = self._read_lock().await;
Tree::new(data.clone(), vec![])
}
pub async fn read(&self) -> ReadGuardedTree<'_> {
let read_guard = self._read_lock().await;
let read_guard_value: Value = read_guard.clone();
ReadGuardedTree::new(read_guard, read_guard_value)
}
pub async fn update(&self) -> WriteGuardedTree<'_> {
let write_guard = self._write_lock().await;
let write_guard_value: Value = write_guard.clone();
WriteGuardedTree::new(write_guard, write_guard_value)
}
pub async fn insert<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), NanoDBError> {
let write_guard = self._write_lock().await;
let write_guard_value: Value = write_guard.clone();
let mut tree = WriteGuardedTree::new(write_guard, write_guard_value);
tree.insert(key, value)?;
Ok(())
}
pub async fn remove(&mut self, key: &str) -> Result<(), NanoDBError> {
let write_guard = self._write_lock().await;
let write_guard_value: Value = write_guard.clone();
let mut tree = WriteGuardedTree::new(write_guard, write_guard_value);
tree.remove(key)?;
Ok(())
}
pub async fn insert_tree(&mut self, other: Tree) -> Result<(), NanoDBError> {
let mut write_guard = self._write_lock().await;
let mut current_tree = Tree::new(write_guard.clone(), vec![]);
current_tree.merge_from(other)?;
*write_guard = current_tree.inner();
Ok(())
}
pub async fn write(&mut self) -> Result<(), NanoDBError> {
let path = self.path.clone();
let data_guard = self._write_lock().await;
let contents = serde_json::to_string_pretty(&*data_guard)?;
tokio::fs::write(path, contents).await?;
Ok(())
}
async fn _write_lock(&self) -> RwLockWriteGuard<'_, Value> {
self.data.write().await
}
async fn _read_lock(&self) -> RwLockReadGuard<'_, Value> {
self.data.read().await
}
}
impl Clone for NanoDB {
fn clone(&self) -> Self {
Self {
path: self.path.clone(),
data: self.data.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_new_from() {
let db = NanoDB::new_from("/path/to/file.json", r#"{"key": "value"}"#).unwrap();
assert_eq!(db.data().await.get("key").unwrap().inner(), json!("value"));
}
#[tokio::test]
async fn test_insert() {
let mut db = NanoDB::new_from("/path/to/file.json", r#"{}"#).unwrap();
db.insert("new_key", "new_value").await.unwrap();
assert_eq!(
db.data().await.get("new_key").unwrap().inner(),
json!("new_value")
);
}
#[tokio::test]
async fn test_get() {
let db = NanoDB::new_from("/path/to/file.json", r#"{"key": "value"}"#).unwrap();
let result = db.data().await.get("key").unwrap();
assert_eq!(result.inner(), json!("value"));
}
#[tokio::test]
async fn test_insert_tree() {
let mut db = NanoDB::new_from(
"/path/to/file.json",
r#"{"key": {"nested_key": "nested_value"}}"#,
)
.unwrap();
let mut tree = db.data().await.get("key").unwrap();
tree.insert("nested_key_2", "nested_value_2").unwrap();
db.insert_tree(tree).await.unwrap();
assert_eq!(
db.data()
.await
.get("key")
.unwrap()
.get("nested_key_2")
.unwrap()
.inner(),
json!("nested_value_2")
);
}
#[tokio::test]
async fn test_tree_remove() {
let mut db = NanoDB::new_from(
"/path/to/file.json",
r#"{"key": {"nested_key": "nested_value"}}"#,
)
.unwrap();
let mut tree = db.data().await.get("key").unwrap();
tree.remove("nested_key").unwrap();
db.insert_tree(tree).await.unwrap();
assert_eq!(db.data().await.get("key").unwrap().inner(), json!({}));
}
}