1use super::error::CoreError;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum ErrorKind {
25 Computation,
27 Domain,
29 Dispatch,
31 Convergence,
33 Dimension,
35 Shape,
37 Index,
39 Value,
41 Type,
43 NotImplemented,
45 Implementation,
47 Memory,
49 Allocation,
51 Config,
53 InvalidArgument,
55 InvalidInput,
57 Permission,
59 Validation,
61 InvalidState,
63 Jit,
65 Json,
67 Io,
69 Scheduler,
71 Timeout,
73 Compression,
75 InvalidShape,
77 Device,
79 Mutex,
81 Thread,
83 Stream,
85 EndOfStream,
87 Resource,
89 Communication,
91 Security,
93}
94
95impl ErrorKind {
96 pub const fn as_str(&self) -> &'static str {
98 match self {
99 ErrorKind::Computation => "computation",
100 ErrorKind::Domain => "domain",
101 ErrorKind::Dispatch => "dispatch",
102 ErrorKind::Convergence => "convergence",
103 ErrorKind::Dimension => "dimension",
104 ErrorKind::Shape => "shape",
105 ErrorKind::Index => "index",
106 ErrorKind::Value => "value",
107 ErrorKind::Type => "type",
108 ErrorKind::NotImplemented => "not_implemented",
109 ErrorKind::Implementation => "implementation",
110 ErrorKind::Memory => "memory",
111 ErrorKind::Allocation => "allocation",
112 ErrorKind::Config => "config",
113 ErrorKind::InvalidArgument => "invalid_argument",
114 ErrorKind::InvalidInput => "invalid_input",
115 ErrorKind::Permission => "permission",
116 ErrorKind::Validation => "validation",
117 ErrorKind::InvalidState => "invalid_state",
118 ErrorKind::Jit => "jit",
119 ErrorKind::Json => "json",
120 ErrorKind::Io => "io",
121 ErrorKind::Scheduler => "scheduler",
122 ErrorKind::Timeout => "timeout",
123 ErrorKind::Compression => "compression",
124 ErrorKind::InvalidShape => "invalid_shape",
125 ErrorKind::Device => "device",
126 ErrorKind::Mutex => "mutex",
127 ErrorKind::Thread => "thread",
128 ErrorKind::Stream => "stream",
129 ErrorKind::EndOfStream => "end_of_stream",
130 ErrorKind::Resource => "resource",
131 ErrorKind::Communication => "communication",
132 ErrorKind::Security => "security",
133 }
134 }
135
136 pub const fn is_shape_related(&self) -> bool {
138 matches!(
139 self,
140 ErrorKind::Dimension | ErrorKind::Shape | ErrorKind::InvalidShape
141 )
142 }
143
144 pub const fn is_resource_related(&self) -> bool {
146 matches!(
147 self,
148 ErrorKind::Memory | ErrorKind::Allocation | ErrorKind::Resource | ErrorKind::Timeout
149 )
150 }
151
152 pub const fn is_io_related(&self) -> bool {
154 matches!(
155 self,
156 ErrorKind::Io | ErrorKind::Stream | ErrorKind::EndOfStream | ErrorKind::Communication
157 )
158 }
159
160 pub const fn is_concurrency_related(&self) -> bool {
162 matches!(
163 self,
164 ErrorKind::Mutex | ErrorKind::Thread | ErrorKind::Scheduler
165 )
166 }
167
168 pub const fn is_programming_error(&self) -> bool {
170 matches!(
171 self,
172 ErrorKind::InvalidArgument
173 | ErrorKind::InvalidInput
174 | ErrorKind::InvalidState
175 | ErrorKind::NotImplemented
176 | ErrorKind::Implementation
177 )
178 }
179
180 pub const fn is_retryable(&self) -> bool {
182 matches!(
183 self,
184 ErrorKind::Timeout
185 | ErrorKind::Resource
186 | ErrorKind::Communication
187 | ErrorKind::Mutex
188 | ErrorKind::Device
189 )
190 }
191}
192
193impl std::fmt::Display for ErrorKind {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 write!(f, "{}", self.as_str())
196 }
197}
198
199impl CoreError {
204 pub fn kind(&self) -> ErrorKind {
206 match self {
207 CoreError::ComputationError(_) => ErrorKind::Computation,
208 CoreError::DomainError(_) => ErrorKind::Domain,
209 CoreError::DispatchError(_) => ErrorKind::Dispatch,
210 CoreError::ConvergenceError(_) => ErrorKind::Convergence,
211 CoreError::DimensionError(_) => ErrorKind::Dimension,
212 CoreError::ShapeError(_) => ErrorKind::Shape,
213 CoreError::IndexError(_) => ErrorKind::Index,
214 CoreError::ValueError(_) => ErrorKind::Value,
215 CoreError::TypeError(_) => ErrorKind::Type,
216 CoreError::NotImplementedError(_) => ErrorKind::NotImplemented,
217 CoreError::ImplementationError(_) => ErrorKind::Implementation,
218 CoreError::MemoryError(_) => ErrorKind::Memory,
219 CoreError::AllocationError(_) => ErrorKind::Allocation,
220 CoreError::ConfigError(_) => ErrorKind::Config,
221 CoreError::InvalidArgument(_) => ErrorKind::InvalidArgument,
222 CoreError::InvalidInput(_) => ErrorKind::InvalidInput,
223 CoreError::PermissionError(_) => ErrorKind::Permission,
224 CoreError::ValidationError(_) => ErrorKind::Validation,
225 CoreError::InvalidState(_) => ErrorKind::InvalidState,
226 CoreError::JITError(_) => ErrorKind::Jit,
227 CoreError::JSONError(_) => ErrorKind::Json,
228 CoreError::IoError(_) => ErrorKind::Io,
229 CoreError::SchedulerError(_) => ErrorKind::Scheduler,
230 CoreError::TimeoutError(_) => ErrorKind::Timeout,
231 CoreError::CompressionError(_) => ErrorKind::Compression,
232 CoreError::InvalidShape(_) => ErrorKind::InvalidShape,
233 CoreError::DeviceError(_) => ErrorKind::Device,
234 CoreError::MutexError(_) => ErrorKind::Mutex,
235 CoreError::ThreadError(_) => ErrorKind::Thread,
236 CoreError::StreamError(_) => ErrorKind::Stream,
237 CoreError::EndOfStream(_) => ErrorKind::EndOfStream,
238 CoreError::ResourceError(_) => ErrorKind::Resource,
239 CoreError::CommunicationError(_) => ErrorKind::Communication,
240 CoreError::SecurityError(_) => ErrorKind::Security,
241 }
242 }
243
244 pub fn error_message(&self) -> &str {
246 match self {
247 CoreError::ComputationError(ctx)
248 | CoreError::DomainError(ctx)
249 | CoreError::DispatchError(ctx)
250 | CoreError::ConvergenceError(ctx)
251 | CoreError::DimensionError(ctx)
252 | CoreError::ShapeError(ctx)
253 | CoreError::IndexError(ctx)
254 | CoreError::ValueError(ctx)
255 | CoreError::TypeError(ctx)
256 | CoreError::NotImplementedError(ctx)
257 | CoreError::ImplementationError(ctx)
258 | CoreError::MemoryError(ctx)
259 | CoreError::AllocationError(ctx)
260 | CoreError::ConfigError(ctx)
261 | CoreError::InvalidArgument(ctx)
262 | CoreError::InvalidInput(ctx)
263 | CoreError::PermissionError(ctx)
264 | CoreError::ValidationError(ctx)
265 | CoreError::InvalidState(ctx)
266 | CoreError::JITError(ctx)
267 | CoreError::JSONError(ctx)
268 | CoreError::IoError(ctx)
269 | CoreError::SchedulerError(ctx)
270 | CoreError::TimeoutError(ctx)
271 | CoreError::CompressionError(ctx)
272 | CoreError::InvalidShape(ctx)
273 | CoreError::DeviceError(ctx)
274 | CoreError::MutexError(ctx)
275 | CoreError::ThreadError(ctx)
276 | CoreError::StreamError(ctx)
277 | CoreError::EndOfStream(ctx)
278 | CoreError::ResourceError(ctx)
279 | CoreError::CommunicationError(ctx)
280 | CoreError::SecurityError(ctx) => &ctx.message,
281 }
282 }
283
284 pub fn context(&self) -> &super::error::ErrorContext {
286 match self {
287 CoreError::ComputationError(ctx)
288 | CoreError::DomainError(ctx)
289 | CoreError::DispatchError(ctx)
290 | CoreError::ConvergenceError(ctx)
291 | CoreError::DimensionError(ctx)
292 | CoreError::ShapeError(ctx)
293 | CoreError::IndexError(ctx)
294 | CoreError::ValueError(ctx)
295 | CoreError::TypeError(ctx)
296 | CoreError::NotImplementedError(ctx)
297 | CoreError::ImplementationError(ctx)
298 | CoreError::MemoryError(ctx)
299 | CoreError::AllocationError(ctx)
300 | CoreError::ConfigError(ctx)
301 | CoreError::InvalidArgument(ctx)
302 | CoreError::InvalidInput(ctx)
303 | CoreError::PermissionError(ctx)
304 | CoreError::ValidationError(ctx)
305 | CoreError::InvalidState(ctx)
306 | CoreError::JITError(ctx)
307 | CoreError::JSONError(ctx)
308 | CoreError::IoError(ctx)
309 | CoreError::SchedulerError(ctx)
310 | CoreError::TimeoutError(ctx)
311 | CoreError::CompressionError(ctx)
312 | CoreError::InvalidShape(ctx)
313 | CoreError::DeviceError(ctx)
314 | CoreError::MutexError(ctx)
315 | CoreError::ThreadError(ctx)
316 | CoreError::StreamError(ctx)
317 | CoreError::EndOfStream(ctx)
318 | CoreError::ResourceError(ctx)
319 | CoreError::CommunicationError(ctx)
320 | CoreError::SecurityError(ctx) => ctx,
321 }
322 }
323
324 pub fn cause(&self) -> Option<&CoreError> {
326 self.context().cause.as_deref()
327 }
328}
329
330impl From<std::num::ParseIntError> for CoreError {
335 fn from(err: std::num::ParseIntError) -> Self {
336 CoreError::ValueError(super::error::ErrorContext::new(format!(
337 "Integer parse error: {err}"
338 )))
339 }
340}
341
342impl From<std::num::ParseFloatError> for CoreError {
343 fn from(err: std::num::ParseFloatError) -> Self {
344 CoreError::ValueError(super::error::ErrorContext::new(format!(
345 "Float parse error: {err}"
346 )))
347 }
348}
349
350impl From<std::fmt::Error> for CoreError {
351 fn from(err: std::fmt::Error) -> Self {
352 CoreError::ComputationError(super::error::ErrorContext::new(format!(
353 "Formatting error: {err}"
354 )))
355 }
356}
357
358impl From<std::str::Utf8Error> for CoreError {
359 fn from(err: std::str::Utf8Error) -> Self {
360 CoreError::ValueError(super::error::ErrorContext::new(format!(
361 "UTF-8 decode error: {err}"
362 )))
363 }
364}
365
366impl From<std::string::FromUtf8Error> for CoreError {
367 fn from(err: std::string::FromUtf8Error) -> Self {
368 CoreError::ValueError(super::error::ErrorContext::new(format!(
369 "UTF-8 decode error: {err}"
370 )))
371 }
372}
373
374impl<T> From<std::sync::PoisonError<T>> for CoreError {
375 fn from(err: std::sync::PoisonError<T>) -> Self {
376 CoreError::MutexError(super::error::ErrorContext::new(format!(
377 "Lock poisoned: {err}"
378 )))
379 }
380}
381
382impl From<std::time::SystemTimeError> for CoreError {
383 fn from(err: std::time::SystemTimeError) -> Self {
384 CoreError::ComputationError(super::error::ErrorContext::new(format!(
385 "System time error: {err}"
386 )))
387 }
388}
389
390impl super::error::ErrorContext {
395 pub fn with_backtrace(mut self) -> Self {
400 let bt = std::backtrace::Backtrace::capture();
402 let bt_string = format!("{bt}");
403 if !bt_string.is_empty() && !bt_string.contains("disabled") {
404 self.message = format!("{}\nBacktrace:\n{bt_string}", self.message);
405 }
406 self
407 }
408}
409
410#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::error::ErrorContext;
418
419 #[test]
420 fn test_kind_mapping_computation() {
421 let err = CoreError::ComputationError(ErrorContext::new("test"));
422 assert_eq!(err.kind(), ErrorKind::Computation);
423 }
424
425 #[test]
426 fn test_kind_mapping_dimension() {
427 let err = CoreError::DimensionError(ErrorContext::new("bad shape"));
428 assert_eq!(err.kind(), ErrorKind::Dimension);
429 assert!(err.kind().is_shape_related());
430 }
431
432 #[test]
433 fn test_kind_mapping_memory() {
434 let err = CoreError::MemoryError(ErrorContext::new("oom"));
435 assert_eq!(err.kind(), ErrorKind::Memory);
436 assert!(err.kind().is_resource_related());
437 }
438
439 #[test]
440 fn test_kind_mapping_io() {
441 let err = CoreError::IoError(ErrorContext::new("file not found"));
442 assert_eq!(err.kind(), ErrorKind::Io);
443 assert!(err.kind().is_io_related());
444 }
445
446 #[test]
447 fn test_kind_mapping_mutex() {
448 let err = CoreError::MutexError(ErrorContext::new("poisoned"));
449 assert_eq!(err.kind(), ErrorKind::Mutex);
450 assert!(err.kind().is_concurrency_related());
451 assert!(err.kind().is_retryable());
452 }
453
454 #[test]
455 fn test_kind_as_str() {
456 assert_eq!(ErrorKind::Computation.as_str(), "computation");
457 assert_eq!(ErrorKind::Io.as_str(), "io");
458 assert_eq!(ErrorKind::Security.as_str(), "security");
459 }
460
461 #[test]
462 fn test_kind_display() {
463 let kind = ErrorKind::Domain;
464 assert_eq!(format!("{kind}"), "domain");
465 }
466
467 #[test]
468 fn test_error_message_extraction() {
469 let err = CoreError::ValueError(ErrorContext::new("bad value 42"));
470 assert_eq!(err.error_message(), "bad value 42");
471 }
472
473 #[test]
474 fn test_error_cause_chain() {
475 let inner = CoreError::IoError(ErrorContext::new("disk full"));
476 let outer =
477 CoreError::ComputationError(ErrorContext::new("write failed").with_cause(inner));
478 let cause = outer.cause();
479 assert!(cause.is_some());
480 assert_eq!(cause.map(|e| e.kind()), Some(ErrorKind::Io));
481 }
482
483 #[test]
484 fn test_from_parse_int_error() {
485 let result: Result<i32, _> = "not_a_number".parse();
486 let err: CoreError = result.expect_err("should fail").into();
487 assert_eq!(err.kind(), ErrorKind::Value);
488 }
489
490 #[test]
491 fn test_from_parse_float_error() {
492 let result: Result<f64, _> = "not_a_float".parse();
493 let err: CoreError = result.expect_err("should fail").into();
494 assert_eq!(err.kind(), ErrorKind::Value);
495 }
496
497 #[test]
498 fn test_from_utf8_error() {
499 let bytes = vec![0xFF, 0xFE];
500 let result = String::from_utf8(bytes);
501 let err: CoreError = result.expect_err("should fail").into();
502 assert_eq!(err.kind(), ErrorKind::Value);
503 }
504
505 #[test]
506 fn test_is_programming_error() {
507 assert!(ErrorKind::InvalidArgument.is_programming_error());
508 assert!(ErrorKind::NotImplemented.is_programming_error());
509 assert!(!ErrorKind::Io.is_programming_error());
510 }
511
512 #[test]
513 fn test_all_error_kinds_have_unique_as_str() {
514 use std::collections::HashSet;
515 let kinds = [
516 ErrorKind::Computation,
517 ErrorKind::Domain,
518 ErrorKind::Dispatch,
519 ErrorKind::Convergence,
520 ErrorKind::Dimension,
521 ErrorKind::Shape,
522 ErrorKind::Index,
523 ErrorKind::Value,
524 ErrorKind::Type,
525 ErrorKind::NotImplemented,
526 ErrorKind::Implementation,
527 ErrorKind::Memory,
528 ErrorKind::Allocation,
529 ErrorKind::Config,
530 ErrorKind::InvalidArgument,
531 ErrorKind::InvalidInput,
532 ErrorKind::Permission,
533 ErrorKind::Validation,
534 ErrorKind::InvalidState,
535 ErrorKind::Jit,
536 ErrorKind::Json,
537 ErrorKind::Io,
538 ErrorKind::Scheduler,
539 ErrorKind::Timeout,
540 ErrorKind::Compression,
541 ErrorKind::InvalidShape,
542 ErrorKind::Device,
543 ErrorKind::Mutex,
544 ErrorKind::Thread,
545 ErrorKind::Stream,
546 ErrorKind::EndOfStream,
547 ErrorKind::Resource,
548 ErrorKind::Communication,
549 ErrorKind::Security,
550 ];
551 let names: HashSet<&str> = kinds.iter().map(|k| k.as_str()).collect();
552 assert_eq!(
553 names.len(),
554 kinds.len(),
555 "Each ErrorKind must have a unique as_str()"
556 );
557 }
558
559 #[test]
560 fn test_context_accessor() {
561 let err = CoreError::TimeoutError(
562 ErrorContext::new("timed out")
563 .with_location(super::super::error::ErrorLocation::new("test.rs", 42)),
564 );
565 let ctx = err.context();
566 assert_eq!(ctx.message, "timed out");
567 assert!(ctx.location.is_some());
568 }
569
570 #[test]
571 fn test_with_backtrace_does_not_panic() {
572 let ctx = ErrorContext::new("test").with_backtrace();
573 assert!(ctx.message.contains("test"));
575 }
576}