1use std::fmt;
11use thiserror::Error;
12
13#[derive(Error, Debug, Clone, PartialEq)]
22pub enum ProcessError {
23 #[error("Processing error: {0}")]
25 Processing(String),
26
27 #[error("Parameter error: {0}")]
29 Parameter(String),
30
31 #[error("Invalid port: {0}")]
33 InvalidPort(String),
34
35 #[error("Buffer error: {0}")]
37 Buffer(String),
38
39 #[error("Node {0} not found")]
41 NodeNotFound(u32),
42
43 #[error("Port {0} not found")]
45 PortNotFound(String),
46
47 #[error("Connection error: {0}")]
49 Connection(String),
50
51 #[error("Type mismatch: expected {expected}, got {got}")]
53 TypeMismatch {
54 expected: &'static str,
56 got: &'static str,
58 },
59
60 #[error("Sample rate mismatch: expected {expected}, got {got}")]
62 SampleRateMismatch {
63 expected: f32,
65 got: f32,
67 },
68
69 #[error("Configuration error: {0}")]
71 Config(String),
72
73 #[error("Not initialized")]
75 NotInitialized,
76
77 #[error("Already initialized")]
79 AlreadyInitialized,
80
81 #[error("Unsupported operation: {0}")]
83 Unsupported(String),
84
85 #[error("Operation timed out")]
87 Timeout,
88
89 #[error("Internal error: {0}")]
91 Internal(String),
92}
93
94pub type ProcessResult<T> = Result<T, ProcessError>;
96
97impl ProcessError {
98 pub fn processing(msg: impl Into<String>) -> Self {
100 Self::Processing(msg.into())
101 }
102
103 pub fn parameter(msg: impl Into<String>) -> Self {
105 Self::Parameter(msg.into())
106 }
107
108 pub fn invalid_port(port: impl fmt::Display) -> Self {
110 Self::InvalidPort(format!("Invalid port: {}", port))
111 }
112
113 pub fn buffer(msg: impl Into<String>) -> Self {
115 Self::Buffer(msg.into())
116 }
117
118 pub fn node_not_found(id: u32) -> Self {
120 Self::NodeNotFound(id)
121 }
122
123 pub fn port_not_found(port: impl fmt::Display) -> Self {
125 Self::PortNotFound(format!("{}", port))
126 }
127
128 pub fn connection(msg: impl Into<String>) -> Self {
130 Self::Connection(msg.into())
131 }
132
133 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
135 Self::TypeMismatch { expected, got }
136 }
137
138 pub fn sample_rate_mismatch(expected: f32, got: f32) -> Self {
140 Self::SampleRateMismatch { expected, got }
141 }
142
143 pub fn config(msg: impl Into<String>) -> Self {
145 Self::Config(msg.into())
146 }
147
148 pub fn unsupported(msg: impl Into<String>) -> Self {
150 Self::Unsupported(msg.into())
151 }
152
153 pub fn internal(msg: impl Into<String>) -> Self {
155 Self::Internal(msg.into())
156 }
157
158 pub fn is_recoverable(&self) -> bool {
163 match self {
164 Self::Processing(_) => true,
165 Self::Parameter(_) => true,
166 Self::InvalidPort(_) => false,
167 Self::Buffer(_) => true,
168 Self::NodeNotFound(_) => false,
169 Self::PortNotFound(_) => false,
170 Self::Connection(_) => false,
171 Self::TypeMismatch { .. } => false,
172 Self::SampleRateMismatch { .. } => false,
173 Self::Config(_) => false,
174 Self::NotInitialized => true,
175 Self::AlreadyInitialized => true,
176 Self::Unsupported(_) => false,
177 Self::Timeout => true,
178 Self::Internal(_) => false,
179 }
180 }
181
182 pub fn code(&self) -> &'static str {
184 match self {
185 Self::Processing(_) => "ERR_PROCESSING",
186 Self::Parameter(_) => "ERR_PARAMETER",
187 Self::InvalidPort(_) => "ERR_INVALID_PORT",
188 Self::Buffer(_) => "ERR_BUFFER",
189 Self::NodeNotFound(_) => "ERR_NODE_NOT_FOUND",
190 Self::PortNotFound(_) => "ERR_PORT_NOT_FOUND",
191 Self::Connection(_) => "ERR_CONNECTION",
192 Self::TypeMismatch { .. } => "ERR_TYPE_MISMATCH",
193 Self::SampleRateMismatch { .. } => "ERR_SAMPLE_RATE",
194 Self::Config(_) => "ERR_CONFIG",
195 Self::NotInitialized => "ERR_NOT_INIT",
196 Self::AlreadyInitialized => "ERR_ALREADY_INIT",
197 Self::Unsupported(_) => "ERR_UNSUPPORTED",
198 Self::Timeout => "ERR_TIMEOUT",
199 Self::Internal(_) => "ERR_INTERNAL",
200 }
201 }
202}
203
204#[derive(Error, Debug, Clone, PartialEq)]
210pub enum ParameterError {
211 #[error("Parameter name cannot be empty")]
213 Empty,
214
215 #[error("Parameter name cannot contain '{0}'")]
217 InvalidCharacter(char),
218
219 #[error("Parameter name too long (max {max} characters)")]
221 TooLong {
222 max: usize,
224 },
225
226 #[error("Parameter name must start with a letter")]
228 MustStartWithLetter,
229
230 #[error("Parameter '{0}' not found")]
232 NotFound(String),
233
234 #[error("Parameter type mismatch: expected {expected:?}, got {got:?}")]
236 TypeMismatch {
237 expected: crate::traits::ParamType,
239 got: crate::traits::ParamType,
241 },
242
243 #[error("Value {value} out of range [{min}, {max}]")]
245 OutOfRange {
246 value: f32,
248 min: f32,
250 max: f32,
252 },
253
254 #[error("Invalid choice '{0}'")]
256 InvalidChoice(String),
257
258 #[error("Parameter '{0}' already exists")]
260 Duplicate(String),
261
262 #[error("Parameter '{0}' is read-only")]
264 ReadOnly(String),
265}
266
267pub type ParameterResult<T> = Result<T, ParameterError>;
269
270impl ParameterError {
271 pub fn not_found(name: impl Into<String>) -> Self {
273 Self::NotFound(name.into())
274 }
275
276 pub fn type_mismatch(
278 expected: crate::traits::ParamType,
279 got: crate::traits::ParamType,
280 ) -> Self {
281 Self::TypeMismatch { expected, got }
282 }
283
284 pub fn out_of_range(value: f32, min: f32, max: f32) -> Self {
286 Self::OutOfRange { value, min, max }
287 }
288
289 pub fn invalid_choice(choice: impl Into<String>) -> Self {
291 Self::InvalidChoice(choice.into())
292 }
293
294 pub fn duplicate(name: impl Into<String>) -> Self {
296 Self::Duplicate(name.into())
297 }
298
299 pub fn read_only(name: impl Into<String>) -> Self {
301 Self::ReadOnly(name.into())
302 }
303}
304
305#[derive(Error, Debug, Clone, PartialEq, Eq)]
311pub enum PortError {
312 #[error("Port {0} not found")]
314 NotFound(String),
315
316 #[error("Port direction mismatch: expected {expected}, got {got}")]
318 DirectionMismatch {
319 expected: crate::traits::PortDirection,
321 got: crate::traits::PortDirection,
323 },
324
325 #[error("Port type mismatch: expected {expected:?}, got {got:?}")]
327 TypeMismatch {
328 expected: crate::traits::PortType,
330 got: crate::traits::PortType,
332 },
333
334 #[error("Port {0} is already connected")]
336 AlreadyConnected(String),
337
338 #[error("Maximum connections reached for port {0}")]
340 MaxConnectionsReached(String),
341
342 #[error("Invalid port index: {0}")]
344 InvalidIndex(usize),
345}
346
347pub type PortResult<T> = Result<T, PortError>;
349
350impl PortError {
351 pub fn not_found(port: impl fmt::Display) -> Self {
353 Self::NotFound(format!("{}", port))
354 }
355
356 pub fn direction_mismatch(
358 expected: crate::traits::PortDirection,
359 got: crate::traits::PortDirection,
360 ) -> Self {
361 Self::DirectionMismatch { expected, got }
362 }
363
364 pub fn type_mismatch(expected: crate::traits::PortType, got: crate::traits::PortType) -> Self {
366 Self::TypeMismatch { expected, got }
367 }
368
369 pub fn already_connected(port: impl fmt::Display) -> Self {
371 Self::AlreadyConnected(format!("{}", port))
372 }
373}
374
375#[derive(Error, Debug, Clone, PartialEq)]
381pub enum ClockError {
382 #[error("Hardware error: {0}")]
384 Hardware(String),
385
386 #[error("Invalid sample rate: {0}")]
388 InvalidSampleRate(f32),
389
390 #[error("Clock not started")]
392 NotStarted,
393
394 #[error("Clock already started")]
396 AlreadyStarted,
397
398 #[error("Clock underflow")]
400 Underflow,
401
402 #[error("Clock overflow")]
404 Overflow,
405}
406
407pub type ClockResult<T> = Result<T, ClockError>;
409
410#[derive(Error, Debug, Clone, PartialEq, Eq)]
416pub enum ConnectionError {
417 #[error("Cannot connect node to itself")]
419 SelfConnection,
420
421 #[error("Cycle detected in graph")]
423 CycleDetected,
424
425 #[error("Connection would create a cycle")]
427 WouldCreateCycle,
428
429 #[error("Invalid connection: {0}")]
431 Invalid(String),
432}
433
434pub type ConnectionResult<T> = Result<T, ConnectionError>;
436
437#[derive(Debug, Clone)]
446pub struct ErrorContext {
447 pub location: Option<String>,
449
450 pub thread_id: Option<std::thread::ThreadId>,
452
453 pub timestamp: std::time::SystemTime,
455
456 pub node_id: Option<crate::traits::NodeId>,
458
459 pub port_id: Option<String>,
461
462 pub parameter_id: Option<String>,
464
465 pub extras: Vec<(String, String)>,
467}
468
469impl Default for ErrorContext {
470 fn default() -> Self {
471 Self {
472 location: None,
473 thread_id: Some(std::thread::current().id()),
474 timestamp: std::time::SystemTime::now(),
475 node_id: None,
476 port_id: None,
477 parameter_id: None,
478 extras: Vec::new(),
479 }
480 }
481}
482
483impl ErrorContext {
484 pub fn new() -> Self {
486 Self::default()
487 }
488
489 pub fn with_location(mut self, file: &str, line: u32) -> Self {
491 self.location = Some(format!("{}:{}", file, line));
492 self
493 }
494
495 pub fn with_node(mut self, node_id: crate::traits::NodeId) -> Self {
497 self.node_id = Some(node_id);
498 self
499 }
500
501 pub fn with_port(mut self, port_id: impl fmt::Display) -> Self {
503 self.port_id = Some(format!("{}", port_id));
504 self
505 }
506
507 pub fn with_parameter(mut self, param_id: impl AsRef<str>) -> Self {
509 self.parameter_id = Some(param_id.as_ref().to_string());
510 self
511 }
512
513 pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
515 self.extras.push((key.into(), value.into()));
516 self
517 }
518
519 pub fn format(&self, error: &impl std::error::Error) -> String {
521 let mut msg = format!("{}", error);
522
523 if let Some(loc) = &self.location {
524 msg.push_str(&format!("\n at {}", loc));
525 }
526
527 if let Some(id) = self.thread_id {
528 msg.push_str(&format!("\n thread: {:?}", id));
529 }
530
531 if let Some(node) = self.node_id {
532 msg.push_str(&format!("\n node: {}", node));
533 }
534
535 if let Some(port) = &self.port_id {
536 msg.push_str(&format!("\n port: {}", port));
537 }
538
539 if let Some(param) = &self.parameter_id {
540 msg.push_str(&format!("\n parameter: {}", param));
541 }
542
543 for (key, value) in &self.extras {
544 msg.push_str(&format!("\n {}: {}", key, value));
545 }
546
547 msg
548 }
549}
550
551impl From<ParameterError> for ProcessError {
556 fn from(err: ParameterError) -> Self {
557 match err {
558 ParameterError::NotFound(name) => {
559 Self::parameter(format!("Parameter not found: {}", name))
560 }
561 ParameterError::TypeMismatch { expected, got } => {
562 Self::type_mismatch(expected.name(), got.name())
563 }
564 ParameterError::OutOfRange { value, min, max } => {
565 Self::parameter(format!("Value {} out of range [{}, {}]", value, min, max))
566 }
567 ParameterError::InvalidChoice(choice) => {
568 Self::parameter(format!("Invalid choice: {}", choice))
569 }
570 ParameterError::Duplicate(name) => {
571 Self::parameter(format!("Duplicate parameter: {}", name))
572 }
573 ParameterError::ReadOnly(name) => {
574 Self::parameter(format!("Parameter is read-only: {}", name))
575 }
576 _ => Self::parameter(err.to_string()),
577 }
578 }
579}
580
581impl From<PortError> for ProcessError {
582 fn from(err: PortError) -> Self {
583 match err {
584 PortError::NotFound(port) => Self::port_not_found(port),
585 PortError::DirectionMismatch { expected, got } => {
586 Self::type_mismatch(expected.name(), got.name())
587 }
588 PortError::TypeMismatch { expected, got } => {
589 Self::type_mismatch(expected.name(), got.name())
590 }
591 PortError::AlreadyConnected(port) => {
592 Self::connection(format!("Port already connected: {}", port))
593 }
594 PortError::MaxConnectionsReached(port) => {
595 Self::connection(format!("Max connections reached for port: {}", port))
596 }
597 PortError::InvalidIndex(idx) => {
598 Self::invalid_port(format!("Invalid port index: {}", idx))
599 }
600 }
601 }
602}
603
604impl From<ClockError> for ProcessError {
605 fn from(err: ClockError) -> Self {
606 match err {
607 ClockError::Hardware(msg) => Self::processing(format!("Hardware error: {}", msg)),
608 ClockError::InvalidSampleRate(sr) => {
609 Self::config(format!("Invalid sample rate: {}", sr))
610 }
611 ClockError::NotStarted => Self::processing("Clock not started"),
612 ClockError::AlreadyStarted => Self::processing("Clock already started"),
613 ClockError::Underflow => Self::buffer("Clock underflow"),
614 ClockError::Overflow => Self::buffer("Clock overflow"),
615 }
616 }
617}
618
619impl From<ConnectionError> for ProcessError {
620 fn from(err: ConnectionError) -> Self {
621 match err {
622 ConnectionError::SelfConnection => Self::connection("Cannot connect node to itself"),
623 ConnectionError::CycleDetected => Self::connection("Cycle detected in graph"),
624 ConnectionError::WouldCreateCycle => {
625 Self::connection("Connection would create a cycle")
626 }
627 ConnectionError::Invalid(msg) => Self::connection(msg),
628 }
629 }
630}
631
632impl From<std::io::Error> for ProcessError {
633 fn from(err: std::io::Error) -> Self {
634 Self::Processing(format!("IO error: {}", err))
635 }
636}
637
638impl From<crate::error::Error> for ProcessError {
639 fn from(err: crate::error::Error) -> Self {
640 Self::Processing(err.to_string())
641 }
642}
643
644#[cfg(test)]
649mod tests {
650 use super::*;
651 use crate::traits::{PortDirection, PortType};
652
653 #[test]
654 fn test_process_error_creation() {
655 let err = ProcessError::processing("test error");
656 assert!(matches!(err, ProcessError::Processing(_)));
657 assert_eq!(err.code(), "ERR_PROCESSING");
658 assert!(err.is_recoverable());
659
660 let err = ProcessError::node_not_found(42);
661 assert!(matches!(err, ProcessError::NodeNotFound(42)));
662 assert_eq!(err.code(), "ERR_NODE_NOT_FOUND");
663 assert!(!err.is_recoverable());
664 }
665
666 #[test]
667 fn test_parameter_error_creation() {
668 let err = ParameterError::not_found("gain");
669 assert!(matches!(err, ParameterError::NotFound(_)));
670
671 let err = ParameterError::out_of_range(2.0, 0.0, 1.0);
672 assert!(matches!(err, ParameterError::OutOfRange { value: 2.0, .. }));
673 }
674
675 #[test]
676 fn test_port_error_creation() {
677 let err = PortError::direction_mismatch(PortDirection::Input, PortDirection::Output);
678 assert!(matches!(err, PortError::DirectionMismatch { .. }));
679
680 let err = PortError::type_mismatch(PortType::Audio, PortType::Control);
681 assert!(matches!(err, PortError::TypeMismatch { .. }));
682 }
683
684 #[test]
685 fn test_error_conversions() {
686 let param_err = ParameterError::not_found("test");
687 let proc_err: ProcessError = param_err.into();
688 assert!(matches!(proc_err, ProcessError::Parameter(_)));
689
690 let port_err = PortError::not_found("port");
691 let proc_err: ProcessError = port_err.into();
692 assert!(matches!(proc_err, ProcessError::PortNotFound(_)));
693
694 let clock_err = ClockError::Underflow;
695 let proc_err: ProcessError = clock_err.into();
696 assert!(matches!(proc_err, ProcessError::Buffer(_)));
697 }
698
699 #[test]
700 fn test_error_context() {
701 let ctx = ErrorContext::new()
702 .with_location("test.rs", 42)
703 .with_node(crate::traits::NodeId(1))
704 .with_extra("sample_rate", "44100");
705
706 let err = ProcessError::processing("test");
707 let formatted = ctx.format(&err);
708
709 assert!(formatted.contains("test.rs:42"));
710 assert!(formatted.contains("node: Node(1)"));
711 assert!(formatted.contains("sample_rate: 44100"));
712 }
713
714 #[test]
715 fn test_recoverable_flags() {
716 assert!(ProcessError::processing("test").is_recoverable());
717 assert!(ProcessError::parameter("test").is_recoverable());
718 assert!(ProcessError::buffer("test").is_recoverable());
719 assert!(!ProcessError::node_not_found(42).is_recoverable());
720 assert!(!ProcessError::port_not_found("port").is_recoverable());
721 }
722
723 #[test]
724 fn test_error_codes() {
725 assert_eq!(ProcessError::processing("").code(), "ERR_PROCESSING");
726 assert_eq!(ProcessError::node_not_found(0).code(), "ERR_NODE_NOT_FOUND");
727 }
728
729 #[test]
730 fn test_parameter_error_details() {
731 let err = ParameterError::out_of_range(1.5, 0.0, 1.0);
732 match err {
733 ParameterError::OutOfRange { value, min, max } => {
734 assert_eq!(value, 1.5);
735 assert_eq!(min, 0.0);
736 assert_eq!(max, 1.0);
737 }
738 _ => panic!("Wrong error type"),
739 }
740 }
741}