use std::error::Error;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::io;
use crate::error::FsEffectState;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::metadata::FileSystemCapability;
use crate::path::Path;
pub struct FsError {
kind: FsErrorKind,
operation: FsOperation,
path: Option<Box<Path>>,
target: Option<Box<Path>>,
failure_path: Option<Box<Path>>,
failure_target: Option<Box<Path>>,
provider: Option<Box<str>>,
required_capability: Option<FileSystemCapability>,
effect_state: Option<FsEffectState>,
message: Box<str>,
source: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl FsError {
#[inline]
#[must_use]
pub fn new(kind: FsErrorKind, operation: FsOperation, message: &str) -> Self {
Self {
kind,
operation,
path: None,
target: None,
failure_path: None,
failure_target: None,
provider: None,
required_capability: None,
effect_state: None,
message: message.into(),
source: None,
}
}
#[inline]
pub fn with_source<E>(kind: FsErrorKind, operation: FsOperation, message: &str, source: E) -> Self
where
E: Error + Send + Sync + 'static,
{
Self {
source: Some(Box::new(source)),
..Self::new(kind, operation, message)
}
}
#[inline]
#[must_use]
pub fn with_path(mut self, path: impl Into<Path>) -> Self {
self.path = Some(Box::new(path.into()));
self
}
#[inline]
#[must_use]
pub fn with_operation(mut self, operation: FsOperation) -> Self {
self.operation = operation;
self
}
#[inline]
#[must_use]
pub fn with_target(mut self, target: impl Into<Path>) -> Self {
self.target = Some(Box::new(target.into()));
self
}
#[inline]
#[must_use]
pub fn with_failure_path(mut self, path: impl Into<Path>) -> Self {
self.failure_path = Some(Box::new(path.into()));
self
}
#[inline]
#[must_use]
pub fn with_failure_target(mut self, target: impl Into<Path>) -> Self {
self.failure_target = Some(Box::new(target.into()));
self
}
#[inline]
#[must_use]
pub fn with_provider(mut self, provider: impl Display) -> Self {
self.provider = Some(provider.to_string().into());
self
}
#[inline]
#[must_use]
pub fn with_required_capability(mut self, capability: FileSystemCapability) -> Self {
self.required_capability = Some(capability);
self
}
#[inline]
#[must_use]
pub fn with_effect_state(mut self, effect_state: FsEffectState) -> Self {
self.effect_state = Some(effect_state);
self
}
pub(crate) fn with_trusted_cleanup_context(
mut self,
operation: FsOperation,
path: Option<&Path>,
provider: &str,
) -> Self {
self.operation = operation;
self.path = path.cloned().map(Box::new);
self.target = None;
self.failure_path = None;
self.failure_target = None;
self.provider = Some(provider.into());
self
}
#[inline]
#[must_use]
pub(crate) fn with_missing_context(mut self, path: &Path, target: Option<&Path>, provider: &str) -> Self {
if self.path.is_none() {
self.path = Some(Box::new(path.clone()));
}
if self.target.is_none() {
self.target = target.cloned().map(Box::new);
}
if self.provider.is_none() {
self.provider = Some(provider.into());
}
self
}
#[inline]
#[must_use]
pub(crate) fn with_missing_provider(mut self, provider: &str) -> Self {
if self.provider.is_none() {
self.provider = Some(provider.into());
}
self
}
#[inline]
#[must_use]
pub fn invalid_path(operation: FsOperation, message: &str) -> Self {
Self::new(FsErrorKind::InvalidPath, operation, message)
}
#[inline]
#[must_use]
pub fn from_io(error: io::Error, operation: FsOperation) -> Self {
let kind = match error.kind() {
io::ErrorKind::NotFound => FsErrorKind::NotFound,
io::ErrorKind::AlreadyExists => FsErrorKind::AlreadyExists,
io::ErrorKind::DirectoryNotEmpty => FsErrorKind::Conflict,
io::ErrorKind::NotADirectory => FsErrorKind::NotDirectory,
io::ErrorKind::IsADirectory => FsErrorKind::IsDirectory,
io::ErrorKind::PermissionDenied => FsErrorKind::PermissionDenied,
io::ErrorKind::InvalidInput => FsErrorKind::InvalidOptions,
io::ErrorKind::Unsupported => FsErrorKind::UnsupportedOperation,
io::ErrorKind::TimedOut => FsErrorKind::Timeout,
io::ErrorKind::Interrupted => FsErrorKind::Interrupted,
io::ErrorKind::StorageFull => FsErrorKind::QuotaExceeded,
io::ErrorKind::InvalidData => FsErrorKind::DataCorruption,
_ => FsErrorKind::Io,
};
Self::with_source(kind, operation, "stream I/O failed", error)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn from_stream_io(error: io::Error, operation: FsOperation, path: &Path) -> Self {
match error.downcast::<Self>() {
Ok(error) => error.with_operation(operation).with_path(path.clone()),
Err(error) if error.kind() == io::ErrorKind::InvalidData => {
Self::with_source(FsErrorKind::Io, operation, "stream I/O contract failed", error)
.with_path(path.clone())
}
Err(error) => Self::from_io(error, operation).with_path(path.clone()),
}
}
#[inline]
#[must_use]
pub fn kind(&self) -> FsErrorKind {
self.kind
}
#[inline]
#[must_use]
pub fn operation(&self) -> FsOperation {
self.operation
}
#[inline]
#[must_use]
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
#[inline]
#[must_use]
pub fn target(&self) -> Option<&Path> {
self.target.as_deref()
}
#[inline]
#[must_use]
pub fn failure_path(&self) -> Option<&Path> {
self.failure_path.as_deref()
}
#[inline]
#[must_use]
pub fn failure_target(&self) -> Option<&Path> {
self.failure_target.as_deref()
}
#[inline]
#[must_use]
pub fn provider(&self) -> Option<&str> {
self.provider.as_deref()
}
#[inline]
#[must_use]
pub fn required_capability(&self) -> Option<FileSystemCapability> {
self.required_capability
}
#[inline]
#[must_use]
pub fn effect_state(&self) -> Option<FsEffectState> {
self.effect_state
}
#[inline]
#[must_use]
pub fn has_indeterminate_effect(&self) -> bool {
self.kind == FsErrorKind::Indeterminate || self.effect_state == Some(FsEffectState::Indeterminate)
}
#[inline]
#[must_use]
pub fn into_io_error(self) -> io::Error {
let kind = match self.kind {
FsErrorKind::NotFound => io::ErrorKind::NotFound,
FsErrorKind::AlreadyExists => io::ErrorKind::AlreadyExists,
FsErrorKind::NotDirectory => io::ErrorKind::NotADirectory,
FsErrorKind::IsDirectory => io::ErrorKind::IsADirectory,
FsErrorKind::PermissionDenied | FsErrorKind::AuthenticationFailed => io::ErrorKind::PermissionDenied,
FsErrorKind::InvalidPath
| FsErrorKind::InvalidUri
| FsErrorKind::InvalidOptions
| FsErrorKind::InvalidState => io::ErrorKind::InvalidInput,
FsErrorKind::UnsupportedOperation | FsErrorKind::UnsupportedCapability => io::ErrorKind::Unsupported,
FsErrorKind::Timeout => io::ErrorKind::TimedOut,
FsErrorKind::Interrupted => io::ErrorKind::Interrupted,
FsErrorKind::Cancelled => io::ErrorKind::Other,
FsErrorKind::QuotaExceeded => io::ErrorKind::StorageFull,
FsErrorKind::DataCorruption => io::ErrorKind::InvalidData,
_ => io::ErrorKind::Other,
};
io::Error::new(kind, self)
}
}
impl Debug for FsError {
#[inline]
fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
formatter
.debug_struct("FsError")
.field("kind", &self.kind)
.field("operation", &self.operation)
.field("path", &self.path.as_deref())
.field("target", &self.target.as_deref())
.field("failure_path", &self.failure_path.as_deref())
.field("failure_target", &self.failure_target.as_deref())
.field("provider", &self.provider)
.field("required_capability", &self.required_capability)
.field("effect_state", &self.effect_state)
.field("message", &self.message)
.field("source_present", &self.source.is_some())
.finish()
}
}
impl Display for FsError {
#[inline]
fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
write!(
formatter,
"{:?} failed with {:?}: {}",
self.operation, self.kind, self.message,
)
}
}
impl Error for FsError {
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source.as_deref().map(|source| source as &(dyn Error + 'static))
}
}