llldap/src/db/filesystem.rs

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)
}
}