1use std::error::Error as StdError;
8use std::fmt;
9
10#[derive(Debug, Clone)]
16pub struct Error {
17 pub category: ErrorCategory,
19 pub code: ErrorCode,
21 pub message: String,
23 pub cause: Option<Box<Error>>,
25 pub location: Option<ErrorLocation>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ErrorCategory {
32 Core,
34 Dsp,
36 Graph,
38 Io,
40 Control,
42 Config,
44 Runtime,
46 Internal,
48}
49
50impl ErrorCategory {
51 pub fn as_str(&self) -> &'static str {
53 match self {
54 ErrorCategory::Core => "core",
55 ErrorCategory::Dsp => "dsp",
56 ErrorCategory::Graph => "graph",
57 ErrorCategory::Io => "io",
58 ErrorCategory::Control => "control",
59 ErrorCategory::Config => "config",
60 ErrorCategory::Runtime => "runtime",
61 ErrorCategory::Internal => "internal",
62 }
63 }
64}
65
66impl fmt::Display for ErrorCategory {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "{}", self.as_str())
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum ErrorCode {
75 Unknown = 0,
80 InvalidParameter = 1,
82 InvalidState = 2,
84 Unsupported = 3,
86 NotImplemented = 4,
88 Timeout = 5,
90
91 BufferFull = 100,
96 BufferEmpty = 101,
98 InvalidBufferSize = 102,
100 BufferMisaligned = 103,
102 BufferNotInitialized = 104,
104
105 QueueFull = 120,
110 QueueEmpty = 121,
112 QueueClosed = 122,
114 InvalidQueueIndex = 123,
116
117 NodeNotFound = 200,
122 PortNotFound = 201,
124 InvalidConnection = 202,
126 CycleDetected = 203,
128 NodeAlreadyExists = 204,
130 PortAlreadyConnected = 205,
132
133 DeviceNotFound = 300,
138 DeviceBusy = 301,
140 AlsaError = 310,
142 JackError = 311,
144 PipeWireError = 312,
146 XRun = 320,
148
149 MidiError = 400,
154 OscError = 401,
156 MappingNotFound = 402,
158 AutomatonNotFound = 403,
160 InvalidParameterValue = 404,
162
163 ConfigNotFound = 500,
168 InvalidConfigFormat = 501,
170 MissingField = 502,
172
173 RealtimeViolation = 600,
178 PriorityError = 601,
180 AlreadyRunning = 602,
182 NotRunning = 603,
184}
185
186impl ErrorCode {
187 pub fn category(&self) -> ErrorCategory {
189 match *self {
190 ErrorCode::Unknown
191 | ErrorCode::InvalidParameter
192 | ErrorCode::InvalidState
193 | ErrorCode::Unsupported
194 | ErrorCode::NotImplemented
195 | ErrorCode::Timeout
196 | ErrorCode::BufferFull
197 | ErrorCode::BufferEmpty
198 | ErrorCode::InvalidBufferSize
199 | ErrorCode::BufferMisaligned
200 | ErrorCode::BufferNotInitialized
201 | ErrorCode::QueueFull
202 | ErrorCode::QueueEmpty
203 | ErrorCode::QueueClosed
204 | ErrorCode::InvalidQueueIndex => ErrorCategory::Core,
205
206 ErrorCode::NodeNotFound
207 | ErrorCode::PortNotFound
208 | ErrorCode::InvalidConnection
209 | ErrorCode::CycleDetected
210 | ErrorCode::NodeAlreadyExists
211 | ErrorCode::PortAlreadyConnected => ErrorCategory::Graph,
212
213 ErrorCode::DeviceNotFound
214 | ErrorCode::DeviceBusy
215 | ErrorCode::AlsaError
216 | ErrorCode::JackError
217 | ErrorCode::PipeWireError
218 | ErrorCode::XRun => ErrorCategory::Io,
219
220 ErrorCode::MidiError
221 | ErrorCode::OscError
222 | ErrorCode::MappingNotFound
223 | ErrorCode::AutomatonNotFound
224 | ErrorCode::InvalidParameterValue => ErrorCategory::Control,
225
226 ErrorCode::ConfigNotFound
227 | ErrorCode::InvalidConfigFormat
228 | ErrorCode::MissingField => ErrorCategory::Config,
229
230 ErrorCode::RealtimeViolation
231 | ErrorCode::PriorityError
232 | ErrorCode::AlreadyRunning
233 | ErrorCode::NotRunning => ErrorCategory::Runtime,
234 }
235 }
236
237 pub fn description(&self) -> &'static str {
239 match self {
240 ErrorCode::Unknown => "Unknown error",
241 ErrorCode::InvalidParameter => "Invalid parameter",
242 ErrorCode::InvalidState => "Invalid state",
243 ErrorCode::Unsupported => "Unsupported operation",
244 ErrorCode::NotImplemented => "Not implemented",
245 ErrorCode::Timeout => "Operation timed out",
246
247 ErrorCode::BufferFull => "Buffer is full",
248 ErrorCode::BufferEmpty => "Buffer is empty",
249 ErrorCode::InvalidBufferSize => "Invalid buffer size",
250 ErrorCode::BufferMisaligned => "Buffer is misaligned for SIMD operations",
251 ErrorCode::BufferNotInitialized => "Buffer not initialized",
252
253 ErrorCode::QueueFull => "Queue is full",
254 ErrorCode::QueueEmpty => "Queue is empty",
255 ErrorCode::QueueClosed => "Queue is closed",
256 ErrorCode::InvalidQueueIndex => "Invalid queue index",
257
258 ErrorCode::NodeNotFound => "Node not found",
259 ErrorCode::PortNotFound => "Port not found",
260 ErrorCode::InvalidConnection => "Invalid connection",
261 ErrorCode::CycleDetected => "Cycle detected in graph",
262 ErrorCode::NodeAlreadyExists => "Node already exists",
263 ErrorCode::PortAlreadyConnected => "Port already connected",
264
265 ErrorCode::DeviceNotFound => "Device not found",
266 ErrorCode::DeviceBusy => "Device is busy",
267 ErrorCode::AlsaError => "ALSA error",
268 ErrorCode::JackError => "JACK error",
269 ErrorCode::PipeWireError => "PipeWire error",
270 ErrorCode::XRun => "Buffer underrun/overrun detected",
271
272 ErrorCode::MidiError => "MIDI error",
273 ErrorCode::OscError => "OSC error",
274 ErrorCode::MappingNotFound => "Mapping not found",
275 ErrorCode::AutomatonNotFound => "Automaton not found",
276 ErrorCode::InvalidParameterValue => "Invalid parameter value",
277
278 ErrorCode::ConfigNotFound => "Configuration not found",
279 ErrorCode::InvalidConfigFormat => "Invalid configuration format",
280 ErrorCode::MissingField => "Missing required field",
281
282 ErrorCode::RealtimeViolation => "Real-time violation detected",
283 ErrorCode::PriorityError => "Failed to set thread priority",
284 ErrorCode::AlreadyRunning => "Already running",
285 ErrorCode::NotRunning => "Not running",
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct ErrorLocation {
293 pub file: &'static str,
295 pub line: u32,
297 pub column: u32,
299}
300
301impl fmt::Display for ErrorLocation {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 write!(f, "{}:{}:{}", self.file, self.line, self.column)
304 }
305}
306
307impl Error {
312 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
314 Self {
315 category: code.category(),
316 code,
317 message: message.into(),
318 cause: None,
319 location: None,
320 }
321 }
322
323 pub fn with_cause(mut self, cause: Error) -> Self {
325 self.cause = Some(Box::new(cause));
326 self
327 }
328
329 pub fn at(mut self, file: &'static str, line: u32, column: u32) -> Self {
331 self.location = Some(ErrorLocation { file, line, column });
332 self
333 }
334
335 pub fn root_cause(&self) -> &Error {
337 let mut current = self;
338 while let Some(cause) = ¤t.cause {
339 current = cause;
340 }
341 current
342 }
343
344 pub fn is_realtime_critical(&self) -> bool {
346 matches!(
347 self.code,
348 ErrorCode::RealtimeViolation
349 | ErrorCode::PriorityError
350 | ErrorCode::BufferFull
351 | ErrorCode::XRun
352 )
353 }
354
355 pub fn is_recoverable(&self) -> bool {
357 !self.is_realtime_critical()
358 }
359}
360
361impl fmt::Display for Error {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 if let Some(loc) = &self.location {
364 write!(
365 f,
366 "[{}] at {}: {} ({})",
367 self.category,
368 loc,
369 self.message,
370 self.code.description()
371 )?;
372 } else {
373 write!(
374 f,
375 "[{}]: {} ({})",
376 self.category,
377 self.message,
378 self.code.description()
379 )?;
380 }
381
382 if let Some(cause) = &self.cause {
383 write!(f, "\n caused by: {}", cause)?;
384 }
385
386 Ok(())
387 }
388}
389
390impl StdError for Error {
391 fn source(&self) -> Option<&(dyn StdError + 'static)> {
392 self.cause.as_ref().map(|c| c as &dyn StdError)
393 }
394}
395
396pub type Result<T> = std::result::Result<T, Error>;
402
403impl From<std::io::Error> for Error {
408 fn from(err: std::io::Error) -> Self {
409 Error::new(ErrorCode::Unknown, err.to_string())
410 }
411}
412
413impl From<std::num::ParseIntError> for Error {
414 fn from(err: std::num::ParseIntError) -> Self {
415 Error::new(ErrorCode::InvalidParameter, err.to_string())
416 }
417}
418
419impl From<std::num::ParseFloatError> for Error {
420 fn from(err: std::num::ParseFloatError) -> Self {
421 Error::new(ErrorCode::InvalidParameter, err.to_string())
422 }
423}
424
425impl From<std::str::Utf8Error> for Error {
426 fn from(err: std::str::Utf8Error) -> Self {
427 Error::new(ErrorCode::InvalidParameter, err.to_string())
428 }
429}
430
431#[macro_export]
437macro_rules! error {
438 ($code:expr, $msg:expr) => {
439 $crate::error::Error::new($code, $msg)
440 };
441 ($code:expr, $fmt:expr, $($arg:tt)*) => {
442 $crate::error::Error::new($code, format!($fmt, $($arg)*))
443 };
444}
445
446#[macro_export]
448macro_rules! error_at {
449 ($code:expr, $msg:expr) => {
450 $crate::error::Error::new($code, $msg).at(file!(), line!(), column!())
451 };
452 ($code:expr, $fmt:expr, $($arg:tt)*) => {
453 $crate::error::Error::new($code, format!($fmt, $($arg)*))
454 .at(file!(), line!(), column!())
455 };
456}
457
458#[macro_export]
460macro_rules! bail {
461 ($code:expr, $msg:expr) => {
462 return Err($crate::error::Error::new($code, $msg))
463 };
464 ($code:expr, $fmt:expr, $($arg:tt)*) => {
465 return Err($crate::error::Error::new($code, format!($fmt, $($arg)*)))
466 };
467}
468
469#[macro_export]
471macro_rules! context {
472 ($expr:expr, $code:expr, $msg:expr) => {
473 $expr.map_err(|e| $crate::error::Error::new($code, $msg).with_cause(e))
474 };
475 ($expr:expr, $code:expr, $fmt:expr, $($arg:tt)*) => {
476 $expr.map_err(|e| $crate::error::Error::new($code, format!($fmt, $($arg)*)).with_cause(e))
477 };
478}
479
480pub mod buffer {
486 use super::*;
487 #[allow(dead_code)]
488 pub fn full() -> Error {
489 Error::new(ErrorCode::BufferFull, "Buffer is full")
490 }
491 #[allow(dead_code)]
492 pub fn empty() -> Error {
493 Error::new(ErrorCode::BufferEmpty, "Buffer is empty")
494 }
495 #[allow(dead_code)]
496 pub fn invalid_size(expected: usize, got: usize) -> Error {
497 error!(
498 ErrorCode::InvalidBufferSize,
499 "Invalid buffer size: expected {}, got {}", expected, got
500 )
501 }
502 #[allow(dead_code)]
503 pub fn misaligned(required: usize, actual: usize) -> Error {
504 error!(
505 ErrorCode::BufferMisaligned,
506 "Buffer misaligned: required {} byte alignment, actual {}", required, actual
507 )
508 }
509 #[allow(dead_code)]
510 pub fn not_initialized() -> Error {
511 Error::new(ErrorCode::BufferNotInitialized, "Buffer not initialized")
512 }
513}
514
515pub mod queue {
517 use super::*;
518
519 pub fn full() -> Error {
520 Error::new(ErrorCode::QueueFull, "Queue is full")
521 }
522
523 pub fn empty() -> Error {
524 Error::new(ErrorCode::QueueEmpty, "Queue is empty")
525 }
526
527 pub fn closed() -> Error {
528 Error::new(ErrorCode::QueueClosed, "Queue is closed")
529 }
530
531 pub fn invalid_index(idx: usize, max: usize) -> Error {
532 error!(
533 ErrorCode::InvalidQueueIndex,
534 "Invalid queue index: {} (max {})", idx, max
535 )
536 }
537}
538
539pub mod graph {
541 use super::*;
542 use crate::traits::NodeId;
543 use crate::traits::PortId;
544
545 pub fn node_not_found(id: NodeId) -> Error {
546 error!(ErrorCode::NodeNotFound, "Node not found: {}", id)
547 }
548
549 pub fn port_not_found(id: PortId) -> Error {
550 error!(ErrorCode::PortNotFound, "Port not found: {}", id)
551 }
552
553 pub fn invalid_connection(from: PortId, to: PortId) -> Error {
554 error!(
555 ErrorCode::InvalidConnection,
556 "Invalid connection: {} -> {}", from, to
557 )
558 }
559
560 pub fn cycle_detected() -> Error {
561 Error::new(ErrorCode::CycleDetected, "Cycle detected in graph")
562 }
563
564 pub fn node_already_exists(id: NodeId) -> Error {
565 error!(ErrorCode::NodeAlreadyExists, "Node already exists: {}", id)
566 }
567
568 pub fn port_already_connected(port: PortId) -> Error {
569 error!(
570 ErrorCode::PortAlreadyConnected,
571 "Port already connected: {}", port
572 )
573 }
574}
575
576pub mod io {
578 use super::*;
579
580 pub fn device_not_found(name: &str) -> Error {
581 error!(ErrorCode::DeviceNotFound, "Device not found: {}", name)
582 }
583
584 pub fn device_busy(name: &str) -> Error {
585 error!(ErrorCode::DeviceBusy, "Device is busy: {}", name)
586 }
587
588 pub fn alsa_error(desc: &str) -> Error {
589 error!(ErrorCode::AlsaError, "ALSA error: {}", desc)
590 }
591
592 pub fn jack_error(desc: &str) -> Error {
593 error!(ErrorCode::JackError, "JACK error: {}", desc)
594 }
595
596 pub fn pipewire_error(desc: &str) -> Error {
597 error!(ErrorCode::PipeWireError, "PipeWire error: {}", desc)
598 }
599
600 pub fn xrun() -> Error {
601 Error::new(ErrorCode::XRun, "Buffer underrun/overrun detected")
602 }
603}
604
605pub mod control {
607 use super::*;
608
609 pub fn midi_error(desc: &str) -> Error {
610 error!(ErrorCode::MidiError, "MIDI error: {}", desc)
611 }
612
613 pub fn osc_error(desc: &str) -> Error {
614 error!(ErrorCode::OscError, "OSC error: {}", desc)
615 }
616
617 pub fn mapping_not_found(id: &str) -> Error {
618 error!(ErrorCode::MappingNotFound, "Mapping not found: {}", id)
619 }
620
621 pub fn automaton_not_found(id: &str) -> Error {
622 error!(ErrorCode::AutomatonNotFound, "Automaton not found: {}", id)
623 }
624
625 pub fn invalid_parameter_value(param: &str, value: f64, min: f64, max: f64) -> Error {
626 error!(
627 ErrorCode::InvalidParameterValue,
628 "Invalid value for parameter {}: {} (allowed range: {} - {})", param, value, min, max
629 )
630 }
631}
632
633pub mod config {
635 use super::*;
636
637 pub fn not_found(path: &str) -> Error {
638 error!(
639 ErrorCode::ConfigNotFound,
640 "Configuration not found: {}", path
641 )
642 }
643
644 pub fn invalid_format(details: &str) -> Error {
645 error!(
646 ErrorCode::InvalidConfigFormat,
647 "Invalid configuration format: {}", details
648 )
649 }
650
651 pub fn missing_field(field: &str) -> Error {
652 error!(ErrorCode::MissingField, "Missing required field: {}", field)
653 }
654}
655
656pub mod runtime {
658 use super::*;
659
660 pub fn realtime_violation(details: &str) -> Error {
661 error!(
662 ErrorCode::RealtimeViolation,
663 "Real-time violation: {}", details
664 )
665 }
666
667 pub fn priority_error(details: &str) -> Error {
668 error!(
669 ErrorCode::PriorityError,
670 "Failed to set thread priority: {}", details
671 )
672 }
673
674 pub fn already_running() -> Error {
675 Error::new(ErrorCode::AlreadyRunning, "Already running")
676 }
677
678 pub fn not_running() -> Error {
679 Error::new(ErrorCode::NotRunning, "Not running")
680 }
681}
682
683#[cfg(test)]
688mod tests {
689 use super::*;
690 use crate::prelude::NodeId;
691
692 #[test]
693 fn test_error_creation() {
694 let err = Error::new(ErrorCode::BufferFull, "Test error");
695 assert_eq!(err.code, ErrorCode::BufferFull);
696 assert_eq!(err.message, "Test error");
697 assert_eq!(err.category, ErrorCategory::Core);
698 }
699
700 #[test]
701 fn test_error_with_cause() {
702 let cause = Error::new(ErrorCode::BufferEmpty, "Cause");
703 let err = Error::new(ErrorCode::BufferFull, "Main error").with_cause(cause);
704
705 assert!(err.cause.is_some());
706 assert_eq!(err.root_cause().code, ErrorCode::BufferEmpty);
707 }
708
709 #[test]
710 fn test_error_macros() {
711 let err = error!(ErrorCode::BufferFull, "Buffer is full");
712 assert_eq!(err.code, ErrorCode::BufferFull);
713
714 let err = error!(ErrorCode::BufferFull, "Buffer {} is full", "test");
715 assert_eq!(err.message, "Buffer test is full");
716 }
717
718 #[test]
719 fn test_specialized_errors() {
720 let err = buffer::full();
721 assert_eq!(err.code, ErrorCode::BufferFull);
722
723 let err = graph::node_not_found(NodeId(42));
724 assert_eq!(err.code, ErrorCode::NodeNotFound);
725 assert!(err.message.contains("42"));
726
727 let err = io::device_not_found("hw:0");
728 assert_eq!(err.code, ErrorCode::DeviceNotFound);
729 assert!(err.message.contains("hw:0"));
730 }
731
732 #[test]
733 fn test_error_category() {
734 assert_eq!(ErrorCode::BufferFull.category(), ErrorCategory::Core);
735 assert_eq!(ErrorCode::NodeNotFound.category(), ErrorCategory::Graph);
736 assert_eq!(ErrorCode::AlsaError.category(), ErrorCategory::Io);
737 assert_eq!(ErrorCode::MidiError.category(), ErrorCategory::Control);
738 assert_eq!(ErrorCode::ConfigNotFound.category(), ErrorCategory::Config);
739 assert_eq!(
740 ErrorCode::RealtimeViolation.category(),
741 ErrorCategory::Runtime
742 );
743 }
744
745 #[test]
746 fn test_realtime_critical() {
747 assert!(buffer::full().is_realtime_critical());
748 assert!(io::xrun().is_realtime_critical());
749 assert!(runtime::realtime_violation("test").is_realtime_critical());
750
751 assert!(!graph::node_not_found(NodeId(1)).is_realtime_critical());
752 assert!(!config::not_found("test").is_realtime_critical());
753 }
754}