Skip to main content

adk_sandbox/
error.rs

1//! Error types for sandbox execution.
2
3use std::time::Duration;
4use thiserror::Error;
5
6/// Errors that can occur during sandbox execution.
7///
8/// All variants include actionable context to help callers diagnose issues.
9///
10/// # Example
11///
12/// ```rust
13/// use adk_sandbox::SandboxError;
14/// use std::time::Duration;
15///
16/// let err = SandboxError::Timeout { timeout: Duration::from_secs(30) };
17/// assert!(err.to_string().contains("30s"));
18/// ```
19#[derive(Debug, Clone, Error)]
20pub enum SandboxError {
21    /// Execution exceeded the configured timeout.
22    #[error("execution timed out after {timeout:?}")]
23    Timeout {
24        /// The timeout duration that was exceeded.
25        timeout: Duration,
26    },
27
28    /// Execution exceeded the configured memory limit (Wasm only).
29    #[error("memory limit exceeded: {limit_mb} MB")]
30    MemoryExceeded {
31        /// The memory limit in megabytes that was exceeded.
32        limit_mb: u32,
33    },
34
35    /// Execution failed due to an internal error (e.g., subprocess I/O failure).
36    #[error("execution failed: {0}")]
37    ExecutionFailed(String),
38
39    /// The request is invalid (e.g., unsupported language for this backend).
40    #[error("invalid request: {0}")]
41    InvalidRequest(String),
42
43    /// The backend is not available (e.g., missing runtime or feature not enabled).
44    #[error("backend unavailable: {0}")]
45    BackendUnavailable(String),
46
47    /// The sandbox enforcer failed to apply the profile.
48    #[error("enforcer '{enforcer}' failed: {message}")]
49    EnforcerFailed {
50        /// The enforcer name (e.g., "seatbelt", "bubblewrap", "appcontainer").
51        enforcer: String,
52        /// A descriptive message explaining what failed.
53        message: String,
54    },
55
56    /// The sandbox enforcer is not available on this system.
57    #[error("enforcer '{enforcer}' unavailable: {message}")]
58    EnforcerUnavailable {
59        /// The enforcer name.
60        enforcer: String,
61        /// A message explaining why the enforcer is not functional.
62        message: String,
63    },
64
65    /// A policy path or resource could not be resolved.
66    #[error("policy violation: {0}")]
67    PolicyViolation(String),
68
69    // ── Workspace lifecycle variants (behind `workspace` feature) ──────────
70    /// Workspace provisioning failed.
71    #[cfg(feature = "workspace")]
72    #[error("provisioning failed for '{resource}': {reason}. {suggestion}")]
73    ProvisionFailed {
74        /// The resource that failed to provision (e.g., manifest entry path).
75        resource: String,
76        /// A description of why provisioning failed.
77        reason: String,
78        /// An actionable suggestion for resolution.
79        suggestion: String,
80    },
81
82    /// Referenced session does not exist or has been stopped.
83    #[cfg(feature = "workspace")]
84    #[error("session '{handle}' not found. It may have been stopped or expired.")]
85    SessionNotFound {
86        /// The session handle that was not found.
87        handle: String,
88    },
89
90    /// Referenced snapshot does not exist.
91    #[cfg(feature = "workspace")]
92    #[error("snapshot '{id}' not found. It may have been deleted or expired.")]
93    SnapshotNotFound {
94        /// The snapshot ID that was not found.
95        id: String,
96    },
97
98    /// Path traversal attempt detected.
99    #[cfg(feature = "workspace")]
100    #[error("path traversal rejected: '{path}' escapes workspace root. Use relative paths only.")]
101    PathTraversal {
102        /// The offending path that attempted to escape the workspace root.
103        path: String,
104    },
105
106    /// Docker is not available on this host.
107    #[cfg(feature = "workspace")]
108    #[error("Docker unavailable: {reason}. Ensure Docker daemon is running and accessible.")]
109    DockerUnavailable {
110        /// A description of why Docker is unavailable.
111        reason: String,
112    },
113
114    /// Session exceeded its configured timeout.
115    #[cfg(feature = "workspace")]
116    #[error(
117        "session timed out after {timeout:?}. Consider increasing session_timeout in SandboxConfig."
118    )]
119    SessionTimeout {
120        /// The timeout duration that was exceeded.
121        timeout: Duration,
122    },
123}
124
125impl From<std::io::Error> for SandboxError {
126    fn from(err: std::io::Error) -> Self {
127        SandboxError::ExecutionFailed(format!("I/O error: {err}"))
128    }
129}
130
131impl From<SandboxError> for adk_core::AdkError {
132    fn from(err: SandboxError) -> Self {
133        use adk_core::{ErrorCategory, ErrorComponent};
134        let (category, code) = match &err {
135            SandboxError::Timeout { .. } => (ErrorCategory::Timeout, "code.sandbox_timeout"),
136            SandboxError::MemoryExceeded { .. } => (ErrorCategory::Internal, "code.sandbox_memory"),
137            SandboxError::ExecutionFailed(_) => (ErrorCategory::Internal, "code.sandbox_execution"),
138            SandboxError::InvalidRequest(_) => {
139                (ErrorCategory::InvalidInput, "code.sandbox_invalid_request")
140            }
141            SandboxError::BackendUnavailable(_) => {
142                (ErrorCategory::Unavailable, "code.sandbox_unavailable")
143            }
144            SandboxError::EnforcerFailed { .. } => {
145                (ErrorCategory::Internal, "code.sandbox_enforcer_failed")
146            }
147            SandboxError::EnforcerUnavailable { .. } => {
148                (ErrorCategory::Unavailable, "code.sandbox_enforcer_unavailable")
149            }
150            SandboxError::PolicyViolation(_) => {
151                (ErrorCategory::InvalidInput, "code.sandbox_policy_violation")
152            }
153            #[cfg(feature = "workspace")]
154            SandboxError::ProvisionFailed { .. } => {
155                (ErrorCategory::Internal, "code.sandbox_provision_failed")
156            }
157            #[cfg(feature = "workspace")]
158            SandboxError::SessionNotFound { .. } => {
159                (ErrorCategory::NotFound, "code.sandbox_session_not_found")
160            }
161            #[cfg(feature = "workspace")]
162            SandboxError::SnapshotNotFound { .. } => {
163                (ErrorCategory::NotFound, "code.sandbox_snapshot_not_found")
164            }
165            #[cfg(feature = "workspace")]
166            SandboxError::PathTraversal { .. } => {
167                (ErrorCategory::InvalidInput, "code.sandbox_path_traversal")
168            }
169            #[cfg(feature = "workspace")]
170            SandboxError::DockerUnavailable { .. } => {
171                (ErrorCategory::Unavailable, "code.sandbox_docker_unavailable")
172            }
173            #[cfg(feature = "workspace")]
174            SandboxError::SessionTimeout { .. } => {
175                (ErrorCategory::Timeout, "code.sandbox_session_timeout")
176            }
177        };
178        adk_core::AdkError::new(ErrorComponent::Code, category, code, err.to_string())
179            .with_source(err)
180    }
181}