1use thiserror::Error;
10
11#[derive(Error, Debug)]
27pub enum RuntimeError {
28 #[error("Service unavailable: {message}")]
34 Unavailable {
35 message: String,
36 target: Option<actr_protocol::ActrId>,
38 },
39
40 #[error("Deadline exceeded: {message}")]
45 DeadlineExceeded { message: String, timeout_ms: u64 },
46
47 #[error("Actor not found: {actor_id:?}")]
53 NotFound {
54 actor_id: actr_protocol::ActrId,
55 message: String,
56 },
57
58 #[error("Invalid argument: {0}")]
63 InvalidArgument(String),
64
65 #[error("Failed precondition: {0}")]
70 FailedPrecondition(String),
71
72 #[error("Permission denied: {0}")]
77 PermissionDenied(String),
78
79 #[error("Protobuf decode failed: {message}")]
85 DecodeFailure {
86 message: String,
87 raw_bytes: Option<Vec<u8>>,
89 },
90
91 #[error("Internal error: {message}")]
97 Internal {
98 message: String,
99 panic_info: Option<String>,
101 },
102
103 #[error("Mailbox error: {0}")]
108 MailboxError(String),
109
110 #[error("Configuration error: {0}")]
113 ConfigurationError(String),
114
115 #[error("Initialization error: {0}")]
117 InitializationError(String),
118
119 #[error("Shutdown error: {0}")]
121 ShutdownError(String),
122
123 #[error("IO error: {0}")]
125 IoError(#[from] std::io::Error),
126
127 #[error("JSON error: {0}")]
129 JsonError(#[from] serde_json::Error),
130
131 #[error("Protocol error: {0}")]
133 ProtocolError(#[from] actr_protocol::ProtocolError),
134
135 #[error("Other error: {0}")]
137 Other(#[from] anyhow::Error),
138}
139
140impl From<crate::transport::error::NetworkError> for RuntimeError {
141 fn from(err: crate::transport::error::NetworkError) -> Self {
142 use crate::transport::error::NetworkError;
144 match err {
145 NetworkError::ConnectionError(_)
147 | NetworkError::SignalingError(_)
148 | NetworkError::WebRtcError(_)
149 | NetworkError::NetworkUnreachableError(_)
150 | NetworkError::ResourceExhaustedError(_)
151 | NetworkError::NatTraversalError(_)
152 | NetworkError::IceError(_)
153 | NetworkError::WebSocketError(_) => RuntimeError::Unavailable {
154 message: err.to_string(),
155 target: None,
156 },
157
158 NetworkError::TimeoutError(_) => RuntimeError::DeadlineExceeded {
160 message: err.to_string(),
161 timeout_ms: 0,
162 },
163
164 NetworkError::ConnectionNotFound(_)
166 | NetworkError::ChannelNotFound(_)
167 | NetworkError::NoRoute(_) => RuntimeError::NotFound {
168 actor_id: actr_protocol::ActrId::default(),
169 message: err.to_string(),
170 },
171
172 NetworkError::InvalidArgument(_) | NetworkError::InvalidOperation(_) => {
174 RuntimeError::InvalidArgument(err.to_string())
175 }
176
177 NetworkError::ConfigurationError(_) => {
179 RuntimeError::ConfigurationError(err.to_string())
180 }
181
182 NetworkError::AuthenticationError(_) | NetworkError::PermissionError(_) => {
184 RuntimeError::PermissionDenied(err.to_string())
185 }
186
187 NetworkError::DeserializationError(msg) => RuntimeError::DecodeFailure {
189 message: msg,
190 raw_bytes: None,
191 },
192
193 NetworkError::ProtocolError(_)
195 | NetworkError::SerializationError(_)
196 | NetworkError::DataChannelError(_)
197 | NetworkError::BroadcastError(_)
198 | NetworkError::DtlsError(_)
199 | NetworkError::StunTurnError(_)
200 | NetworkError::ServiceDiscoveryError(_)
201 | NetworkError::NotImplemented(_)
202 | NetworkError::ChannelClosed(_)
203 | NetworkError::ConnectionClosed(_)
204 | NetworkError::SendError(_)
205 | NetworkError::IoError(_)
206 | NetworkError::UrlParseError(_)
207 | NetworkError::JsonError(_)
208 | NetworkError::Timeout(_)
209 | NetworkError::Other(_) => RuntimeError::Other(anyhow::anyhow!("{err}")),
210 }
211 }
212}
213
214impl RuntimeError {
215 pub fn classification(&self) -> ErrorClassification {
222 match self {
223 RuntimeError::Unavailable { .. } | RuntimeError::DeadlineExceeded { .. } => {
225 ErrorClassification::Transient
226 }
227
228 RuntimeError::NotFound { .. }
230 | RuntimeError::InvalidArgument(_)
231 | RuntimeError::FailedPrecondition(_)
232 | RuntimeError::PermissionDenied(_)
233 | RuntimeError::ConfigurationError(_)
234 | RuntimeError::InitializationError(_) => ErrorClassification::Permanent,
235
236 RuntimeError::DecodeFailure { .. } => ErrorClassification::Poison,
238
239 RuntimeError::Internal { .. } | RuntimeError::MailboxError(_) => {
241 ErrorClassification::Internal
242 }
243
244 RuntimeError::ShutdownError(_)
246 | RuntimeError::IoError(_)
247 | RuntimeError::JsonError(_)
248 | RuntimeError::ProtocolError(_)
249 | RuntimeError::Other(_) => ErrorClassification::Permanent,
250 }
251 }
252
253 pub fn is_retryable(&self) -> bool {
257 matches!(
258 self.classification(),
259 ErrorClassification::Transient | ErrorClassification::Internal
260 )
261 }
262
263 pub fn requires_dlq(&self) -> bool {
267 matches!(self.classification(), ErrorClassification::Poison)
268 }
269
270 pub fn status_code(&self) -> &'static str {
274 match self {
275 RuntimeError::Unavailable { .. } => "UNAVAILABLE",
276 RuntimeError::DeadlineExceeded { .. } => "DEADLINE_EXCEEDED",
277 RuntimeError::NotFound { .. } => "NOT_FOUND",
278 RuntimeError::InvalidArgument(_) => "INVALID_ARGUMENT",
279 RuntimeError::FailedPrecondition(_) => "FAILED_PRECONDITION",
280 RuntimeError::PermissionDenied(_) => "PERMISSION_DENIED",
281 RuntimeError::DecodeFailure { .. } => "DATA_LOSS",
282 RuntimeError::Internal { .. } => "INTERNAL",
283 RuntimeError::MailboxError(_) => "INTERNAL",
284 RuntimeError::ConfigurationError(_) => "FAILED_PRECONDITION",
285 RuntimeError::InitializationError(_) => "FAILED_PRECONDITION",
286 RuntimeError::ShutdownError(_) => "UNAVAILABLE",
287 RuntimeError::IoError(_) => "INTERNAL",
288 RuntimeError::JsonError(_) => "INTERNAL",
289 RuntimeError::ProtocolError(_) => "INTERNAL",
290 RuntimeError::Other(_) => "UNKNOWN",
291 }
292 }
293
294 pub fn severity(&self) -> u8 {
298 match self {
299 RuntimeError::ConfigurationError(_) | RuntimeError::InitializationError(_) => 10,
301
302 RuntimeError::MailboxError(_) | RuntimeError::DecodeFailure { .. } => 9,
304
305 RuntimeError::Internal { .. } => 8,
307
308 RuntimeError::PermissionDenied(_) => 7,
310
311 RuntimeError::NotFound { .. }
313 | RuntimeError::InvalidArgument(_)
314 | RuntimeError::FailedPrecondition(_) => 5,
315
316 RuntimeError::Unavailable { .. } | RuntimeError::DeadlineExceeded { .. } => 3,
318
319 RuntimeError::ShutdownError(_) => 2,
321
322 RuntimeError::IoError(_) | RuntimeError::JsonError(_) => 1,
324
325 RuntimeError::ProtocolError(_) | RuntimeError::Other(_) => 4,
327 }
328 }
329
330 pub fn requires_system_shutdown(&self) -> bool {
334 matches!(
335 self,
336 RuntimeError::ConfigurationError(_) | RuntimeError::InitializationError(_)
337 )
338 }
339
340 pub fn category(&self) -> &'static str {
344 match self {
345 RuntimeError::Unavailable { .. } => "unavailable",
346 RuntimeError::DeadlineExceeded { .. } => "timeout",
347 RuntimeError::NotFound { .. } => "not_found",
348 RuntimeError::InvalidArgument(_) => "invalid_argument",
349 RuntimeError::FailedPrecondition(_) => "failed_precondition",
350 RuntimeError::PermissionDenied(_) => "permission_denied",
351 RuntimeError::DecodeFailure { .. } => "decode_failure",
352 RuntimeError::Internal { .. } => "internal",
353 RuntimeError::MailboxError(_) => "mailbox",
354 RuntimeError::ConfigurationError(_) => "configuration",
355 RuntimeError::InitializationError(_) => "initialization",
356 RuntimeError::ShutdownError(_) => "shutdown",
357 RuntimeError::IoError(_) => "io",
358 RuntimeError::JsonError(_) => "json",
359 RuntimeError::ProtocolError(_) => "protocol",
360 RuntimeError::Other(_) => "other",
361 }
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367pub enum ErrorClassification {
368 Transient,
370 Permanent,
372 Poison,
374 Internal,
376}
377
378pub type RuntimeResult<T> = Result<T, RuntimeError>;