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::SendError(_)
204 | NetworkError::IoError(_)
205 | NetworkError::UrlParseError(_)
206 | NetworkError::JsonError(_)
207 | NetworkError::Other(_) => RuntimeError::Other(anyhow::anyhow!("{err}")),
208 }
209 }
210}
211
212impl RuntimeError {
213 pub fn classification(&self) -> ErrorClassification {
220 match self {
221 RuntimeError::Unavailable { .. } | RuntimeError::DeadlineExceeded { .. } => {
223 ErrorClassification::Transient
224 }
225
226 RuntimeError::NotFound { .. }
228 | RuntimeError::InvalidArgument(_)
229 | RuntimeError::FailedPrecondition(_)
230 | RuntimeError::PermissionDenied(_)
231 | RuntimeError::ConfigurationError(_)
232 | RuntimeError::InitializationError(_) => ErrorClassification::Permanent,
233
234 RuntimeError::DecodeFailure { .. } => ErrorClassification::Poison,
236
237 RuntimeError::Internal { .. } | RuntimeError::MailboxError(_) => {
239 ErrorClassification::Internal
240 }
241
242 RuntimeError::ShutdownError(_)
244 | RuntimeError::IoError(_)
245 | RuntimeError::JsonError(_)
246 | RuntimeError::ProtocolError(_)
247 | RuntimeError::Other(_) => ErrorClassification::Permanent,
248 }
249 }
250
251 pub fn is_retryable(&self) -> bool {
255 matches!(
256 self.classification(),
257 ErrorClassification::Transient | ErrorClassification::Internal
258 )
259 }
260
261 pub fn requires_dlq(&self) -> bool {
265 matches!(self.classification(), ErrorClassification::Poison)
266 }
267
268 pub fn status_code(&self) -> &'static str {
272 match self {
273 RuntimeError::Unavailable { .. } => "UNAVAILABLE",
274 RuntimeError::DeadlineExceeded { .. } => "DEADLINE_EXCEEDED",
275 RuntimeError::NotFound { .. } => "NOT_FOUND",
276 RuntimeError::InvalidArgument(_) => "INVALID_ARGUMENT",
277 RuntimeError::FailedPrecondition(_) => "FAILED_PRECONDITION",
278 RuntimeError::PermissionDenied(_) => "PERMISSION_DENIED",
279 RuntimeError::DecodeFailure { .. } => "DATA_LOSS",
280 RuntimeError::Internal { .. } => "INTERNAL",
281 RuntimeError::MailboxError(_) => "INTERNAL",
282 RuntimeError::ConfigurationError(_) => "FAILED_PRECONDITION",
283 RuntimeError::InitializationError(_) => "FAILED_PRECONDITION",
284 RuntimeError::ShutdownError(_) => "UNAVAILABLE",
285 RuntimeError::IoError(_) => "INTERNAL",
286 RuntimeError::JsonError(_) => "INTERNAL",
287 RuntimeError::ProtocolError(_) => "INTERNAL",
288 RuntimeError::Other(_) => "UNKNOWN",
289 }
290 }
291
292 pub fn severity(&self) -> u8 {
296 match self {
297 RuntimeError::ConfigurationError(_) | RuntimeError::InitializationError(_) => 10,
299
300 RuntimeError::MailboxError(_) | RuntimeError::DecodeFailure { .. } => 9,
302
303 RuntimeError::Internal { .. } => 8,
305
306 RuntimeError::PermissionDenied(_) => 7,
308
309 RuntimeError::NotFound { .. }
311 | RuntimeError::InvalidArgument(_)
312 | RuntimeError::FailedPrecondition(_) => 5,
313
314 RuntimeError::Unavailable { .. } | RuntimeError::DeadlineExceeded { .. } => 3,
316
317 RuntimeError::ShutdownError(_) => 2,
319
320 RuntimeError::IoError(_) | RuntimeError::JsonError(_) => 1,
322
323 RuntimeError::ProtocolError(_) | RuntimeError::Other(_) => 4,
325 }
326 }
327
328 pub fn requires_system_shutdown(&self) -> bool {
332 matches!(
333 self,
334 RuntimeError::ConfigurationError(_) | RuntimeError::InitializationError(_)
335 )
336 }
337
338 pub fn category(&self) -> &'static str {
342 match self {
343 RuntimeError::Unavailable { .. } => "unavailable",
344 RuntimeError::DeadlineExceeded { .. } => "timeout",
345 RuntimeError::NotFound { .. } => "not_found",
346 RuntimeError::InvalidArgument(_) => "invalid_argument",
347 RuntimeError::FailedPrecondition(_) => "failed_precondition",
348 RuntimeError::PermissionDenied(_) => "permission_denied",
349 RuntimeError::DecodeFailure { .. } => "decode_failure",
350 RuntimeError::Internal { .. } => "internal",
351 RuntimeError::MailboxError(_) => "mailbox",
352 RuntimeError::ConfigurationError(_) => "configuration",
353 RuntimeError::InitializationError(_) => "initialization",
354 RuntimeError::ShutdownError(_) => "shutdown",
355 RuntimeError::IoError(_) => "io",
356 RuntimeError::JsonError(_) => "json",
357 RuntimeError::ProtocolError(_) => "protocol",
358 RuntimeError::Other(_) => "other",
359 }
360 }
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum ErrorClassification {
366 Transient,
368 Permanent,
370 Poison,
372 Internal,
374}
375
376pub type RuntimeResult<T> = Result<T, RuntimeError>;