use std::fmt;
use thiserror::Error;
use crate::limits::LimitViolation;
use crate::convert::ValueConversionError;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("compilation error: {0}")]
Compilation(String),
#[error("runtime error: {0}")]
Runtime(String),
#[error("limit violation: {0}")]
LimitViolation(#[from] LimitViolation),
#[error("value conversion error: {0}")]
ValueConversion(#[from] ValueConversionError),
#[error("capability denied: {capability}")]
CapabilityDenied {
capability: String,
},
#[error("sandbox violation: {0}")]
SandboxViolation(String),
#[error("engine pool exhausted, all {count} engines busy")]
PoolExhausted {
count: usize,
},
#[error("timeout waiting for engine from pool")]
PoolTimeout,
#[error("engine pool has been shut down")]
PoolShutdown,
#[error("engine poisoned: {0}")]
EnginePoisoned(String),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("invalid configuration: {0}")]
InvalidConfig(String),
#[error("version incompatibility: expected {expected}, got {actual}")]
VersionMismatch {
expected: String,
actual: String,
},
#[error("host function error: {0}")]
HostFunction(String),
#[error("invalid bytecode: {0}")]
InvalidBytecode(String),
#[error("execution timeout after {0:?}")]
Timeout(std::time::Duration),
#[error("execution cancelled")]
Cancelled,
#[error("internal error: {0}")]
Internal(String),
}
impl Error {
pub fn compilation(msg: impl Into<String>) -> Self {
Self::Compilation(msg.into())
}
pub fn runtime(msg: impl Into<String>) -> Self {
Self::Runtime(msg.into())
}
pub fn capability_denied(capability: impl Into<String>) -> Self {
Self::CapabilityDenied {
capability: capability.into(),
}
}
pub fn sandbox_violation(msg: impl Into<String>) -> Self {
Self::SandboxViolation(msg.into())
}
pub fn invalid_config(msg: impl Into<String>) -> Self {
Self::InvalidConfig(msg.into())
}
pub fn version_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self::VersionMismatch {
expected: expected.into(),
actual: actual.into(),
}
}
pub fn host_function(msg: impl Into<String>) -> Self {
Self::HostFunction(msg.into())
}
pub fn invalid_bytecode(msg: impl Into<String>) -> Self {
Self::InvalidBytecode(msg.into())
}
pub fn is_transient(&self) -> bool {
matches!(
self,
Self::PoolExhausted { .. } | Self::PoolTimeout | Self::Timeout(_)
)
}
pub fn is_fatal(&self) -> bool {
matches!(
self,
Self::EnginePoisoned(_) | Self::PoolShutdown | Self::Internal(_)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::compilation("syntax error at line 5");
assert_eq!(err.to_string(), "compilation error: syntax error at line 5");
let err = Error::PoolExhausted { count: 4 };
assert_eq!(err.to_string(), "engine pool exhausted, all 4 engines busy");
}
#[test]
fn test_error_classification() {
assert!(Error::PoolTimeout.is_transient());
assert!(Error::PoolExhausted { count: 4 }.is_transient());
assert!(!Error::Compilation("test".into()).is_transient());
assert!(Error::EnginePoisoned("panic".into()).is_fatal());
assert!(Error::PoolShutdown.is_fatal());
assert!(!Error::PoolTimeout.is_fatal());
}
}