1use std::fmt;
4use thiserror::Error;
5
6use crate::limits::LimitViolation;
7use crate::convert::ValueConversionError;
8
9pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Error, Debug)]
14pub enum Error {
15 #[error("compilation error: {0}")]
17 Compilation(String),
18
19 #[error("runtime error: {0}")]
21 Runtime(String),
22
23 #[error("limit violation: {0}")]
25 LimitViolation(#[from] LimitViolation),
26
27 #[error("value conversion error: {0}")]
29 ValueConversion(#[from] ValueConversionError),
30
31 #[error("capability denied: {capability}")]
33 CapabilityDenied {
34 capability: String,
36 },
37
38 #[error("sandbox violation: {0}")]
40 SandboxViolation(String),
41
42 #[error("engine pool exhausted, all {count} engines busy")]
44 PoolExhausted {
45 count: usize,
47 },
48
49 #[error("timeout waiting for engine from pool")]
51 PoolTimeout,
52
53 #[error("engine pool has been shut down")]
55 PoolShutdown,
56
57 #[error("engine poisoned: {0}")]
59 EnginePoisoned(String),
60
61 #[error("io error: {0}")]
63 Io(#[from] std::io::Error),
64
65 #[error("invalid configuration: {0}")]
67 InvalidConfig(String),
68
69 #[error("version incompatibility: expected {expected}, got {actual}")]
71 VersionMismatch {
72 expected: String,
74 actual: String,
76 },
77
78 #[error("host function error: {0}")]
80 HostFunction(String),
81
82 #[error("invalid bytecode: {0}")]
84 InvalidBytecode(String),
85
86 #[error("execution timeout after {0:?}")]
88 Timeout(std::time::Duration),
89
90 #[error("execution cancelled")]
92 Cancelled,
93
94 #[error("internal error: {0}")]
96 Internal(String),
97}
98
99impl Error {
100 pub fn compilation(msg: impl Into<String>) -> Self {
102 Self::Compilation(msg.into())
103 }
104
105 pub fn runtime(msg: impl Into<String>) -> Self {
107 Self::Runtime(msg.into())
108 }
109
110 pub fn capability_denied(capability: impl Into<String>) -> Self {
112 Self::CapabilityDenied {
113 capability: capability.into(),
114 }
115 }
116
117 pub fn sandbox_violation(msg: impl Into<String>) -> Self {
119 Self::SandboxViolation(msg.into())
120 }
121
122 pub fn invalid_config(msg: impl Into<String>) -> Self {
124 Self::InvalidConfig(msg.into())
125 }
126
127 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 pub fn host_function(msg: impl Into<String>) -> Self {
137 Self::HostFunction(msg.into())
138 }
139
140 pub fn invalid_bytecode(msg: impl Into<String>) -> Self {
142 Self::InvalidBytecode(msg.into())
143 }
144
145 pub fn is_transient(&self) -> bool {
147 matches!(
148 self,
149 Self::PoolExhausted { .. } | Self::PoolTimeout | Self::Timeout(_)
150 )
151 }
152
153 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}