use thiserror::Error;
pub type Result<T, E = LogoxideError> = std::result::Result<T, E>;
pub type Source = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LogoxideError {
#[error("{0}: not found")]
NotFound(String),
#[error("{path}: {message}")]
Io {
path: String,
message: String,
#[source]
source: Option<Source>,
},
#[error("{0}: not a path inside the package")]
BadPath(String),
#[error("this storage is read-only")]
ReadOnly,
#[error("logoxide.toml: {0}")]
Manifest(String),
#[error("{0}: written by a newer lx (update lx to read it)")]
Newer(String),
#[error("no format logoxide reads is enabled (enabled: {0})")]
NoReadableFormat(String),
#[error("{book}: {message}")]
Book { book: String, message: String },
#[error("cache: {0}")]
Cache(String),
#[error("logoxide.lock: {0}")]
Lock(String),
#[error("catalog: {0}")]
Catalog(String),
#[error("import: {0}")]
Import(String),
#[error("media: {0}")]
Media(String),
#[error("versification: {0}")]
Versification(String),
#[error("{url}: {message}")]
Network {
url: String,
message: String,
#[source]
source: Option<Source>,
},
#[error("{url}: {message}")]
Git { url: String, message: String },
#[error("{url}: {message}")]
Archive { url: String, message: String },
#[error("{0}")]
Resolve(String),
#[error("{url}: {message}")]
Mismatch { url: String, message: String },
#[error("{id}: expected {expected}, got {actual} (the source changed since it was locked)")]
Checksum {
id: String,
expected: String,
actual: String,
},
#[error(
"{id}: its files in {dir} changed since it was locked (expected {expected}, got {actual}): `lx install --repair` puts them back"
)]
Changed {
id: String,
dir: String,
expected: String,
actual: String,
},
#[error("{what}: {message}")]
Busy { what: String, message: String },
#[error("{path}: changed since it was read (open it again and redo the change)")]
Conflict { path: String },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
NotFound,
Io,
Invalid,
Network,
Git,
Archive,
Integrity,
Resolve,
Busy,
Conflict,
}
impl LogoxideError {
pub fn kind(&self) -> ErrorKind {
use LogoxideError as E;
match self {
E::NotFound(_) => ErrorKind::NotFound,
E::Io { .. } | E::ReadOnly => ErrorKind::Io,
E::BadPath(_)
| E::Manifest(_)
| E::Newer(_)
| E::NoReadableFormat(_)
| E::Book { .. }
| E::Cache(_)
| E::Lock(_)
| E::Catalog(_)
| E::Import(_)
| E::Media(_)
| E::Versification(_) => ErrorKind::Invalid,
E::Network { .. } => ErrorKind::Network,
E::Git { .. } => ErrorKind::Git,
E::Archive { .. } => ErrorKind::Archive,
E::Mismatch { .. } | E::Checksum { .. } | E::Changed { .. } => ErrorKind::Integrity,
E::Resolve(_) => ErrorKind::Resolve,
E::Busy { .. } => ErrorKind::Busy,
E::Conflict { .. } => ErrorKind::Conflict,
}
}
pub fn io(path: &str, error: std::io::Error) -> Self {
LogoxideError::Io {
path: path.to_string(),
message: error.to_string(),
source: Some(Box::new(error)),
}
}
pub fn io_message(path: &str, message: impl std::fmt::Display) -> Self {
LogoxideError::Io {
path: path.to_string(),
message: message.to_string(),
source: None,
}
}
pub fn network(url: &str, message: impl Into<String>) -> Self {
LogoxideError::Network {
url: url.to_string(),
message: message.into(),
source: None,
}
}
pub fn git(url: &str, message: impl Into<String>) -> Self {
LogoxideError::Git {
url: url.to_string(),
message: message.into(),
}
}
pub fn archive(url: &str, message: impl Into<String>) -> Self {
LogoxideError::Archive {
url: url.to_string(),
message: message.into(),
}
}
pub fn is_fetch(&self) -> bool {
matches!(
self.kind(),
ErrorKind::Network | ErrorKind::Git | ErrorKind::Archive
)
}
pub fn with_fetch_note(self, note: &str) -> Self {
match self {
LogoxideError::Network {
url,
message,
source,
} => LogoxideError::Network {
url,
message: format!("{message} {note}"),
source,
},
LogoxideError::Git { url, message } => LogoxideError::Git {
url,
message: format!("{message} {note}"),
},
LogoxideError::Archive { url, message } => LogoxideError::Archive {
url,
message: format!("{message} {note}"),
},
other => other,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kinds_and_sources() {
let io = LogoxideError::io("x", std::io::Error::other("gone"));
assert_eq!(io.kind(), ErrorKind::Io);
assert!(std::error::Error::source(&io).is_some());
assert_eq!(io.to_string(), "x: gone");
let network = LogoxideError::network("https://x", "no route");
assert_eq!(network.kind(), ErrorKind::Network);
assert!(std::error::Error::source(&network).is_none());
assert!(network.is_fetch());
assert_eq!(
network.with_fetch_note("(try later)").to_string(),
"https://x: no route (try later)"
);
assert_eq!(
LogoxideError::Manifest(String::new()).kind(),
ErrorKind::Invalid
);
assert_eq!(
LogoxideError::Conflict {
path: String::from("logoxide.toml")
}
.kind(),
ErrorKind::Conflict
);
assert!(!LogoxideError::ReadOnly.is_fetch());
}
}