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