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,
78 InvalidParameter = 1,
80 InvalidState = 2,
82 Unsupported = 3,
84 NotImplemented = 4,
86 Timeout = 5,
88
89 BufferFull = 100,
92 BufferEmpty = 101,
94 InvalidBufferSize = 102,
96 BufferMisaligned = 103,
98 BufferNotInitialized = 104,
100
101 QueueFull = 120,
104 QueueEmpty = 121,
106 QueueClosed = 122,
108 InvalidQueueIndex = 123,
110
111 NodeNotFound = 200,
114 PortNotFound = 201,
116 InvalidConnection = 202,
118 CycleDetected = 203,
120 NodeAlreadyExists = 204,
122 PortAlreadyConnected = 205,
124
125 DeviceNotFound = 300,
128 DeviceBusy = 301,
130 AlsaError = 310,
132 JackError = 311,
134 PipeWireError = 312,
136 XRun = 320,
138
139 MidiError = 400,
142 OscError = 401,
144 MappingNotFound = 402,
146 AutomatonNotFound = 403,
148 InvalidParameterValue = 404,
150
151 ConfigNotFound = 500,
154 InvalidConfigFormat = 501,
156 MissingField = 502,
158
159 RealtimeViolation = 600,
162 PriorityError = 601,
164 AlreadyRunning = 602,
166 NotRunning = 603,
168}
169
170impl ErrorCode {
171 pub fn category(&self) -> ErrorCategory {
173 match *self {
174 ErrorCode::Unknown
175 | ErrorCode::InvalidParameter
176 | ErrorCode::InvalidState
177 | ErrorCode::Unsupported
178 | ErrorCode::NotImplemented
179 | ErrorCode::Timeout
180 | ErrorCode::BufferFull
181 | ErrorCode::BufferEmpty
182 | ErrorCode::InvalidBufferSize
183 | ErrorCode::BufferMisaligned
184 | ErrorCode::BufferNotInitialized
185 | ErrorCode::QueueFull
186 | ErrorCode::QueueEmpty
187 | ErrorCode::QueueClosed
188 | ErrorCode::InvalidQueueIndex => ErrorCategory::Core,
189
190 ErrorCode::NodeNotFound
191 | ErrorCode::PortNotFound
192 | ErrorCode::InvalidConnection
193 | ErrorCode::CycleDetected
194 | ErrorCode::NodeAlreadyExists
195 | ErrorCode::PortAlreadyConnected => ErrorCategory::Graph,
196
197 ErrorCode::DeviceNotFound
198 | ErrorCode::DeviceBusy
199 | ErrorCode::AlsaError
200 | ErrorCode::JackError
201 | ErrorCode::PipeWireError
202 | ErrorCode::XRun => ErrorCategory::Io,
203
204 ErrorCode::MidiError
205 | ErrorCode::OscError
206 | ErrorCode::MappingNotFound
207 | ErrorCode::AutomatonNotFound
208 | ErrorCode::InvalidParameterValue => ErrorCategory::Control,
209
210 ErrorCode::ConfigNotFound
211 | ErrorCode::InvalidConfigFormat
212 | ErrorCode::MissingField => ErrorCategory::Config,
213
214 ErrorCode::RealtimeViolation
215 | ErrorCode::PriorityError
216 | ErrorCode::AlreadyRunning
217 | ErrorCode::NotRunning => ErrorCategory::Runtime,
218 }
219 }
220
221 pub fn description(&self) -> &'static str {
223 match self {
224 ErrorCode::Unknown => "Unknown error",
225 ErrorCode::InvalidParameter => "Invalid parameter",
226 ErrorCode::InvalidState => "Invalid state",
227 ErrorCode::Unsupported => "Unsupported operation",
228 ErrorCode::NotImplemented => "Not implemented",
229 ErrorCode::Timeout => "Operation timed out",
230
231 ErrorCode::BufferFull => "Buffer is full",
232 ErrorCode::BufferEmpty => "Buffer is empty",
233 ErrorCode::InvalidBufferSize => "Invalid buffer size",
234 ErrorCode::BufferMisaligned => "Buffer is misaligned for SIMD operations",
235 ErrorCode::BufferNotInitialized => "Buffer not initialized",
236
237 ErrorCode::QueueFull => "Queue is full",
238 ErrorCode::QueueEmpty => "Queue is empty",
239 ErrorCode::QueueClosed => "Queue is closed",
240 ErrorCode::InvalidQueueIndex => "Invalid queue index",
241
242 ErrorCode::NodeNotFound => "Node not found",
243 ErrorCode::PortNotFound => "Port not found",
244 ErrorCode::InvalidConnection => "Invalid connection",
245 ErrorCode::CycleDetected => "Cycle detected in graph",
246 ErrorCode::NodeAlreadyExists => "Node already exists",
247 ErrorCode::PortAlreadyConnected => "Port already connected",
248
249 ErrorCode::DeviceNotFound => "Device not found",
250 ErrorCode::DeviceBusy => "Device is busy",
251 ErrorCode::AlsaError => "ALSA error",
252 ErrorCode::JackError => "JACK error",
253 ErrorCode::PipeWireError => "PipeWire error",
254 ErrorCode::XRun => "Buffer underrun/overrun detected",
255
256 ErrorCode::MidiError => "MIDI error",
257 ErrorCode::OscError => "OSC error",
258 ErrorCode::MappingNotFound => "Mapping not found",
259 ErrorCode::AutomatonNotFound => "Automaton not found",
260 ErrorCode::InvalidParameterValue => "Invalid parameter value",
261
262 ErrorCode::ConfigNotFound => "Configuration not found",
263 ErrorCode::InvalidConfigFormat => "Invalid configuration format",
264 ErrorCode::MissingField => "Missing required field",
265
266 ErrorCode::RealtimeViolation => "Real-time violation detected",
267 ErrorCode::PriorityError => "Failed to set thread priority",
268 ErrorCode::AlreadyRunning => "Already running",
269 ErrorCode::NotRunning => "Not running",
270 }
271 }
272}
273
274#[derive(Debug, Clone)]
276pub struct ErrorLocation {
277 pub file: &'static str,
279 pub line: u32,
281 pub column: u32,
283}
284
285impl fmt::Display for ErrorLocation {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 write!(f, "{}:{}:{}", self.file, self.line, self.column)
288 }
289}
290
291impl Error {
296 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
298 Self {
299 category: code.category(),
300 code,
301 message: message.into(),
302 cause: None,
303 location: None,
304 }
305 }
306
307 pub fn with_cause(mut self, cause: Error) -> Self {
309 self.cause = Some(Box::new(cause));
310 self
311 }
312
313 pub fn at(mut self, file: &'static str, line: u32, column: u32) -> Self {
315 self.location = Some(ErrorLocation { file, line, column });
316 self
317 }
318
319 pub fn root_cause(&self) -> &Error {
321 let mut current = self;
322 while let Some(cause) = ¤t.cause {
323 current = cause;
324 }
325 current
326 }
327
328 pub fn is_realtime_critical(&self) -> bool {
330 matches!(
331 self.code,
332 ErrorCode::RealtimeViolation
333 | ErrorCode::PriorityError
334 | ErrorCode::BufferFull
335 | ErrorCode::XRun
336 )
337 }
338
339 pub fn is_recoverable(&self) -> bool {
341 !self.is_realtime_critical()
342 }
343}
344
345impl fmt::Display for Error {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 if let Some(loc) = &self.location {
348 write!(
349 f,
350 "[{}] at {}: {} ({})",
351 self.category,
352 loc,
353 self.message,
354 self.code.description()
355 )?;
356 } else {
357 write!(
358 f,
359 "[{}]: {} ({})",
360 self.category,
361 self.message,
362 self.code.description()
363 )?;
364 }
365
366 if let Some(cause) = &self.cause {
367 write!(f, "\n caused by: {}", cause)?;
368 }
369
370 Ok(())
371 }
372}
373
374impl StdError for Error {
375 fn source(&self) -> Option<&(dyn StdError + 'static)> {
376 self.cause.as_ref().map(|c| c as &dyn StdError)
377 }
378}
379
380pub type Result<T> = std::result::Result<T, Error>;
386
387impl From<std::io::Error> for Error {
392 fn from(err: std::io::Error) -> Self {
393 Error::new(ErrorCode::Unknown, err.to_string())
394 }
395}
396
397impl From<std::num::ParseIntError> for Error {
398 fn from(err: std::num::ParseIntError) -> Self {
399 Error::new(ErrorCode::InvalidParameter, err.to_string())
400 }
401}
402
403impl From<std::num::ParseFloatError> for Error {
404 fn from(err: std::num::ParseFloatError) -> Self {
405 Error::new(ErrorCode::InvalidParameter, err.to_string())
406 }
407}
408
409impl From<std::str::Utf8Error> for Error {
410 fn from(err: std::str::Utf8Error) -> Self {
411 Error::new(ErrorCode::InvalidParameter, err.to_string())
412 }
413}
414
415#[macro_export]
421macro_rules! error {
422 ($code:expr, $msg:expr) => {
423 $crate::error::Error::new($code, $msg)
424 };
425 ($code:expr, $fmt:expr, $($arg:tt)*) => {
426 $crate::error::Error::new($code, format!($fmt, $($arg)*))
427 };
428}
429
430#[macro_export]
432macro_rules! error_at {
433 ($code:expr, $msg:expr) => {
434 $crate::error::Error::new($code, $msg).at(file!(), line!(), column!())
435 };
436 ($code:expr, $fmt:expr, $($arg:tt)*) => {
437 $crate::error::Error::new($code, format!($fmt, $($arg)*))
438 .at(file!(), line!(), column!())
439 };
440}
441
442#[macro_export]
444macro_rules! bail {
445 ($code:expr, $msg:expr) => {
446 return Err($crate::error::Error::new($code, $msg))
447 };
448 ($code:expr, $fmt:expr, $($arg:tt)*) => {
449 return Err($crate::error::Error::new($code, format!($fmt, $($arg)*)))
450 };
451}
452
453#[macro_export]
455macro_rules! context {
456 ($expr:expr, $code:expr, $msg:expr) => {
457 $expr.map_err(|e| $crate::error::Error::new($code, $msg).with_cause(e))
458 };
459 ($expr:expr, $code:expr, $fmt:expr, $($arg:tt)*) => {
460 $expr.map_err(|e| $crate::error::Error::new($code, format!($fmt, $($arg)*)).with_cause(e))
461 };
462}
463
464pub mod io {
470 use super::*;
471
472 pub fn device_not_found(name: &str) -> Error {
474 error!(ErrorCode::DeviceNotFound, "Device not found: {}", name)
475 }
476
477 pub fn device_busy(name: &str) -> Error {
479 error!(ErrorCode::DeviceBusy, "Device is busy: {}", name)
480 }
481
482 pub fn alsa_error(desc: &str) -> Error {
484 error!(ErrorCode::AlsaError, "ALSA error: {}", desc)
485 }
486
487 pub fn jack_error(desc: &str) -> Error {
489 error!(ErrorCode::JackError, "JACK error: {}", desc)
490 }
491
492 pub fn pipewire_error(desc: &str) -> Error {
494 error!(ErrorCode::PipeWireError, "PipeWire error: {}", desc)
495 }
496
497 pub fn xrun() -> Error {
499 Error::new(ErrorCode::XRun, "Buffer underrun/overrun detected")
500 }
501}
502
503pub mod control {
505 use super::*;
506
507 pub fn midi_error(desc: &str) -> Error {
509 error!(ErrorCode::MidiError, "MIDI error: {}", desc)
510 }
511
512 pub fn osc_error(desc: &str) -> Error {
514 error!(ErrorCode::OscError, "OSC error: {}", desc)
515 }
516
517 pub fn mapping_not_found(id: &str) -> Error {
519 error!(ErrorCode::MappingNotFound, "Mapping not found: {}", id)
520 }
521
522 pub fn automaton_not_found(id: &str) -> Error {
524 error!(ErrorCode::AutomatonNotFound, "Automaton not found: {}", id)
525 }
526
527 pub fn invalid_parameter_value(param: &str, value: f64, min: f64, max: f64) -> Error {
529 error!(
530 ErrorCode::InvalidParameterValue,
531 "Invalid value for parameter {}: {} (allowed range: {} - {})", param, value, min, max
532 )
533 }
534}
535
536pub mod config {
538 use super::*;
539
540 pub fn not_found(path: &str) -> Error {
542 error!(
543 ErrorCode::ConfigNotFound,
544 "Configuration not found: {}", path
545 )
546 }
547
548 pub fn invalid_format(details: &str) -> Error {
550 error!(
551 ErrorCode::InvalidConfigFormat,
552 "Invalid configuration format: {}", details
553 )
554 }
555
556 pub fn missing_field(field: &str) -> Error {
558 error!(ErrorCode::MissingField, "Missing required field: {}", field)
559 }
560}
561
562pub mod runtime {
564 use super::*;
565
566 pub fn realtime_violation(details: &str) -> Error {
568 error!(
569 ErrorCode::RealtimeViolation,
570 "Real-time violation: {}", details
571 )
572 }
573
574 pub fn priority_error(details: &str) -> Error {
576 error!(
577 ErrorCode::PriorityError,
578 "Failed to set thread priority: {}", details
579 )
580 }
581
582 pub fn already_running() -> Error {
584 Error::new(ErrorCode::AlreadyRunning, "Already running")
585 }
586
587 pub fn not_running() -> Error {
589 Error::new(ErrorCode::NotRunning, "Not running")
590 }
591}
592
593#[cfg(test)]
598mod tests {
599 use super::*;
600
601 #[test]
602 fn test_error_creation() {
603 let err = Error::new(ErrorCode::BufferFull, "Test error");
604 assert_eq!(err.code, ErrorCode::BufferFull);
605 assert_eq!(err.message, "Test error");
606 assert_eq!(err.category, ErrorCategory::Core);
607 }
608
609 #[test]
610 fn test_error_with_cause() {
611 let cause = Error::new(ErrorCode::BufferEmpty, "Cause");
612 let err = Error::new(ErrorCode::BufferFull, "Main error").with_cause(cause);
613
614 assert!(err.cause.is_some());
615 assert_eq!(err.root_cause().code, ErrorCode::BufferEmpty);
616 }
617
618 #[test]
619 fn test_error_macros() {
620 let err = error!(ErrorCode::BufferFull, "Buffer is full");
621 assert_eq!(err.code, ErrorCode::BufferFull);
622
623 let err = error!(ErrorCode::BufferFull, "Buffer {} is full", "test");
624 assert_eq!(err.message, "Buffer test is full");
625 }
626
627 #[test]
628 fn test_specialized_errors() {
629 let err = io::device_not_found("hw:0");
630 assert_eq!(err.code, ErrorCode::DeviceNotFound);
631 assert!(err.message.contains("hw:0"));
632 }
633
634 #[test]
635 fn test_error_category() {
636 assert_eq!(ErrorCode::BufferFull.category(), ErrorCategory::Core);
637 assert_eq!(ErrorCode::NodeNotFound.category(), ErrorCategory::Graph);
638 assert_eq!(ErrorCode::AlsaError.category(), ErrorCategory::Io);
639 assert_eq!(ErrorCode::MidiError.category(), ErrorCategory::Control);
640 assert_eq!(ErrorCode::ConfigNotFound.category(), ErrorCategory::Config);
641 assert_eq!(
642 ErrorCode::RealtimeViolation.category(),
643 ErrorCategory::Runtime
644 );
645 }
646
647 #[test]
648 fn test_realtime_critical() {
649 assert!(io::xrun().is_realtime_critical());
650 assert!(runtime::realtime_violation("test").is_realtime_critical());
651
652 assert!(!config::not_found("test").is_realtime_critical());
653 }
654}