1use std::path::PathBuf;
2use std::time::Duration;
3use thiserror::Error;
4
5pub type InstanceId = uuid::Uuid;
7
8#[derive(Debug, Clone)]
10pub enum ResourceKind {
11 Memory,
12 CpuTime,
13 Fuel,
14 FileHandles,
15 NetworkConnections,
16 ExecutionTime,
17}
18
19#[derive(Debug, Clone)]
21pub struct SecurityContext {
22 pub attempted_operation: String,
23 pub required_capability: String,
24 pub available_capabilities: Vec<String>,
25}
26
27#[derive(Error, Debug)]
29pub enum SandboxError {
30 #[error("Security violation: {violation}")]
32 SecurityViolation {
33 violation: String,
34 instance_id: Option<InstanceId>,
35 context: SecurityContext,
36 },
37
38 #[error("Resource exhausted: {kind:?} used {used}/{limit}")]
40 ResourceExhausted {
41 kind: ResourceKind,
42 limit: u64,
43 used: u64,
44 instance_id: Option<InstanceId>,
45 suggestion: Option<String>,
46 },
47
48 #[error("WebAssembly runtime error in function '{function}': {message}")]
50 WasmRuntime {
51 function: String,
52 instance_id: Option<InstanceId>,
53 message: String,
54 },
55
56 #[error("Configuration error: {message}")]
58 Configuration {
59 message: String,
60 suggestion: Option<String>,
61 field: Option<String>,
62 },
63
64 #[error("Module error: {operation} failed - {reason}")]
66 Module {
67 operation: String,
68 reason: String,
69 suggestion: Option<String>,
70 },
71
72 #[error("Instance error: {operation} failed for instance {instance_id:?} - {reason}")]
74 Instance {
75 operation: String,
76 instance_id: Option<InstanceId>,
77 reason: String,
78 },
79
80 #[error("Communication error: {channel} channel failed - {reason}")]
82 Communication {
83 channel: String,
84 reason: String,
85 instance_id: Option<InstanceId>,
86 },
87
88 #[error("Operation timed out after {duration:?}: {operation}")]
90 Timeout {
91 operation: String,
92 duration: Duration,
93 instance_id: Option<InstanceId>,
94 },
95
96 #[error("Failed to call function '{function_name}': {reason}")]
98 FunctionCall {
99 function_name: String,
100 reason: String,
101 },
102
103 #[error("Filesystem error: {operation} failed on '{path:?}' - {reason}")]
105 Filesystem {
106 operation: String,
107 path: PathBuf,
108 reason: String,
109 },
110
111 #[error("Network error: {operation} failed - {reason}")]
113 Network {
114 operation: String,
115 reason: String,
116 endpoint: Option<String>,
117 },
118
119 #[error("Serialization error: {format} {operation} failed - {reason}")]
121 Serialization {
122 format: String, operation: String, reason: String,
125 },
126
127 #[error("Invalid input: {field} - {reason}")]
129 InvalidInput {
130 field: String,
131 reason: String,
132 suggestion: Option<String>,
133 },
134
135 #[error("Resource not found: {resource_type} '{identifier}' not found")]
137 NotFound {
138 resource_type: String, identifier: String,
140 },
141
142 #[error("Unsupported operation: {operation} is not supported in this context")]
144 Unsupported {
145 operation: String,
146 context: String,
147 suggestion: Option<String>,
148 },
149
150 #[error("Generic error: {message}")]
152 Generic { message: String },
153
154 #[error("Template error: {message}")]
156 Template { message: String },
157
158 #[error("Compilation error: {message}")]
160 Compilation { message: String },
161
162 #[error("Instance creation error: {reason}")]
164 InstanceCreation {
165 reason: String,
166 instance_id: Option<InstanceId>,
167 },
168
169 #[error("Wrapper generation error: {reason}")]
171 WrapperGeneration {
172 reason: String,
173 wrapper_type: Option<String>,
174 },
175
176 #[error("Module load error: {message}")]
178 ModuleLoad { message: String },
179
180 #[error("Runtime initialization error: {message}")]
182 RuntimeInitialization { message: String },
183
184 #[error("IO error: {message}")]
186 IoError { message: String },
187
188 #[error("Capability error: {message}")]
190 Capability { message: String },
191
192 #[error("Resource limit error: {message}")]
194 ResourceLimit { message: String },
195
196 #[error("Unsupported operation: {message}")]
198 UnsupportedOperation { message: String },
199
200 #[error("IO error: {0}")]
202 Io(#[from] std::io::Error),
203
204 #[error("JSON error: {0}")]
205 Json(#[from] serde_json::Error),
206
207 #[error("MessagePack encode error: {0}")]
208 MessagePack(#[from] rmp_serde::encode::Error),
209
210 #[error("MessagePack decode error: {0}")]
211 MessagePackDecode(#[from] rmp_serde::decode::Error),
212
213 #[error(transparent)]
214 Other(#[from] anyhow::Error),
215}
216
217impl SandboxError {
219 pub fn security_violation(violation: impl Into<String>, context: SecurityContext) -> Self {
221 Self::SecurityViolation {
222 violation: violation.into(),
223 instance_id: None,
224 context,
225 }
226 }
227
228 pub fn resource_exhausted(
230 kind: ResourceKind,
231 used: u64,
232 limit: u64,
233 suggestion: Option<String>,
234 ) -> Self {
235 Self::ResourceExhausted {
236 kind,
237 limit,
238 used,
239 instance_id: None,
240 suggestion,
241 }
242 }
243
244 pub fn config_error(message: impl Into<String>, suggestion: Option<String>) -> Self {
246 Self::Configuration {
247 message: message.into(),
248 suggestion,
249 field: None,
250 }
251 }
252
253 pub fn module_load_error(reason: impl Into<String>) -> Self {
255 Self::Module {
256 operation: "load".to_string(),
257 reason: reason.into(),
258 suggestion: Some("Check that the WASM file is valid and accessible".to_string()),
259 }
260 }
261
262 pub fn function_call_error(function_name: impl Into<String>, reason: impl Into<String>) -> Self {
264 Self::FunctionCall {
265 function_name: function_name.into(),
266 reason: reason.into(),
267 }
268 }
269}
270
271impl Clone for SandboxError {
272 fn clone(&self) -> Self {
273 match self {
274 SandboxError::SecurityViolation { violation, instance_id, context } => {
275 SandboxError::SecurityViolation {
276 violation: violation.clone(),
277 instance_id: *instance_id,
278 context: context.clone(),
279 }
280 }
281 SandboxError::ResourceExhausted { kind, limit, used, instance_id, suggestion } => {
282 SandboxError::ResourceExhausted {
283 kind: kind.clone(),
284 limit: *limit,
285 used: *used,
286 instance_id: *instance_id,
287 suggestion: suggestion.clone(),
288 }
289 }
290 SandboxError::WasmRuntime { function, instance_id, message } => {
291 SandboxError::WasmRuntime {
292 function: function.clone(),
293 instance_id: *instance_id,
294 message: message.clone(),
295 }
296 }
297 SandboxError::Configuration { message, suggestion, field } => {
298 SandboxError::Configuration {
299 message: message.clone(),
300 suggestion: suggestion.clone(),
301 field: field.clone(),
302 }
303 }
304 SandboxError::Module { operation, reason, suggestion } => {
305 SandboxError::Module {
306 operation: operation.clone(),
307 reason: reason.clone(),
308 suggestion: suggestion.clone(),
309 }
310 }
311 SandboxError::Instance { operation, instance_id, reason } => {
312 SandboxError::Instance {
313 operation: operation.clone(),
314 instance_id: *instance_id,
315 reason: reason.clone(),
316 }
317 }
318 SandboxError::Communication { channel, reason, instance_id } => {
319 SandboxError::Communication {
320 channel: channel.clone(),
321 reason: reason.clone(),
322 instance_id: *instance_id,
323 }
324 }
325 SandboxError::Timeout { operation, duration, instance_id } => {
326 SandboxError::Timeout {
327 operation: operation.clone(),
328 duration: *duration,
329 instance_id: *instance_id,
330 }
331 }
332 SandboxError::FunctionCall { function_name, reason } => {
333 SandboxError::FunctionCall {
334 function_name: function_name.clone(),
335 reason: reason.clone(),
336 }
337 }
338 SandboxError::Filesystem { operation, path, reason } => {
339 SandboxError::Filesystem {
340 operation: operation.clone(),
341 path: path.clone(),
342 reason: reason.clone(),
343 }
344 }
345 SandboxError::Network { operation, reason, endpoint } => {
346 SandboxError::Network {
347 operation: operation.clone(),
348 reason: reason.clone(),
349 endpoint: endpoint.clone(),
350 }
351 }
352 SandboxError::Serialization { format, operation, reason } => {
353 SandboxError::Serialization {
354 format: format.clone(),
355 operation: operation.clone(),
356 reason: reason.clone(),
357 }
358 }
359 SandboxError::InvalidInput { field, reason, suggestion } => {
360 SandboxError::InvalidInput {
361 field: field.clone(),
362 reason: reason.clone(),
363 suggestion: suggestion.clone(),
364 }
365 }
366 SandboxError::NotFound { resource_type, identifier } => {
367 SandboxError::NotFound {
368 resource_type: resource_type.clone(),
369 identifier: identifier.clone(),
370 }
371 }
372 SandboxError::Unsupported { operation, context, suggestion } => {
373 SandboxError::Unsupported {
374 operation: operation.clone(),
375 context: context.clone(),
376 suggestion: suggestion.clone(),
377 }
378 }
379 SandboxError::Generic { message } => {
380 SandboxError::Generic {
381 message: message.clone(),
382 }
383 }
384 SandboxError::Template { message } => {
385 SandboxError::Template {
386 message: message.clone(),
387 }
388 }
389 SandboxError::Compilation { message } => {
390 SandboxError::Compilation {
391 message: message.clone(),
392 }
393 }
394 SandboxError::InstanceCreation { reason, instance_id } => {
395 SandboxError::InstanceCreation {
396 reason: reason.clone(),
397 instance_id: *instance_id,
398 }
399 }
400 SandboxError::WrapperGeneration { reason, wrapper_type } => {
401 SandboxError::WrapperGeneration {
402 reason: reason.clone(),
403 wrapper_type: wrapper_type.clone(),
404 }
405 }
406 SandboxError::ModuleLoad { message } => {
407 SandboxError::ModuleLoad {
408 message: message.clone(),
409 }
410 }
411 SandboxError::RuntimeInitialization { message } => {
412 SandboxError::RuntimeInitialization {
413 message: message.clone(),
414 }
415 }
416 SandboxError::IoError { message } => {
417 SandboxError::IoError {
418 message: message.clone(),
419 }
420 }
421 SandboxError::Capability { message } => {
422 SandboxError::Capability {
423 message: message.clone(),
424 }
425 }
426 SandboxError::ResourceLimit { message } => {
427 SandboxError::ResourceLimit {
428 message: message.clone(),
429 }
430 }
431 SandboxError::UnsupportedOperation { message } => {
432 SandboxError::UnsupportedOperation {
433 message: message.clone(),
434 }
435 }
436 SandboxError::Io(e) => SandboxError::Filesystem {
438 operation: "io".to_string(),
439 path: "unknown".into(),
440 reason: e.to_string(),
441 },
442 SandboxError::Json(e) => SandboxError::Serialization {
443 format: "json".to_string(),
444 operation: "parse".to_string(),
445 reason: e.to_string(),
446 },
447 SandboxError::MessagePack(e) => SandboxError::Serialization {
448 format: "messagepack".to_string(),
449 operation: "encode".to_string(),
450 reason: e.to_string(),
451 },
452 SandboxError::MessagePackDecode(e) => SandboxError::Serialization {
453 format: "messagepack".to_string(),
454 operation: "decode".to_string(),
455 reason: e.to_string(),
456 },
457 SandboxError::Other(e) => SandboxError::Configuration {
458 message: e.to_string(),
459 suggestion: None,
460 field: None,
461 },
462 }
463 }
464}
465
466pub type Result<T> = std::result::Result<T, SandboxError>;
468
469pub type Error = SandboxError;