169 lines
5.7 KiB
Rust
169 lines
5.7 KiB
Rust
use camino::{Utf8Path, Utf8PathBuf};
|
|
use serde_json::Error as JsonError;
|
|
|
|
use std::fmt;
|
|
use std::io::Error as IOError;
|
|
|
|
use crate::db::error::{BoxedError, DomainCreationError, UserCreationError};
|
|
use crate::db::{Database, DatabaseInterface, Domain, MemoryDatabase, User, UserRef};
|
|
|
|
#[derive(Debug)]
|
|
pub enum FilesystemDatabaseError {
|
|
ReadFileIO(Utf8PathBuf, IOError),
|
|
ReadFileJson(Utf8PathBuf, JsonError),
|
|
WriteFileIO(Utf8PathBuf, IOError),
|
|
WriteFileJson(Utf8PathBuf, JsonError),
|
|
}
|
|
|
|
impl fmt::Display for FilesystemDatabaseError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match self {
|
|
Self::ReadFileIO(path, e) => format!("Failed to read database file {path}: {e}"),
|
|
Self::ReadFileJson(path, e) =>
|
|
format!("Failed to parse database JSON file {path}: {e}"),
|
|
Self::WriteFileIO(path, e) => format!("Failed to write database file {path}: {e}"),
|
|
Self::WriteFileJson(path, e) =>
|
|
format!("Failed to convert database to JSON file {path}: {e}"),
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for FilesystemDatabaseError {}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct FilesystemDatabase {
|
|
// TODO: Once we have common validation steps in place across DB backends, we can reduce cloning.
|
|
// For now, we clone the DB on every write operation, and update it when saving to disk
|
|
// succeeds.
|
|
pub inner: MemoryDatabase,
|
|
pub path: Utf8PathBuf,
|
|
}
|
|
|
|
impl FilesystemDatabase {
|
|
pub async fn from_path(path: impl AsRef<Utf8Path>) -> Result<Database, BoxedError> {
|
|
let path = path.as_ref();
|
|
|
|
if !tokio::fs::try_exists(path)
|
|
.await
|
|
.map_err(|e| Box::new(FilesystemDatabaseError::ReadFileIO(path.to_path_buf(), e)))?
|
|
{
|
|
// The database doesn't exist yet, we create an empty one and try to write it
|
|
// to make sure the permissions are correct and the destination folder exists.
|
|
let mut db = Self {
|
|
inner: MemoryDatabase::default(),
|
|
path: path.to_path_buf(),
|
|
};
|
|
|
|
tracing::info!("Initializing empty database in file {path}. Checking permissions...");
|
|
db.save_self().await?;
|
|
tracing::info!("Database successfully created");
|
|
|
|
return Ok(Database::new(db));
|
|
}
|
|
|
|
tracing::info!("Loading database from file {path}");
|
|
let s = tokio::fs::read(path)
|
|
.await
|
|
.map_err(|e| Box::new(FilesystemDatabaseError::ReadFileIO(path.to_path_buf(), e)))?;
|
|
let inner: MemoryDatabase = serde_json::from_slice(&s)
|
|
.map_err(|e| Box::new(FilesystemDatabaseError::ReadFileJson(path.to_path_buf(), e)))?;
|
|
Ok(Database::new(Self {
|
|
inner,
|
|
path: path.to_path_buf(),
|
|
}))
|
|
}
|
|
|
|
/// Attempts to save a new state of the DB, effectively switching to the new state
|
|
/// only if the save is successful.
|
|
pub async fn save(&mut self, new_db: MemoryDatabase) -> Result<(), BoxedError> {
|
|
self.save_inner(&new_db).await?;
|
|
self.inner = new_db;
|
|
Ok(())
|
|
}
|
|
|
|
/// Saves an in-memory DB to disk. Cannot be used directly to observe exclusive access.
|
|
async fn save_inner(&self, inner: &MemoryDatabase) -> Result<(), BoxedError> {
|
|
let s = serde_json::to_string(inner)
|
|
.map_err(|e| Box::new(FilesystemDatabaseError::WriteFileJson(self.path.clone(), e)))?;
|
|
Ok(tokio::fs::write(&self.path, &s)
|
|
.await
|
|
.map_err(|e| Box::new(FilesystemDatabaseError::WriteFileIO(self.path.clone(), e)))?)
|
|
}
|
|
|
|
/// Save the database without switching to a new state.
|
|
///
|
|
/// Used when initializing the DB, to check for permissions.
|
|
pub async fn save_self(&mut self) -> Result<(), BoxedError> {
|
|
self.save_inner(&self.inner).await
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl DatabaseInterface for FilesystemDatabase {
|
|
async fn create_domain(
|
|
&mut self,
|
|
domain: &str,
|
|
) -> Result<Result<(), DomainCreationError>, BoxedError> {
|
|
let mut new_db = self.inner.clone();
|
|
|
|
if let Err(e) = new_db.create_domain(domain).await? {
|
|
return Ok(Err(e));
|
|
}
|
|
|
|
self.save(new_db).await?;
|
|
Ok(Ok(()))
|
|
}
|
|
|
|
async fn get_domain(&self, domain: &str) -> Result<Option<Domain>, BoxedError> {
|
|
self.inner.get_domain(domain).await
|
|
}
|
|
|
|
async fn get_user(&self, req_user: &UserRef) -> Result<Option<User>, BoxedError> {
|
|
self.inner.get_user(req_user).await
|
|
}
|
|
|
|
async fn create_user(
|
|
&mut self,
|
|
user: User,
|
|
) -> Result<Result<(), UserCreationError>, BoxedError> {
|
|
let mut new_db = self.inner.clone();
|
|
|
|
if let Err(e) = new_db.create_user(user).await? {
|
|
return Ok(Err(e));
|
|
}
|
|
|
|
self.save(new_db).await?;
|
|
Ok(Ok(()))
|
|
}
|
|
|
|
async fn try_create_user(
|
|
&mut self,
|
|
new_user: User,
|
|
current_user: &User,
|
|
) -> Result<Result<(), UserCreationError>, BoxedError> {
|
|
let mut new_db = self.inner.clone();
|
|
|
|
if let Err(e) = new_db.try_create_user(new_user, current_user).await? {
|
|
return Ok(Err(e));
|
|
}
|
|
|
|
self.save(new_db).await?;
|
|
Ok(Ok(()))
|
|
}
|
|
|
|
async fn list_all_domains(&self) -> Result<Vec<Domain>, BoxedError> {
|
|
self.inner.list_all_domains().await
|
|
}
|
|
|
|
async fn list_all_users(&self) -> Result<Vec<User>, BoxedError> {
|
|
self.inner.list_all_users().await
|
|
}
|
|
|
|
async fn list_domain_users(&self, domain: Option<String>) -> Result<Vec<User>, BoxedError> {
|
|
self.inner.list_domain_users(domain).await
|
|
}
|
|
}
|