Skip to main content

squib_core/
error.rs

1//! Error type for portable squib operations.
2
3use thiserror::Error;
4
5/// The result alias every fallible operation in `squib-core` and downstream crates returns.
6pub type Result<T, E = Error> = core::result::Result<T, E>;
7
8/// Errors that can surface from a hypervisor backend, a vCPU, or a memory operation.
9///
10/// Variants are intentionally coarse — backend-specific error context is captured in the
11/// `source` field of [`Error::Backend`] so consumers can downcast without coupling to a
12/// specific backend's error enum.
13#[derive(Debug, Error)]
14#[non_exhaustive]
15pub enum Error {
16    /// A backend (HVF, VZ, mock) reported a failure while executing a hypervisor operation.
17    #[error("hypervisor backend error: {message}")]
18    Backend {
19        /// Human-readable message from the backend.
20        message: String,
21        /// Optional underlying error returned by the backend SDK.
22        #[source]
23        source: Option<Box<dyn core::error::Error + Send + Sync + 'static>>,
24    },
25
26    /// A guest memory operation referenced an address or range that is not mapped.
27    #[error("guest memory operation out of range: {0}")]
28    MemoryOutOfRange(String),
29
30    /// A guest memory operation requested an unsupported protection change.
31    #[error("invalid memory protection change: {0}")]
32    InvalidProtection(String),
33
34    /// The capability the caller requested is not implemented by the active backend.
35    #[error("backend does not support capability: {0}")]
36    Unsupported(&'static str),
37
38    /// A vCPU was driven from an unexpected lifecycle state.
39    #[error("invalid vCPU state transition: {0}")]
40    InvalidVcpuState(&'static str),
41
42    /// The argument supplied by the caller was rejected by validation.
43    #[error("invalid argument: {0}")]
44    InvalidArgument(String),
45}
46
47impl Error {
48    /// Build a [`Error::Backend`] from a static message.
49    pub fn backend(message: impl Into<String>) -> Self {
50        Self::Backend {
51            message: message.into(),
52            source: None,
53        }
54    }
55
56    /// Build a [`Error::Backend`] from a message and a wrapped source error.
57    pub fn backend_source<E>(message: impl Into<String>, source: E) -> Self
58    where
59        E: core::error::Error + Send + Sync + 'static,
60    {
61        Self::Backend {
62            message: message.into(),
63            source: Some(Box::new(source)),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn backend_error_carries_message() {
74        let err = Error::backend("failed to map");
75        assert_eq!(err.to_string(), "hypervisor backend error: failed to map");
76    }
77
78    #[test]
79    fn backend_error_chains_source() {
80        let inner = Error::MemoryOutOfRange("0x1000".into());
81        let outer = Error::backend_source("wrap", inner);
82        let chained = core::error::Error::source(&outer)
83            .map(ToString::to_string)
84            .unwrap_or_default();
85        assert!(chained.contains("0x1000"));
86    }
87}