#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("unsupported operating system: {0}")]
UnsupportedOS(String),
#[error("KDE Plasma desktop environment not detected")]
NotKDE,
#[error("network request failed: {0}")]
Network(#[from] reqwest::Error),
#[error("api rate limited, retry after backoff")]
RateLimited,
#[error("api returned error status: {0}")]
ApiError(u16),
#[error("failed to parse xml: {0}")]
XmlParse(String),
#[error("failed to parse metadata.json: {0}")]
MetadataParse(#[from] serde_json::Error),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("component not found: {0}")]
ComponentNotFound(String),
#[error("extraction failed: {0}")]
ExtractionFailed(String),
#[error("installation failed: {0}")]
InstallFailed(String),
#[error("could not resolve content id for: {0}")]
IdResolutionFailed(String),
#[error("config error: {0}")]
Config(String),
#[error("invalid version: {0}")]
InvalidVersion(String),
#[error("download failed: {0}")]
DownloadFailed(String),
#[error("checksum mismatch: expected {expected}, got {actual}")]
ChecksumMismatch { expected: String, actual: String },
#[error("metadata not found in package")]
MetadataNotFound,
#[error("backup failed: {0}")]
BackupFailed(String),
#[error("restart failed: {0}")]
RestartFailed(String),
#[error("installation failed ({install_error}) and backup restore also failed ({restore_error})")]
InstallAndRestoreFailed {
install_error: String,
restore_error: String,
},
#[error("missing required dependency: {0}")]
MissingDependency(String),
#[error("{0}")]
Other(String),
#[error("no updates available")]
NoUpdatesAvailable,
#[error("another plasmoid-updater instance is already running")]
AlreadyRunning,
}
impl Error {
pub fn is_skippable(&self) -> bool {
matches!(
self,
Self::NoUpdatesAvailable | Self::ComponentNotFound(_) | Self::AlreadyRunning
)
}
pub fn is_transient(&self) -> bool {
matches!(self, Self::Network(_) | Self::RateLimited)
}
pub fn is_fatal(&self) -> bool {
!self.is_skippable() && !self.is_transient()
}
}
macro_rules! error_ctor {
($($name:ident => $variant:ident),* $(,)?) => {
$(
pub(crate) fn $name(msg: impl Into<String>) -> Self {
Self::$variant(msg.into())
}
)*
};
}
impl Error {
error_ctor!(
xml_parse => XmlParse,
extraction => ExtractionFailed,
install => InstallFailed,
download => DownloadFailed,
backup => BackupFailed,
restart => RestartFailed,
);
pub fn other(msg: impl Into<String>) -> Self {
Self::Other(msg.into())
}
pub(crate) fn checksum(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::ChecksumMismatch {
expected: expected.into(),
actual: actual.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn install_and_restore_failed_displays_both_errors() {
let err = Error::InstallAndRestoreFailed {
install_error: "extraction failed".to_string(),
restore_error: "permission denied".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("extraction failed"));
assert!(msg.contains("permission denied"));
}
#[test]
fn missing_dependency_is_fatal() {
let err = Error::MissingDependency("bsdtar".to_string());
assert!(err.is_fatal());
assert!(err.to_string().contains("bsdtar"));
}
#[test]
fn install_and_restore_failed_is_fatal() {
let err = Error::InstallAndRestoreFailed {
install_error: "x".to_string(),
restore_error: "y".to_string(),
};
assert!(err.is_fatal());
assert!(!err.is_transient());
assert!(!err.is_skippable());
}
}