Skip to main content

ciab_core/
error.rs

1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use serde_json::json;
4
5pub type CiabResult<T> = Result<T, CiabError>;
6
7#[derive(Debug, thiserror::Error)]
8pub enum CiabError {
9    // Sandbox errors
10    #[error("sandbox not found: {0}")]
11    SandboxNotFound(String),
12    #[error("sandbox already exists: {0}")]
13    SandboxAlreadyExists(String),
14    #[error("sandbox in invalid state: {current}, expected: {expected}")]
15    SandboxInvalidState { current: String, expected: String },
16    #[error("sandbox creation failed: {0}")]
17    SandboxCreationFailed(String),
18    #[error("sandbox timeout: {0}")]
19    SandboxTimeout(String),
20
21    // Agent errors
22    #[error("agent provider not found: {0}")]
23    AgentProviderNotFound(String),
24    #[error("agent not running")]
25    AgentNotRunning,
26    #[error("agent communication error: {0}")]
27    AgentCommunicationError(String),
28
29    // Session errors
30    #[error("session not found: {0}")]
31    SessionNotFound(String),
32    #[error("session in invalid state: {0}")]
33    SessionInvalidState(String),
34
35    // Workspace errors
36    #[error("workspace not found: {0}")]
37    WorkspaceNotFound(String),
38    #[error("workspace already exists: {0}")]
39    WorkspaceAlreadyExists(String),
40    #[error("workspace validation error: {0}")]
41    WorkspaceValidationError(String),
42
43    // Template errors
44    #[error("template source not found: {0}")]
45    TemplateSourceNotFound(String),
46    #[error("template sync failed: {0}")]
47    TemplateSyncFailed(String),
48
49    // Credential errors
50    #[error("credential not found: {0}")]
51    CredentialNotFound(String),
52    #[error("decryption failed: {0}")]
53    DecryptionFailed(String),
54    #[error("OAuth flow failed: {0}")]
55    OAuthFlowFailed(String),
56    #[error("OAuth token expired")]
57    OAuthTokenExpired,
58
59    // Runtime errors
60    #[error("runtime unavailable: {0}")]
61    RuntimeUnavailable(String),
62    #[error("OpenSandbox error: {0}")]
63    OpenSandboxError(String),
64    #[error("kubernetes error: {0}")]
65    KubernetesError(String),
66    #[error("kubernetes pod not found: {0}")]
67    KubernetesPodNotFound(String),
68
69    #[error("EC2 error: {0}")]
70    Ec2Error(String),
71
72    #[error("SSH error: {0}")]
73    SshError(String),
74
75    #[error("Packer error: {0}")]
76    PackerError(String),
77
78    #[error("Image build error: {0}")]
79    ImageBuildError(String),
80
81    #[error("Resource resolution error: {0}")]
82    ResourceResolutionError(String),
83
84    #[error("Unsupported operation: {0}")]
85    Unsupported(String),
86
87    // Provisioning errors
88    #[error("provisioning failed: {0}")]
89    ProvisioningFailed(String),
90    #[error("git clone failed: {0}")]
91    GitCloneFailed(String),
92    #[error("script execution failed: {0}")]
93    ScriptExecutionFailed(String),
94    #[error("local mount failed: {0}")]
95    LocalMountFailed(String),
96    #[error("git worktree failed: {0}")]
97    GitWorktreeFailed(String),
98    #[error("agentfs error: {0}")]
99    AgentFsError(String),
100
101    // Stream errors
102    #[error("stream buffer overflow")]
103    StreamBufferOverflow,
104    #[error("stream connection lost")]
105    StreamConnectionLost,
106
107    // IO errors
108    #[error("file not found: {0}")]
109    FileNotFound(String),
110    #[error("exec failed: {0}")]
111    ExecFailed(String),
112
113    // Config errors
114    #[error("configuration error: {0}")]
115    ConfigError(String),
116    #[error("configuration validation error: {0}")]
117    ConfigValidationError(String),
118
119    // Auth errors
120    #[error("unauthorized: {0}")]
121    Unauthorized(String),
122    #[error("forbidden: {0}")]
123    Forbidden(String),
124    #[error("rate limited")]
125    RateLimited,
126
127    // Gateway errors
128    #[error("tunnel not found: {0}")]
129    TunnelNotFound(String),
130    #[error("tunnel creation failed: {0}")]
131    TunnelCreationFailed(String),
132    #[error("client token not found: {0}")]
133    ClientTokenNotFound(String),
134    #[error("client token expired")]
135    ClientTokenExpired,
136    #[error("client token revoked")]
137    ClientTokenRevoked,
138    #[error("insufficient scope: {0}")]
139    InsufficientScope(String),
140    #[error("gateway not enabled")]
141    GatewayNotEnabled,
142    #[error("FRP error: {0}")]
143    FrpError(String),
144    #[error("tunnel provider error: {0}")]
145    TunnelProviderError(String),
146    #[error("tunnel provider not ready: {0}")]
147    TunnelProviderNotReady(String),
148
149    // Channel errors
150    #[error("channel not found: {0}")]
151    ChannelNotFound(String),
152    #[error("channel adapter error: {0}")]
153    ChannelAdapterError(String),
154    #[error("channel sender not allowed: {0}")]
155    ChannelSenderNotAllowed(String),
156
157    // Generic errors
158    #[error("internal error: {0}")]
159    Internal(String),
160    #[error("external error: {0}")]
161    External(String),
162    #[error("timeout: {0}")]
163    Timeout(String),
164
165    // Wrapping
166    #[error("database error: {0}")]
167    Database(String),
168    #[error("serialization error: {0}")]
169    Serialization(#[from] serde_json::Error),
170}
171
172impl CiabError {
173    pub fn status_code(&self) -> u16 {
174        match self {
175            Self::SandboxNotFound(_)
176            | Self::SessionNotFound(_)
177            | Self::WorkspaceNotFound(_)
178            | Self::CredentialNotFound(_)
179            | Self::FileNotFound(_)
180            | Self::AgentProviderNotFound(_)
181            | Self::TemplateSourceNotFound(_)
182            | Self::TunnelNotFound(_)
183            | Self::ClientTokenNotFound(_)
184            | Self::ChannelNotFound(_) => 404,
185            Self::SandboxAlreadyExists(_) | Self::WorkspaceAlreadyExists(_) => 409,
186            Self::SandboxInvalidState { .. } | Self::SessionInvalidState(_) => 409,
187            Self::Unauthorized(_) => 401,
188            Self::Forbidden(_)
189            | Self::ClientTokenExpired
190            | Self::ClientTokenRevoked
191            | Self::InsufficientScope(_)
192            | Self::ChannelSenderNotAllowed(_) => 403,
193            Self::GatewayNotEnabled | Self::TunnelProviderNotReady(_) => 503,
194            Self::RateLimited => 429,
195            Self::ConfigError(_)
196            | Self::ConfigValidationError(_)
197            | Self::WorkspaceValidationError(_)
198            | Self::AgentCommunicationError(_)
199            | Self::TunnelProviderError(_) => 400,
200            Self::SandboxTimeout(_) | Self::Timeout(_) => 504,
201            Self::Ec2Error(_) | Self::SshError(_) => 502,
202            Self::PackerError(_) | Self::ImageBuildError(_) => 500,
203            Self::ResourceResolutionError(_) => 400,
204            Self::Unsupported(_) => 501,
205            _ => 500,
206        }
207    }
208
209    pub fn error_code(&self) -> &str {
210        match self {
211            Self::SandboxNotFound(_) => "sandbox_not_found",
212            Self::SandboxAlreadyExists(_) => "sandbox_already_exists",
213            Self::SandboxInvalidState { .. } => "sandbox_invalid_state",
214            Self::SandboxCreationFailed(_) => "sandbox_creation_failed",
215            Self::SandboxTimeout(_) => "sandbox_timeout",
216            Self::AgentProviderNotFound(_) => "agent_provider_not_found",
217            Self::AgentNotRunning => "agent_not_running",
218            Self::AgentCommunicationError(_) => "agent_communication_error",
219            Self::SessionNotFound(_) => "session_not_found",
220            Self::SessionInvalidState(_) => "session_invalid_state",
221            Self::WorkspaceNotFound(_) => "workspace_not_found",
222            Self::WorkspaceAlreadyExists(_) => "workspace_already_exists",
223            Self::WorkspaceValidationError(_) => "workspace_validation_error",
224            Self::TemplateSourceNotFound(_) => "template_source_not_found",
225            Self::TemplateSyncFailed(_) => "template_sync_failed",
226            Self::CredentialNotFound(_) => "credential_not_found",
227            Self::DecryptionFailed(_) => "decryption_failed",
228            Self::OAuthFlowFailed(_) => "oauth_flow_failed",
229            Self::OAuthTokenExpired => "oauth_token_expired",
230            Self::RuntimeUnavailable(_) => "runtime_unavailable",
231            Self::OpenSandboxError(_) => "opensandbox_error",
232            Self::KubernetesError(_) => "kubernetes_error",
233            Self::KubernetesPodNotFound(_) => "kubernetes_pod_not_found",
234            Self::Ec2Error(_) => "EC2_ERROR",
235            Self::SshError(_) => "SSH_ERROR",
236            Self::PackerError(_) => "PACKER_ERROR",
237            Self::ImageBuildError(_) => "IMAGE_BUILD_ERROR",
238            Self::ResourceResolutionError(_) => "RESOURCE_RESOLUTION_ERROR",
239            Self::Unsupported(_) => "UNSUPPORTED",
240            Self::ProvisioningFailed(_) => "provisioning_failed",
241            Self::GitCloneFailed(_) => "git_clone_failed",
242            Self::ScriptExecutionFailed(_) => "script_execution_failed",
243            Self::LocalMountFailed(_) => "local_mount_failed",
244            Self::GitWorktreeFailed(_) => "git_worktree_failed",
245            Self::AgentFsError(_) => "agentfs_error",
246            Self::StreamBufferOverflow => "stream_buffer_overflow",
247            Self::StreamConnectionLost => "stream_connection_lost",
248            Self::FileNotFound(_) => "file_not_found",
249            Self::ExecFailed(_) => "exec_failed",
250            Self::ConfigError(_) => "config_error",
251            Self::ConfigValidationError(_) => "config_validation_error",
252            Self::Unauthorized(_) => "unauthorized",
253            Self::Forbidden(_) => "forbidden",
254            Self::RateLimited => "rate_limited",
255            Self::TunnelNotFound(_) => "tunnel_not_found",
256            Self::TunnelCreationFailed(_) => "tunnel_creation_failed",
257            Self::ClientTokenNotFound(_) => "client_token_not_found",
258            Self::ClientTokenExpired => "client_token_expired",
259            Self::ClientTokenRevoked => "client_token_revoked",
260            Self::InsufficientScope(_) => "insufficient_scope",
261            Self::GatewayNotEnabled => "gateway_not_enabled",
262            Self::FrpError(_) => "frp_error",
263            Self::TunnelProviderError(_) => "tunnel_provider_error",
264            Self::TunnelProviderNotReady(_) => "tunnel_provider_not_ready",
265            Self::ChannelNotFound(_) => "channel_not_found",
266            Self::ChannelAdapterError(_) => "channel_adapter_error",
267            Self::ChannelSenderNotAllowed(_) => "channel_sender_not_allowed",
268            Self::Internal(_) => "internal_error",
269            Self::External(_) => "external_error",
270            Self::Timeout(_) => "timeout",
271            Self::Database(_) => "database_error",
272            Self::Serialization(_) => "serialization_error",
273        }
274    }
275}
276
277impl IntoResponse for CiabError {
278    fn into_response(self) -> Response {
279        let status =
280            StatusCode::from_u16(self.status_code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
281        let body = json!({
282            "error": {
283                "code": self.error_code(),
284                "message": self.to_string(),
285            }
286        });
287        (status, axum::Json(body)).into_response()
288    }
289}