use std::path::{Path, PathBuf};
use miette::{Context, IntoDiagnostic};
use tokio::fs;
use crate::errors::FileExistsError;
#[async_trait::async_trait]
pub trait File: Sized + Send + Sync + 'static {
const DEFAULT_PATH: &str;
fn resolve<P>(path: P) -> miette::Result<PathBuf>
where
P: AsRef<Path> + Send + Sync,
{
let path = path.as_ref();
if path.is_file() {
return Ok(path.to_path_buf());
}
if path.is_dir() {
return Ok(path.join(Self::DEFAULT_PATH));
}
let parent = path.parent().ok_or(miette::miette!(
"Parent not resolvable for {}",
path.display()
))?;
Ok(parent.join(Self::DEFAULT_PATH))
}
async fn exists() -> miette::Result<bool> {
Self::exists_at(Self::DEFAULT_PATH).await
}
async fn exists_at<P>(path: P) -> miette::Result<bool>
where
P: AsRef<Path> + Send + Sync,
{
let resolved = Self::resolve(path)?;
fs::try_exists(resolved)
.await
.into_diagnostic()
.wrap_err(FileExistsError(Self::DEFAULT_PATH))
}
async fn load() -> miette::Result<Self> {
Self::load_from(Self::DEFAULT_PATH).await
}
async fn load_from<P>(path: P) -> miette::Result<Self>
where
P: AsRef<Path> + Send + Sync;
async fn load_or_default() -> miette::Result<Self>
where
Self: Default,
{
if Self::exists().await? {
Self::load().await
} else {
Ok(Self::default())
}
}
async fn load_from_or_default<P>(path: P) -> miette::Result<Self>
where
Self: Default,
P: AsRef<Path> + Send + Sync,
{
let resolved = Self::resolve(path)?;
if Self::exists_at(&resolved).await? {
Self::load_from(resolved).await
} else {
Ok(Self::default())
}
}
async fn save_to<P>(&self, path: P) -> miette::Result<()>
where
P: AsRef<Path> + Send + Sync;
}