pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(
clippy::error_impl_error,
reason = "Error is the crate's public error type"
)]
pub enum Error {
#[error("{0}")]
InvalidConfig(String),
#[error("{0}")]
NotFound(String),
#[error("{0}")]
Ambiguous(String),
#[error("{0}")]
InvalidState(String),
#[error("{0}")]
Busy(String),
#[error("guest agent unavailable: {0}")]
GuestUnavailable(String),
#[error("secrets required: re-supply with start_with(StartOptions {{ secrets, .. }})")]
SecretsRequired,
#[error("secrets require NetworkSpec::Enabled (gvproxy MITM)")]
SecretsNeedVirtioNet,
#[error("{0}")]
SecurityUnavailable(String),
#[error(transparent)]
Io(#[from] std::io::Error),
#[cfg(unix)]
#[error(transparent)]
Nix(#[from] nix::errno::Errno),
#[cfg(unix)]
#[error(transparent)]
E2fs(#[from] bux_e2fs::Error),
#[cfg(unix)]
#[error(transparent)]
Qcow2(#[from] bux_qcow2::Error),
#[cfg(unix)]
#[error(transparent)]
Jail(#[from] bux_jail::Error),
#[cfg(unix)]
#[error(transparent)]
Shim(#[from] bux_shim::Error),
#[cfg(unix)]
#[error(transparent)]
Oci(#[from] bux_oci::OciError),
#[cfg(unix)]
#[error(transparent)]
Db(#[from] rusqlite::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error("runtime has been shut down")]
Shutdown,
}
impl Error {
#[must_use]
pub const fn is_user_error(&self) -> bool {
matches!(
self,
Self::InvalidConfig(_)
| Self::NotFound(_)
| Self::Ambiguous(_)
| Self::InvalidState(_)
| Self::SecretsRequired
| Self::SecretsNeedVirtioNet
)
}
#[must_use]
pub const fn is_retryable(&self) -> bool {
matches!(self, Self::Busy(_) | Self::GuestUnavailable(_))
}
#[must_use]
pub const fn is_fatal(&self) -> bool {
matches!(self, Self::Shutdown)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn user_errors() {
assert!(Error::InvalidConfig("bad".into()).is_user_error());
assert!(Error::NotFound("gone".into()).is_user_error());
assert!(Error::Ambiguous("many".into()).is_user_error());
assert!(Error::InvalidState("wrong".into()).is_user_error());
assert!(!Error::InvalidConfig("bad".into()).is_retryable());
assert!(!Error::InvalidConfig("bad".into()).is_fatal());
}
#[test]
fn retryable_errors() {
assert!(Error::Busy("locked".into()).is_retryable());
assert!(Error::GuestUnavailable("x".into()).is_retryable());
assert!(Error::SecretsRequired.is_user_error());
assert!(!Error::GuestUnavailable("x".into()).is_user_error());
assert!(!Error::GuestUnavailable("x".into()).is_fatal());
assert_eq!(
Error::GuestUnavailable("timed out".into()).to_string(),
"guest agent unavailable: timed out"
);
}
#[test]
fn fatal_error() {
assert!(Error::Shutdown.is_fatal());
assert!(!Error::Shutdown.is_user_error());
assert!(!Error::Shutdown.is_retryable());
}
#[test]
fn system_errors_not_categorized() {
let io = Error::Io(std::io::Error::other("x"));
assert!(!io.is_user_error());
assert!(!io.is_retryable());
assert!(!io.is_fatal());
}
}