use {
crate::Uri,
error_stack::{
Context,
Report,
},
std::{
io,
path::PathBuf,
},
};
#[derive(thiserror::Error, Debug, Clone)]
pub enum FsError {
#[error("I/O error: {0}")]
Io(String),
#[error("Uri error: {0}")]
Uri(String),
#[error("Path conversion error: {0}")]
PathConversion(String),
#[error("Invalid glob pattern: {0}")]
InvalidGlob(String),
#[error("File system error: {0}")]
FileSystem(String),
#[error("Invalid operation: {0}")]
InvalidOperation(String),
#[error("Not implemented: {0}")]
NotImplemented(String),
#[error("No filesystem found")]
NoFileSystem,
}
pub type Result<T> = std::result::Result<T, Report<FsError>>;
impl FsError {
pub fn io_error(message: impl Into<String>) -> Self {
FsError::Io(message.into())
}
pub fn uri_error(message: impl Into<String>) -> Self {
FsError::Uri(message.into())
}
pub fn path_conversion_error(message: impl Into<String>) -> Self {
FsError::PathConversion(message.into())
}
pub fn invalid_glob(message: impl Into<String>) -> Self {
FsError::InvalidGlob(message.into())
}
pub fn filesystem_error(message: impl Into<String>) -> Self {
FsError::FileSystem(message.into())
}
pub fn invalid_operation(message: impl Into<String>) -> Self {
FsError::InvalidOperation(message.into())
}
pub fn not_implemented(message: impl Into<String>) -> Self {
FsError::NotImplemented(message.into())
}
}
pub fn io_err(error: io::Error) -> Report<FsError> {
Report::new(FsError::Io(error.to_string()))
.attach_printable(format!("I/O error: {error}"))
}
pub fn uri_err(error: fluent_uri::error::ParseError) -> Report<FsError> {
Report::new(FsError::Uri(error.to_string()))
.attach_printable(format!("Uri parse error: {error}"))
}
#[derive(Debug, Clone)]
pub struct FilePath(pub PathBuf);
#[derive(Debug, Clone)]
pub struct UriPath(pub Uri);
pub trait IoErrorExt {
fn to_fs_error(self) -> Report<FsError>;
fn to_fs_error_with_path(self, path: impl Into<PathBuf>) -> Report<FsError>;
fn to_fs_error_with_uri(self, uri: Uri) -> Report<FsError>;
}
impl IoErrorExt for io::Error {
fn to_fs_error(self) -> Report<FsError> {
Report::new(FsError::Io(self.to_string()))
.attach_printable(format!("I/O error: {self}"))
}
fn to_fs_error_with_path(self, path: impl Into<PathBuf>) -> Report<FsError> {
let path = path.into();
Report::new(FsError::Io(self.to_string()))
.attach_printable(format!("I/O error: {self}"))
.attach(FilePath(path.clone()))
.attach_printable(format!("Path: {}", path.display()))
}
fn to_fs_error_with_uri(self, uri: Uri) -> Report<FsError> {
Report::new(FsError::Io(self.to_string()))
.attach_printable(format!("I/O error: {self}"))
.attach(UriPath(uri.clone()))
.attach_printable(format!("uri: {uri}"))
}
}
#[macro_export]
macro_rules! fs_error {
($msg:expr) => {
error_stack::Report::new($crate::fs::errors::FsError::fs_error($msg))
};
($fmt:expr, $($arg:tt)*) => {
error_stack::Report::new($crate::fs::errors::FsError::fs_error(format!($fmt, $($arg)*)))
};
}
pub trait FsResultExt<T, E: Context> {
fn with_path(
self,
path: impl Into<PathBuf>,
) -> std::result::Result<T, Report<FsError>>;
fn with_uri(self, uri: Uri) -> std::result::Result<T, Report<FsError>>;
}
impl<T, E: Context> FsResultExt<T, E> for std::result::Result<T, E> {
fn with_path(
self,
path: impl Into<PathBuf>,
) -> std::result::Result<T, Report<FsError>> {
let path = path.into();
self.map_err(|e| {
Report::new(e)
.change_context(FsError::filesystem_error("Operation failed"))
.attach(FilePath(path.clone()))
.attach_printable(format!("Path: {}", path.display()))
})
}
fn with_uri(self, uri: Uri) -> std::result::Result<T, Report<FsError>> {
self.map_err(|e| {
Report::new(e)
.change_context(FsError::filesystem_error("Operation failed"))
.attach(UriPath(uri.clone()))
.attach_printable(format!("uri: {uri}"))
})
}
}
pub trait ReportExt<E: Context> {
fn with_path(self, path: impl Into<PathBuf>) -> Report<FsError>;
fn with_uri(self, uri: Uri) -> Report<FsError>;
}
impl<E: Context> ReportExt<E> for Report<E> {
fn with_path(self, path: impl Into<PathBuf>) -> Report<FsError> {
let path = path.into();
self
.change_context(FsError::filesystem_error("Operation failed"))
.attach(FilePath(path.clone()))
.attach_printable(format!("Path: {}", path.display()))
}
fn with_uri(self, uri: Uri) -> Report<FsError> {
self
.change_context(FsError::filesystem_error("Operation failed"))
.attach(UriPath(uri.clone()))
.attach_printable(format!("uri: {uri}"))
}
}