use std::fmt;
use std::io;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
InvalidInput(String),
UnknownMember(String),
Io {
operation: &'static str,
source: io::Error,
},
UpdateInProgress {
lock: std::path::PathBuf,
},
DestinationConflict {
destination: std::path::PathBuf,
},
VerificationFailed(String),
CandidateExecution(String),
#[cfg(test)]
Injected(String),
}
#[non_exhaustive]
#[derive(Debug)]
pub enum RecoveryError {
Core(Error),
RecoveryRequired {
evidence: std::path::PathBuf,
detail: String,
},
}
pub type RecoveryResult<T> = std::result::Result<T, RecoveryError>;
impl RecoveryError {
pub fn evidence(&self) -> Option<&std::path::Path> {
match self {
Self::Core(_) => None,
Self::RecoveryRequired { evidence, .. } => Some(evidence),
}
}
pub fn detail(&self) -> &str {
match self {
Self::Core(error) => match error {
Error::InvalidInput(m)
| Error::UnknownMember(m)
| Error::VerificationFailed(m)
| Error::CandidateExecution(m) => m,
_ => "",
},
Self::RecoveryRequired { detail, .. } => detail,
}
}
pub(crate) fn into_core(self) -> Error {
match self {
Self::Core(error) => error,
Self::RecoveryRequired { evidence, detail } => Error::io(
"internal recovery error outside a recovery entry point",
io::Error::other(format!("{detail} (retained at {})", evidence.display())),
),
}
}
}
impl From<Error> for RecoveryError {
fn from(error: Error) -> Self {
Self::Core(error)
}
}
impl Error {
pub(crate) fn invalid(message: impl Into<String>) -> Self {
Self::InvalidInput(message.into())
}
#[cfg(test)]
pub(crate) fn injected(message: impl Into<String>) -> Self {
Self::Injected(message.into())
}
pub(crate) fn io(operation: &'static str, source: io::Error) -> Self {
Self::Io { operation, source }
}
}
impl fmt::Display for RecoveryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Core(error) => error.fmt(formatter),
Self::RecoveryRequired { evidence, detail } => write!(
formatter,
"recovery evidence retained at {}: {detail}",
evidence.display()
),
}
}
}
impl std::error::Error for RecoveryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Core(error) => Some(error),
Self::RecoveryRequired { .. } => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidInput(message) => write!(formatter, "invalid update input: {message}"),
Self::UnknownMember(member) => write!(formatter, "unknown artifact member: {member}"),
Self::Io { operation, source } => write!(formatter, "{operation}: {source}"),
Self::UpdateInProgress { lock } => {
write!(
formatter,
"installation update already in progress: {}",
lock.display()
)
}
Self::DestinationConflict { destination } => write!(
formatter,
"destination failed ownership revalidation: {}",
destination.display()
),
Self::VerificationFailed(message) => {
write!(formatter, "verification failed: {message}")
}
Self::CandidateExecution(message) => {
write!(formatter, "candidate execution failed: {message}")
}
#[cfg(test)]
Self::Injected(message) => {
write!(formatter, "injected failure: {message}")
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io { source, .. } => Some(source),
#[cfg(test)]
Self::Injected(_) => None,
Self::InvalidInput(_)
| Self::UnknownMember(_)
| Self::UpdateInProgress { .. }
| Self::DestinationConflict { .. }
| Self::VerificationFailed(_)
| Self::CandidateExecution(_) => None,
}
}
}