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 Io,
38 Control,
40 Config,
42 Runtime,
44 Internal,
46}
47
48impl ErrorCategory {
49 pub fn as_str(&self) -> &'static str {
51 match self {
52 ErrorCategory::Core => "core",
53 ErrorCategory::Dsp => "dsp",
54 ErrorCategory::Io => "io",
55 ErrorCategory::Control => "control",
56 ErrorCategory::Config => "config",
57 ErrorCategory::Runtime => "runtime",
58 ErrorCategory::Internal => "internal",
59 }
60 }
61}
62
63impl fmt::Display for ErrorCategory {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.as_str())
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum ErrorCode {
72 Unknown = 0,
75 InvalidParameter = 1,
77 InvalidState = 2,
79 Unsupported = 3,
81 NotImplemented = 4,
83 Timeout = 5,
85
86 BufferFull = 100,
89 BufferEmpty = 101,
91 InvalidBufferSize = 102,
93 BufferMisaligned = 103,
95 BufferNotInitialized = 104,
97
98 QueueFull = 120,
101 QueueEmpty = 121,
103 QueueClosed = 122,
105 InvalidQueueIndex = 123,
107
108 DeviceNotFound = 300,
111 DeviceBusy = 301,
113 AlsaError = 310,
115 JackError = 311,
117 PipeWireError = 312,
119 XRun = 320,
121
122 OscError = 401,
125 MappingNotFound = 402,
127 AutomatonNotFound = 403,
129 InvalidParameterValue = 404,
131
132 ConfigNotFound = 500,
135 InvalidConfigFormat = 501,
137 MissingField = 502,
139
140 RealtimeViolation = 600,
143 PriorityError = 601,
145 AlreadyRunning = 602,
147 NotRunning = 603,
149}
150
151impl ErrorCode {
152 pub fn category(&self) -> ErrorCategory {
154 match *self {
155 ErrorCode::Unknown
156 | ErrorCode::InvalidParameter
157 | ErrorCode::InvalidState
158 | ErrorCode::Unsupported
159 | ErrorCode::NotImplemented
160 | ErrorCode::Timeout
161 | ErrorCode::BufferFull
162 | ErrorCode::BufferEmpty
163 | ErrorCode::InvalidBufferSize
164 | ErrorCode::BufferMisaligned
165 | ErrorCode::BufferNotInitialized
166 | ErrorCode::QueueFull
167 | ErrorCode::QueueEmpty
168 | ErrorCode::QueueClosed
169 | ErrorCode::InvalidQueueIndex => ErrorCategory::Core,
170
171 ErrorCode::DeviceNotFound
172 | ErrorCode::DeviceBusy
173 | ErrorCode::AlsaError
174 | ErrorCode::JackError
175 | ErrorCode::PipeWireError
176 | ErrorCode::XRun => ErrorCategory::Io,
177
178 ErrorCode::OscError
179 | ErrorCode::MappingNotFound
180 | ErrorCode::AutomatonNotFound
181 | ErrorCode::InvalidParameterValue => ErrorCategory::Control,
182
183 ErrorCode::ConfigNotFound
184 | ErrorCode::InvalidConfigFormat
185 | ErrorCode::MissingField => ErrorCategory::Config,
186
187 ErrorCode::RealtimeViolation
188 | ErrorCode::PriorityError
189 | ErrorCode::AlreadyRunning
190 | ErrorCode::NotRunning => ErrorCategory::Runtime,
191 }
192 }
193
194 pub fn description(&self) -> &'static str {
196 match self {
197 ErrorCode::Unknown => "Unknown error",
198 ErrorCode::InvalidParameter => "Invalid parameter",
199 ErrorCode::InvalidState => "Invalid state",
200 ErrorCode::Unsupported => "Unsupported operation",
201 ErrorCode::NotImplemented => "Not implemented",
202 ErrorCode::Timeout => "Operation timed out",
203
204 ErrorCode::BufferFull => "Buffer is full",
205 ErrorCode::BufferEmpty => "Buffer is empty",
206 ErrorCode::InvalidBufferSize => "Invalid buffer size",
207 ErrorCode::BufferMisaligned => "Buffer is misaligned for SIMD operations",
208 ErrorCode::BufferNotInitialized => "Buffer not initialized",
209
210 ErrorCode::QueueFull => "Queue is full",
211 ErrorCode::QueueEmpty => "Queue is empty",
212 ErrorCode::QueueClosed => "Queue is closed",
213 ErrorCode::InvalidQueueIndex => "Invalid queue index",
214
215 ErrorCode::DeviceNotFound => "Device not found",
216 ErrorCode::DeviceBusy => "Device is busy",
217 ErrorCode::AlsaError => "ALSA error",
218 ErrorCode::JackError => "JACK error",
219 ErrorCode::PipeWireError => "PipeWire error",
220 ErrorCode::XRun => "Buffer underrun/overrun detected",
221
222 ErrorCode::OscError => "OSC error",
223 ErrorCode::MappingNotFound => "Mapping not found",
224 ErrorCode::AutomatonNotFound => "Automaton not found",
225 ErrorCode::InvalidParameterValue => "Invalid parameter value",
226
227 ErrorCode::ConfigNotFound => "Configuration not found",
228 ErrorCode::InvalidConfigFormat => "Invalid configuration format",
229 ErrorCode::MissingField => "Missing required field",
230
231 ErrorCode::RealtimeViolation => "Real-time violation detected",
232 ErrorCode::PriorityError => "Failed to set thread priority",
233 ErrorCode::AlreadyRunning => "Already running",
234 ErrorCode::NotRunning => "Not running",
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
241pub struct ErrorLocation {
242 pub file: &'static str,
244 pub line: u32,
246 pub column: u32,
248}
249
250impl fmt::Display for ErrorLocation {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 write!(f, "{}:{}:{}", self.file, self.line, self.column)
253 }
254}
255
256impl Error {
261 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
263 Self {
264 category: code.category(),
265 code,
266 message: message.into(),
267 cause: None,
268 location: None,
269 }
270 }
271
272 pub fn with_cause(mut self, cause: Error) -> Self {
274 self.cause = Some(Box::new(cause));
275 self
276 }
277
278 pub fn at(mut self, file: &'static str, line: u32, column: u32) -> Self {
280 self.location = Some(ErrorLocation { file, line, column });
281 self
282 }
283
284 pub fn root_cause(&self) -> &Error {
286 let mut current = self;
287 while let Some(cause) = ¤t.cause {
288 current = cause;
289 }
290 current
291 }
292
293 pub fn is_realtime_critical(&self) -> bool {
295 matches!(
296 self.code,
297 ErrorCode::RealtimeViolation
298 | ErrorCode::PriorityError
299 | ErrorCode::BufferFull
300 | ErrorCode::XRun
301 )
302 }
303
304 pub fn is_recoverable(&self) -> bool {
306 !self.is_realtime_critical()
307 }
308}
309
310impl fmt::Display for Error {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 if let Some(loc) = &self.location {
313 write!(
314 f,
315 "[{}] at {}: {} ({})",
316 self.category,
317 loc,
318 self.message,
319 self.code.description()
320 )?;
321 } else {
322 write!(
323 f,
324 "[{}]: {} ({})",
325 self.category,
326 self.message,
327 self.code.description()
328 )?;
329 }
330
331 if let Some(cause) = &self.cause {
332 write!(f, "\n caused by: {}", cause)?;
333 }
334
335 Ok(())
336 }
337}
338
339impl StdError for Error {
340 fn source(&self) -> Option<&(dyn StdError + 'static)> {
341 self.cause.as_ref().map(|c| c as &dyn StdError)
342 }
343}
344
345pub type Result<T> = std::result::Result<T, Error>;
351
352impl From<std::io::Error> for Error {
357 fn from(err: std::io::Error) -> Self {
358 Error::new(ErrorCode::Unknown, err.to_string())
359 }
360}
361
362impl From<std::num::ParseIntError> for Error {
363 fn from(err: std::num::ParseIntError) -> Self {
364 Error::new(ErrorCode::InvalidParameter, err.to_string())
365 }
366}
367
368impl From<std::num::ParseFloatError> for Error {
369 fn from(err: std::num::ParseFloatError) -> Self {
370 Error::new(ErrorCode::InvalidParameter, err.to_string())
371 }
372}
373
374impl From<std::str::Utf8Error> for Error {
375 fn from(err: std::str::Utf8Error) -> Self {
376 Error::new(ErrorCode::InvalidParameter, err.to_string())
377 }
378}
379
380#[macro_export]
386macro_rules! error {
387 ($code:expr, $msg:expr) => {
388 $crate::error::Error::new($code, $msg)
389 };
390 ($code:expr, $fmt:expr, $($arg:tt)*) => {
391 $crate::error::Error::new($code, format!($fmt, $($arg)*))
392 };
393}
394
395pub mod io {
401 #![allow(unused)]
402 use super::*;
403
404 pub fn device_not_found(name: &str) -> Error {
406 error!(ErrorCode::DeviceNotFound, "Device not found: {}", name)
407 }
408
409 pub fn device_busy(name: &str) -> Error {
411 error!(ErrorCode::DeviceBusy, "Device is busy: {}", name)
412 }
413
414 pub fn alsa_error(desc: &str) -> Error {
416 error!(ErrorCode::AlsaError, "ALSA error: {}", desc)
417 }
418
419 pub fn jack_error(desc: &str) -> Error {
421 error!(ErrorCode::JackError, "JACK error: {}", desc)
422 }
423
424 pub fn pipewire_error(desc: &str) -> Error {
426 error!(ErrorCode::PipeWireError, "PipeWire error: {}", desc)
427 }
428
429 pub fn xrun() -> Error {
431 Error::new(ErrorCode::XRun, "Buffer underrun/overrun detected")
432 }
433}
434
435pub mod control {
437 use super::*;
438
439 pub fn osc_error(desc: &str) -> Error {
441 error!(ErrorCode::OscError, "OSC error: {}", desc)
442 }
443
444 pub fn mapping_not_found(id: &str) -> Error {
446 error!(ErrorCode::MappingNotFound, "Mapping not found: {}", id)
447 }
448
449 pub fn automaton_not_found(id: &str) -> Error {
451 error!(ErrorCode::AutomatonNotFound, "Automaton not found: {}", id)
452 }
453
454 pub fn invalid_parameter_value(param: &str, value: f64, min: f64, max: f64) -> Error {
456 error!(
457 ErrorCode::InvalidParameterValue,
458 "Invalid value for parameter {}: {} (allowed range: {} - {})", param, value, min, max
459 )
460 }
461}
462
463pub mod config {
465 use super::*;
466
467 pub fn not_found(path: &str) -> Error {
469 error!(
470 ErrorCode::ConfigNotFound,
471 "Configuration not found: {}", path
472 )
473 }
474
475 pub fn invalid_format(details: &str) -> Error {
477 error!(
478 ErrorCode::InvalidConfigFormat,
479 "Invalid configuration format: {}", details
480 )
481 }
482
483 pub fn missing_field(field: &str) -> Error {
485 error!(ErrorCode::MissingField, "Missing required field: {}", field)
486 }
487}
488
489pub mod runtime {
491 use super::*;
492
493 pub fn realtime_violation(details: &str) -> Error {
495 error!(
496 ErrorCode::RealtimeViolation,
497 "Real-time violation: {}", details
498 )
499 }
500
501 pub fn priority_error(details: &str) -> Error {
503 error!(
504 ErrorCode::PriorityError,
505 "Failed to set thread priority: {}", details
506 )
507 }
508
509 pub fn already_running() -> Error {
511 Error::new(ErrorCode::AlreadyRunning, "Already running")
512 }
513
514 pub fn not_running() -> Error {
516 Error::new(ErrorCode::NotRunning, "Not running")
517 }
518}
519
520#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
529 fn test_error_creation() {
530 let err = Error::new(ErrorCode::BufferFull, "Test error");
531 assert_eq!(err.code, ErrorCode::BufferFull);
532 assert_eq!(err.message, "Test error");
533 assert_eq!(err.category, ErrorCategory::Core);
534 }
535
536 #[test]
537 fn test_error_with_cause() {
538 let cause = Error::new(ErrorCode::BufferEmpty, "Cause");
539 let err = Error::new(ErrorCode::BufferFull, "Main error").with_cause(cause);
540
541 assert!(err.cause.is_some());
542 assert_eq!(err.root_cause().code, ErrorCode::BufferEmpty);
543 }
544
545 #[test]
546 fn test_error_macros() {
547 let err = error!(ErrorCode::BufferFull, "Buffer is full");
548 assert_eq!(err.code, ErrorCode::BufferFull);
549
550 let err = error!(ErrorCode::BufferFull, "Buffer {} is full", "test");
551 assert_eq!(err.message, "Buffer test is full");
552 }
553
554 #[test]
555 fn test_specialized_errors() {
556 let err = io::device_not_found("hw:0");
557 assert_eq!(err.code, ErrorCode::DeviceNotFound);
558 assert!(err.message.contains("hw:0"));
559 }
560
561 #[test]
562 fn test_error_category() {
563 assert_eq!(ErrorCode::BufferFull.category(), ErrorCategory::Core);
564 assert_eq!(ErrorCode::AlsaError.category(), ErrorCategory::Io);
565 assert_eq!(ErrorCode::OscError.category(), ErrorCategory::Control);
566 assert_eq!(ErrorCode::ConfigNotFound.category(), ErrorCategory::Config);
567 assert_eq!(
568 ErrorCode::RealtimeViolation.category(),
569 ErrorCategory::Runtime
570 );
571 }
572
573 #[test]
574 fn test_realtime_critical() {
575 assert!(io::xrun().is_realtime_critical());
576 assert!(runtime::realtime_violation("test").is_realtime_critical());
577
578 assert!(!config::not_found("test").is_realtime_critical());
579 }
580}