use thiserror::Error;
#[derive(Debug, Error)]
pub enum ComputerError {
#[error("VM not found: {0}")]
NotFound(String),
#[error("VM already exists: {0}")]
AlreadyExists(String),
#[error("VM '{id}' is in wrong state: expected {expected}, got {actual}")]
WrongState {
id: String,
expected: String,
actual: String,
},
#[error("sandbox '{0}' is paused")]
Paused(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("driver error: {0}")]
Driver(#[source] arcbox_vm_driver::Error),
#[error("network error: {0}")]
Network(String),
#[error("snapshot error: {0}")]
Snapshot(String),
#[error("device-mapper error: {0}")]
DeviceMapper(String),
#[error("rootfs error: {0}")]
Rootfs(String),
#[error("process error: {0}")]
Process(String),
#[error("configuration error: {0}")]
Config(String),
#[error("vsock error: {0}")]
Vsock(String),
#[error("path not found: {0}")]
PathNotFound(String),
#[error("not a directory: {0}")]
NotADirectory(String),
#[error("directory not empty: {0}")]
DirectoryNotEmpty(String),
#[error("template not found: {0}")]
TemplateNotFound(String),
#[error("template version already exists: {0}")]
TemplateVersionExists(String),
#[error("failed precondition: {0}")]
FailedPrecondition(String),
#[error("deadline exceeded: {0}")]
DeadlineExceeded(String),
#[error("service unavailable: {0}")]
Unavailable(String),
#[error("sandbox {id} committed, but ACK durability is unconfirmed: {detail}")]
AckUnconfirmed {
id: String,
detail: String,
},
#[error("stdin offset {offset} is past the {accepted} accepted bytes")]
StdinGap {
accepted: u64,
offset: u64,
},
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, ComputerError>;
impl From<arcbox_snapshot::SnapshotError> for ComputerError {
fn from(err: arcbox_snapshot::SnapshotError) -> Self {
use arcbox_error::CommonError;
use arcbox_snapshot::SnapshotError as S;
match err {
S::Common(CommonError::Io(io)) => Self::Io(io),
S::Common(CommonError::Config(msg)) => Self::Config(msg),
S::Common(CommonError::NotFound(msg)) => Self::NotFound(msg),
S::Common(CommonError::AlreadyExists(msg)) => Self::AlreadyExists(msg),
S::Common(other) => Self::Other(other.to_string()),
S::Snapshot(msg) => Self::Snapshot(msg),
S::DeviceMapper(msg) => Self::DeviceMapper(msg),
S::TemplateNotFound(msg) => Self::TemplateNotFound(msg),
S::TemplateVersionExists(msg) => Self::TemplateVersionExists(msg),
S::FailedPrecondition(msg) => Self::FailedPrecondition(msg),
S::Unavailable(msg) => Self::Unavailable(msg),
}
}
}
impl From<arcbox_vm_driver::Error> for ComputerError {
fn from(err: arcbox_vm_driver::Error) -> Self {
use arcbox_vm_driver::Error as D;
match err {
D::NotFound(id) => Self::NotFound(id.to_string()),
D::WrongState {
id,
state,
expected,
} => Self::WrongState {
id: id.to_string(),
expected: expected.to_string(),
actual: state.to_string(),
},
D::Io(io) => Self::Io(io),
D::Network(msg) => Self::Network(msg),
D::InvalidSpec(msg) => Self::Config(msg),
D::Unavailable(msg) => Self::Unavailable(msg),
D::PreconditionFailed(msg) => Self::FailedPrecondition(msg),
other @ (D::Driver { .. } | D::ForeignCheckpoint(_)) => Self::Driver(other),
}
}
}
impl From<arcbox_atomic_file::AtomicWriteError> for ComputerError {
fn from(err: arcbox_atomic_file::AtomicWriteError) -> Self {
use arcbox_atomic_file::AtomicWriteError;
match err {
AtomicWriteError::NotCommitted { source, .. } => Self::Io(source),
error @ AtomicWriteError::DurabilityUncertain { .. } => {
Self::Unavailable(error.to_string())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_not_found_display() {
let e = ComputerError::NotFound("vm-123".into());
assert_eq!(e.to_string(), "VM not found: vm-123");
}
#[test]
fn test_already_exists_display() {
let e = ComputerError::AlreadyExists("my-vm".into());
assert_eq!(e.to_string(), "VM already exists: my-vm");
}
#[test]
fn test_wrong_state_display() {
let e = ComputerError::WrongState {
id: "vm-1".into(),
expected: "running".into(),
actual: "stopped".into(),
};
let s = e.to_string();
assert!(s.contains("vm-1"));
assert!(s.contains("running"));
assert!(s.contains("stopped"));
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let vmm_err = ComputerError::from(io_err);
assert!(matches!(vmm_err, ComputerError::Io(_)));
assert!(vmm_err.to_string().contains("I/O error"));
}
#[test]
fn test_network_error_display() {
let e = ComputerError::Network("TAP creation failed".into());
assert_eq!(e.to_string(), "network error: TAP creation failed");
}
#[test]
fn the_ports_cleanup_answers_keep_their_classification() {
use arcbox_vm_driver::Error as D;
let retry = ComputerError::from(D::Unavailable("startup cleanup pending".into()));
assert!(
matches!(&retry, ComputerError::Unavailable(m) if m == "startup cleanup pending"),
"{retry}"
);
let mismatch = ComputerError::from(D::PreconditionFailed("token b is not token a".into()));
assert!(
matches!(&mismatch, ComputerError::FailedPrecondition(m) if m == "token b is not token a"),
"{mismatch}"
);
}
}