fusabi_host/
error.rs

1//! Error types for fusabi-host operations.
2
3use std::fmt;
4use thiserror::Error;
5
6use crate::limits::LimitViolation;
7use crate::convert::ValueConversionError;
8
9/// Result type alias using [`Error`].
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Errors that can occur during Fusabi host operations.
13#[derive(Error, Debug)]
14pub enum Error {
15    /// Compilation failed with the given message.
16    #[error("compilation error: {0}")]
17    Compilation(String),
18
19    /// Runtime execution failed.
20    #[error("runtime error: {0}")]
21    Runtime(String),
22
23    /// A resource limit was violated.
24    #[error("limit violation: {0}")]
25    LimitViolation(#[from] LimitViolation),
26
27    /// Value conversion failed.
28    #[error("value conversion error: {0}")]
29    ValueConversion(#[from] ValueConversionError),
30
31    /// Capability was denied.
32    #[error("capability denied: {capability}")]
33    CapabilityDenied {
34        /// The capability that was denied.
35        capability: String,
36    },
37
38    /// Sandbox policy violation.
39    #[error("sandbox violation: {0}")]
40    SandboxViolation(String),
41
42    /// Engine pool exhausted.
43    #[error("engine pool exhausted, all {count} engines busy")]
44    PoolExhausted {
45        /// Number of engines in the pool.
46        count: usize,
47    },
48
49    /// Pool acquire timeout.
50    #[error("timeout waiting for engine from pool")]
51    PoolTimeout,
52
53    /// Pool was shut down.
54    #[error("engine pool has been shut down")]
55    PoolShutdown,
56
57    /// Engine was poisoned (panicked during execution).
58    #[error("engine poisoned: {0}")]
59    EnginePoisoned(String),
60
61    /// IO error occurred.
62    #[error("io error: {0}")]
63    Io(#[from] std::io::Error),
64
65    /// Invalid configuration.
66    #[error("invalid configuration: {0}")]
67    InvalidConfig(String),
68
69    /// Version incompatibility.
70    #[error("version incompatibility: expected {expected}, got {actual}")]
71    VersionMismatch {
72        /// Expected version range.
73        expected: String,
74        /// Actual version.
75        actual: String,
76    },
77
78    /// Host function registration error.
79    #[error("host function error: {0}")]
80    HostFunction(String),
81
82    /// Bytecode validation failed.
83    #[error("invalid bytecode: {0}")]
84    InvalidBytecode(String),
85
86    /// Timeout during execution.
87    #[error("execution timeout after {0:?}")]
88    Timeout(std::time::Duration),
89
90    /// Cancelled by user.
91    #[error("execution cancelled")]
92    Cancelled,
93
94    /// Internal error (should not happen).
95    #[error("internal error: {0}")]
96    Internal(String),
97}
98
99impl Error {
100    /// Create a compilation error.
101    pub fn compilation(msg: impl Into<String>) -> Self {
102        Self::Compilation(msg.into())
103    }
104
105    /// Create a runtime error.
106    pub fn runtime(msg: impl Into<String>) -> Self {
107        Self::Runtime(msg.into())
108    }
109
110    /// Create a capability denied error.
111    pub fn capability_denied(capability: impl Into<String>) -> Self {
112        Self::CapabilityDenied {
113            capability: capability.into(),
114        }
115    }
116
117    /// Create a sandbox violation error.
118    pub fn sandbox_violation(msg: impl Into<String>) -> Self {
119        Self::SandboxViolation(msg.into())
120    }
121
122    /// Create an invalid config error.
123    pub fn invalid_config(msg: impl Into<String>) -> Self {
124        Self::InvalidConfig(msg.into())
125    }
126
127    /// Create a version mismatch error.
128    pub fn version_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
129        Self::VersionMismatch {
130            expected: expected.into(),
131            actual: actual.into(),
132        }
133    }
134
135    /// Create a host function error.
136    pub fn host_function(msg: impl Into<String>) -> Self {
137        Self::HostFunction(msg.into())
138    }
139
140    /// Create an invalid bytecode error.
141    pub fn invalid_bytecode(msg: impl Into<String>) -> Self {
142        Self::InvalidBytecode(msg.into())
143    }
144
145    /// Returns true if this is a transient error that may succeed on retry.
146    pub fn is_transient(&self) -> bool {
147        matches!(
148            self,
149            Self::PoolExhausted { .. } | Self::PoolTimeout | Self::Timeout(_)
150        )
151    }
152
153    /// Returns true if this error indicates the engine is unusable.
154    pub fn is_fatal(&self) -> bool {
155        matches!(
156            self,
157            Self::EnginePoisoned(_) | Self::PoolShutdown | Self::Internal(_)
158        )
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_error_display() {
168        let err = Error::compilation("syntax error at line 5");
169        assert_eq!(err.to_string(), "compilation error: syntax error at line 5");
170
171        let err = Error::PoolExhausted { count: 4 };
172        assert_eq!(err.to_string(), "engine pool exhausted, all 4 engines busy");
173    }
174
175    #[test]
176    fn test_error_classification() {
177        assert!(Error::PoolTimeout.is_transient());
178        assert!(Error::PoolExhausted { count: 4 }.is_transient());
179        assert!(!Error::Compilation("test".into()).is_transient());
180
181        assert!(Error::EnginePoisoned("panic".into()).is_fatal());
182        assert!(Error::PoolShutdown.is_fatal());
183        assert!(!Error::PoolTimeout.is_fatal());
184    }
185}