use std::{
convert::Infallible,
fmt::{self, Debug, Display},
io,
path::PathBuf,
result,
str::FromStr,
};
use thiserror::Error as DeriveError;
use crate::{
FileOrigin, RootBy,
block::{BlockId, FileId},
};
#[derive(Debug, Clone, PartialEq, Eq, DeriveError)]
pub enum InternalError {
#[error("File with ID {id:?} not found.")]
FileNotFound { id: FileId },
#[error("Block with ID {id:?} not found.")]
BlockNotFound { id: BlockId },
#[error("Invalid UUID: {uuid}.")]
InvalidUuid { uuid: String },
#[error("Root ID in the database is not a valid UUID: {uuid}")]
RootIdIsNotAUuid { uuid: String },
#[error("Could not read filesystem settings.")]
MalformedSettings,
#[error("Block hash was not exactly 32 bytes long.")]
IncorrectBlockHashLen,
#[error("Block has a hash but not data or data but not a hash.")]
MismatchedBlockHashAndData,
#[error("Invalid file kind: {kind}")]
InvalidFileKind { kind: String },
#[error("Cannot insert root path into closure table.")]
RootPathCannotBeInserted,
#[error("Feature 'compression' is disabled.")]
CompressionDisabled,
#[error("Feature 'chunking' is disabled.")]
ChunkingDisabled,
#[error("Cannot move or copy a directory into one of its descendants.")]
CannotMoveIntoDescendant,
#[error("Cannot delete or unlink the root directory.")]
CannotDeleteRootDir,
#[error("ACL is malformed.")]
MalformedAcl,
#[error("Timestamp is malformed.")]
MalformedTimeBlob,
#[error("There was unexpectedly more than one root merkle node.")]
MoreThanOneRootMerkleNode,
#[error("Error serializing or deserializing metadata: {reason}")]
MetadataSerializationError { reason: String },
#[error("Mutually exclusive walk options were specified.")]
MutuallyExclusiveWalkOptions,
#[error("{reason}")]
Other { reason: String },
}
#[derive(Clone, PartialEq, Eq)]
pub struct ErrorReason(pub(crate) InternalError);
#[cfg_attr(coverage_nightly, coverage(off))]
impl Display for ErrorReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl Debug for ErrorReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0.to_string())
}
}
#[doc(hidden)]
impl FromStr for ErrorReason {
type Err = Infallible;
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
Ok(Self(InternalError::Other {
reason: s.to_string(),
}))
}
}
impl From<InternalError> for ErrorReason {
fn from(err: InternalError) -> Self {
Self(err)
}
}
impl From<InternalError> for Error {
fn from(err: InternalError) -> Self {
match err {
InternalError::MalformedSettings => crate::Error::Malformed { reason: err.into() },
InternalError::MalformedTimeBlob => crate::Error::Malformed { reason: err.into() },
InternalError::IncorrectBlockHashLen => crate::Error::Malformed { reason: err.into() },
InternalError::RootIdIsNotAUuid { .. } => {
crate::Error::Malformed { reason: err.into() }
}
InternalError::CompressionDisabled | InternalError::ChunkingDisabled => {
crate::Error::FeatureDisabled { reason: err.into() }
}
InternalError::MutuallyExclusiveWalkOptions => {
crate::Error::InvalidArgs { reason: err.into() }
}
_ => crate::Error::Other { reason: err.into() },
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct SqliteErrorCode {
code: Option<rusqlite::ffi::Error>,
reason: String,
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl fmt::Debug for SqliteErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = &mut f.debug_struct("SqliteErrorCode");
if let Some(code) = &self.code {
debug = debug.field("code", &format!("{}", code.extended_code));
}
debug.field("reason", &self.reason).finish()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl fmt::Display for SqliteErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.reason)
}
}
impl SqliteErrorCode {
pub fn raw_code(&self) -> Option<std::ffi::c_int> {
self.code.map(|err| err.extended_code)
}
}
#[derive(Debug, Clone, PartialEq, Eq, DeriveError)]
#[non_exhaustive]
pub enum Error {
#[error("Some arguments were invalid: {reason}")]
InvalidArgs {
reason: ErrorReason,
},
#[error("The specified file was not found: {file:?}")]
FileNotFound {
file: FileOrigin<'static>,
},
#[error("This file already exists: {path:?}")]
FileAlreadyExists {
path: FileOrigin<'static, PathBuf>,
},
#[error("This file has no parent directory: {file:?}")]
NoParentDirectory {
file: FileOrigin<'static>,
},
#[error("This file is not a directory: {file:?}")]
NotADirectory {
file: FileOrigin<'static>,
},
#[error("This directory is not empty: {path:?}")]
DirectoryNotEmpty {
path: FileOrigin<'static, PathBuf>,
},
#[error("This path is not valid: {path:?}\n{reason}")]
InvalidPath {
path: FileOrigin<'static, PathBuf>,
reason: ErrorReason,
},
#[error("There is no root with this name: {root}")]
RootNotFound {
root: RootBy<'static>,
},
#[error("There is already a root with this name: {name}")]
RootAlreadyExists {
name: String,
},
#[error("Cannot delete or rename the default root.")]
IsDefaultRoot,
#[error("Cannot delete the current root.")]
IsCurrentRoot,
#[error("This is not a regular file: {file:?}")]
NotARegularFile {
file: FileOrigin<'static>,
},
#[error("This file is a type that's not supported by LiteboxFS.: {file:?}")]
UnsupportedFileType {
file: FileOrigin<'static>,
},
#[error("Following this symbolic link would form a loop: {path:?}")]
SymlinkLoop {
path: FileOrigin<'static, PathBuf>,
},
#[error("The litebox could not be opened.")]
CannotOpen,
#[error("Attempted to write to a read-only database.")]
ReadOnly,
#[error("A filesystem limit was exceeded.")]
TooLarge,
#[error("The maximum length of a metadata key or value was exceeded.")]
MetadataLimitExceeded,
#[error("This file is not a SQLite database.")]
NotADatabase,
#[error("This SQLite database is not litebox.")]
NotALitebox,
#[error("Attempted to create a new litebox, but one already exists.")]
LiteboxAlreadyExists,
#[error("This litebox is in use; mounting via FUSE requires an exclsuive lock on the litebox.")]
LiteboxLocked,
#[error("The format version of the litebox is not supported by this version of the library.")]
UnsupportedFormatVersion,
#[error(
"Cannot create or open the litebox because of a missing compile-time feature: {reason}"
)]
FeatureDisabled {
reason: ErrorReason,
},
#[error("This litebox is malformed or corrupted: {reason}")]
Malformed {
reason: ErrorReason,
},
#[error("There was an error from the underlying SQLite database: {code}")]
Sqlite {
code: SqliteErrorCode,
},
#[error("An I/O error occurred: {kind}")]
Io {
kind: io::ErrorKind,
code: Option<i32>,
},
#[error("{reason}")]
Other {
reason: ErrorReason,
},
}
impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Self {
match err.sqlite_error() {
Some(rusqlite::ffi::Error {
code: rusqlite::ErrorCode::ReadOnly,
..
}) => Error::ReadOnly,
Some(rusqlite::ffi::Error {
code: rusqlite::ErrorCode::CannotOpen,
..
}) => Error::CannotOpen,
Some(rusqlite::ffi::Error {
code: rusqlite::ErrorCode::NotADatabase,
..
}) => Error::NotADatabase,
code => Error::Sqlite {
code: SqliteErrorCode {
code: code.cloned(),
reason: err.to_string(),
},
},
}
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
let kind = error.kind();
let code = error.raw_os_error();
match error.into_inner() {
Some(payload) => match payload.downcast::<Error>() {
Ok(crate_error) => *crate_error,
Err(_) => Error::Io { kind, code },
},
None => Error::Io { kind, code },
}
}
}
impl From<Error> for io::Error {
fn from(err: Error) -> Self {
let kind = match err {
Error::Io { kind, .. } => kind,
_ => io::ErrorKind::Other,
};
io::Error::new(kind, err)
}
}
pub type Result<T> = result::Result<T, Error>;