145 lines
4.4 KiB
Rust
145 lines
4.4 KiB
Rust
use camino::{Utf8Path, Utf8PathBuf};
|
|
use serde::{Deserialize, Serialize};
|
|
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, Deserialize, Serialize)]
|
|
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<Self>, BoxedError> {
|
|
let path = path.as_ref();
|
|
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(),
|
|
}))
|
|
}
|
|
|
|
pub async fn save(&mut self) -> Result<(), BoxedError> {
|
|
let s = serde_json::to_string(&self)
|
|
.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)))?)
|
|
}
|
|
}
|
|
|
|
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().await?;
|
|
self.inner = new_db;
|
|
|
|
Ok(Ok(()))
|
|
}
|
|
|
|
fn get_domain(&self, domain: &str) -> impl Future<Output = Result<Option<Domain>, BoxedError>> {
|
|
self.inner.get_domain(domain)
|
|
}
|
|
|
|
fn get_user(
|
|
&self,
|
|
req_user: &UserRef,
|
|
) -> impl Future<Output = Result<Option<User>, BoxedError>> {
|
|
self.inner.get_user(req_user)
|
|
}
|
|
|
|
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().await?;
|
|
self.inner = new_db;
|
|
|
|
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().await?;
|
|
self.inner = new_db;
|
|
|
|
Ok(Ok(()))
|
|
}
|
|
|
|
fn list_all_domains(&self) -> impl Future<Output = Result<Vec<Domain>, BoxedError>> {
|
|
self.inner.list_all_domains()
|
|
}
|
|
|
|
fn list_all_users(&self) -> impl Future<Output = Result<Vec<User>, BoxedError>> {
|
|
self.inner.list_all_users()
|
|
}
|
|
|
|
fn list_domain_users(
|
|
&self,
|
|
domain: Option<String>,
|
|
) -> impl Future<Output = Result<Vec<User>, BoxedError>> {
|
|
self.inner.list_domain_users(domain)
|
|
}
|
|
}
|