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("Realtime violation: {0}")]
92 RealtimeViolation(String),
93
94 #[error("Internal error: {0}")]
96 Internal(String),
97}
98
99pub type ProcessResult<T> = Result<T, ProcessError>;
101
102impl ProcessError {
103 pub fn processing(msg: impl Into<String>) -> Self {
105 Self::Processing(msg.into())
106 }
107
108 pub fn parameter(msg: impl Into<String>) -> Self {
110 Self::Parameter(msg.into())
111 }
112
113 pub fn invalid_port(port: impl fmt::Display) -> Self {
115 Self::InvalidPort(format!("Invalid port: {}", port))
116 }
117
118 pub fn buffer(msg: impl Into<String>) -> Self {
120 Self::Buffer(msg.into())
121 }
122
123 pub fn node_not_found(id: u32) -> Self {
125 Self::NodeNotFound(id)
126 }
127
128 pub fn port_not_found(port: impl fmt::Display) -> Self {
130 Self::PortNotFound(format!("{}", port))
131 }
132
133 pub fn connection(msg: impl Into<String>) -> Self {
135 Self::Connection(msg.into())
136 }
137
138 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
140 Self::TypeMismatch { expected, got }
141 }
142
143 pub fn sample_rate_mismatch(expected: f32, got: f32) -> Self {
145 Self::SampleRateMismatch { expected, got }
146 }
147
148 pub fn config(msg: impl Into<String>) -> Self {
150 Self::Config(msg.into())
151 }
152
153 pub fn unsupported(msg: impl Into<String>) -> Self {
155 Self::Unsupported(msg.into())
156 }
157
158 pub fn internal(msg: impl Into<String>) -> Self {
160 Self::Internal(msg.into())
161 }
162
163 pub fn is_recoverable(&self) -> bool {
168 match self {
169 Self::Processing(_) => true,
170 Self::Parameter(_) => true,
171 Self::InvalidPort(_) => false,
172 Self::Buffer(_) => true,
173 Self::NodeNotFound(_) => false,
174 Self::PortNotFound(_) => false,
175 Self::Connection(_) => false,
176 Self::TypeMismatch { .. } => false,
177 Self::SampleRateMismatch { .. } => false,
178 Self::Config(_) => false,
179 Self::NotInitialized => true,
180 Self::AlreadyInitialized => true,
181 Self::Unsupported(_) => false,
182 Self::Timeout => true,
183 Self::RealtimeViolation(_) => false,
184 Self::Internal(_) => false,
185 }
186 }
187
188 pub fn code(&self) -> &'static str {
190 match self {
191 Self::Processing(_) => "ERR_PROCESSING",
192 Self::Parameter(_) => "ERR_PARAMETER",
193 Self::InvalidPort(_) => "ERR_INVALID_PORT",
194 Self::Buffer(_) => "ERR_BUFFER",
195 Self::NodeNotFound(_) => "ERR_NODE_NOT_FOUND",
196 Self::PortNotFound(_) => "ERR_PORT_NOT_FOUND",
197 Self::Connection(_) => "ERR_CONNECTION",
198 Self::TypeMismatch { .. } => "ERR_TYPE_MISMATCH",
199 Self::SampleRateMismatch { .. } => "ERR_SAMPLE_RATE",
200 Self::Config(_) => "ERR_CONFIG",
201 Self::NotInitialized => "ERR_NOT_INIT",
202 Self::AlreadyInitialized => "ERR_ALREADY_INIT",
203 Self::Unsupported(_) => "ERR_UNSUPPORTED",
204 Self::Timeout => "ERR_TIMEOUT",
205 Self::RealtimeViolation(_) => "ERR_RT_VIOLATION",
206 Self::Internal(_) => "ERR_INTERNAL",
207 }
208 }
209}
210
211#[derive(Error, Debug, Clone, PartialEq)]
217pub enum ParameterError {
218 #[error("Parameter name cannot be empty")]
220 Empty,
221
222 #[error("Parameter name cannot contain '{0}'")]
224 InvalidCharacter(char),
225
226 #[error("Parameter name too long (max {max} characters)")]
228 TooLong {
229 max: usize,
231 },
232
233 #[error("Parameter name must start with a letter")]
235 MustStartWithLetter,
236
237 #[error("Parameter '{0}' not found")]
239 NotFound(String),
240
241 #[error("Parameter type mismatch: expected {expected:?}, got {got:?}")]
243 TypeMismatch {
244 expected: crate::traits::ParamType,
246 got: crate::traits::ParamType,
248 },
249
250 #[error("Value {value} out of range [{min}, {max}]")]
252 OutOfRange {
253 value: f32,
255 min: f32,
257 max: f32,
259 },
260
261 #[error("Invalid choice '{0}'")]
263 InvalidChoice(String),
264
265 #[error("Parameter '{0}' already exists")]
267 Duplicate(String),
268
269 #[error("Parameter '{0}' is read-only")]
271 ReadOnly(String),
272}
273
274pub type ParameterResult<T> = Result<T, ParameterError>;
276
277impl ParameterError {
278 pub fn not_found(name: impl Into<String>) -> Self {
280 Self::NotFound(name.into())
281 }
282
283 pub fn type_mismatch(
285 expected: crate::traits::ParamType,
286 got: crate::traits::ParamType,
287 ) -> Self {
288 Self::TypeMismatch { expected, got }
289 }
290
291 pub fn out_of_range(value: f32, min: f32, max: f32) -> Self {
293 Self::OutOfRange { value, min, max }
294 }
295
296 pub fn invalid_choice(choice: impl Into<String>) -> Self {
298 Self::InvalidChoice(choice.into())
299 }
300
301 pub fn duplicate(name: impl Into<String>) -> Self {
303 Self::Duplicate(name.into())
304 }
305
306 pub fn read_only(name: impl Into<String>) -> Self {
308 Self::ReadOnly(name.into())
309 }
310}
311
312#[derive(Error, Debug, Clone, PartialEq, Eq)]
318pub enum PortError {
319 #[error("Port {0} not found")]
321 NotFound(String),
322
323 #[error("Port direction mismatch: expected {expected}, got {got}")]
325 DirectionMismatch {
326 expected: crate::traits::PortDirection,
328 got: crate::traits::PortDirection,
330 },
331
332 #[error("Port type mismatch: expected {expected:?}, got {got:?}")]
334 TypeMismatch {
335 expected: crate::traits::PortType,
337 got: crate::traits::PortType,
339 },
340
341 #[error("Port {0} is already connected")]
343 AlreadyConnected(String),
344
345 #[error("Maximum connections reached for port {0}")]
347 MaxConnectionsReached(String),
348
349 #[error("Invalid port index: {0}")]
351 InvalidIndex(usize),
352}
353
354pub type PortResult<T> = Result<T, PortError>;
356
357impl PortError {
358 pub fn not_found(port: impl fmt::Display) -> Self {
360 Self::NotFound(format!("{}", port))
361 }
362
363 pub fn direction_mismatch(
365 expected: crate::traits::PortDirection,
366 got: crate::traits::PortDirection,
367 ) -> Self {
368 Self::DirectionMismatch { expected, got }
369 }
370
371 pub fn type_mismatch(expected: crate::traits::PortType, got: crate::traits::PortType) -> Self {
373 Self::TypeMismatch { expected, got }
374 }
375
376 pub fn already_connected(port: impl fmt::Display) -> Self {
378 Self::AlreadyConnected(format!("{}", port))
379 }
380}
381
382#[derive(Error, Debug, Clone, PartialEq)]
388pub enum ClockError {
389 #[error("Hardware error: {0}")]
391 Hardware(String),
392
393 #[error("Invalid sample rate: {0}")]
395 InvalidSampleRate(f32),
396
397 #[error("Clock not started")]
399 NotStarted,
400
401 #[error("Clock already started")]
403 AlreadyStarted,
404
405 #[error("Clock underflow")]
407 Underflow,
408
409 #[error("Clock overflow")]
411 Overflow,
412}
413
414pub type ClockResult<T> = Result<T, ClockError>;
416
417#[derive(Error, Debug, Clone, PartialEq, Eq)]
423pub enum ConnectionError {
424 #[error("Cannot connect node to itself")]
426 SelfConnection,
427
428 #[error("Cycle detected in graph")]
430 CycleDetected,
431
432 #[error("Connection would create a cycle")]
434 WouldCreateCycle,
435
436 #[error("Invalid connection: {0}")]
438 Invalid(String),
439}
440
441pub type ConnectionResult<T> = Result<T, ConnectionError>;
443
444#[derive(Debug, Clone)]
453pub struct ErrorContext {
454 pub location: Option<String>,
456
457 pub thread_id: Option<std::thread::ThreadId>,
459
460 pub timestamp: std::time::SystemTime,
462
463 pub node_id: Option<crate::traits::NodeId>,
465
466 pub port_id: Option<String>,
468
469 pub parameter_id: Option<String>,
471
472 pub extras: Vec<(String, String)>,
474}
475
476impl Default for ErrorContext {
477 fn default() -> Self {
478 Self {
479 location: None,
480 thread_id: Some(std::thread::current().id()),
481 timestamp: std::time::SystemTime::now(),
482 node_id: None,
483 port_id: None,
484 parameter_id: None,
485 extras: Vec::new(),
486 }
487 }
488}
489
490impl ErrorContext {
491 pub fn new() -> Self {
493 Self::default()
494 }
495
496 pub fn with_location(mut self, file: &str, line: u32) -> Self {
498 self.location = Some(format!("{}:{}", file, line));
499 self
500 }
501
502 pub fn with_node(mut self, node_id: crate::traits::NodeId) -> Self {
504 self.node_id = Some(node_id);
505 self
506 }
507
508 pub fn with_port(mut self, port_id: impl fmt::Display) -> Self {
510 self.port_id = Some(format!("{}", port_id));
511 self
512 }
513
514 pub fn with_parameter(mut self, param_id: impl AsRef<str>) -> Self {
516 self.parameter_id = Some(param_id.as_ref().to_string());
517 self
518 }
519
520 pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
522 self.extras.push((key.into(), value.into()));
523 self
524 }
525
526 pub fn format(&self, error: &impl std::error::Error) -> String {
528 let mut msg = format!("{}", error);
529
530 if let Some(loc) = &self.location {
531 msg.push_str(&format!("\n at {}", loc));
532 }
533
534 if let Some(id) = self.thread_id {
535 msg.push_str(&format!("\n thread: {:?}", id));
536 }
537
538 if let Some(node) = self.node_id {
539 msg.push_str(&format!("\n node: {}", node));
540 }
541
542 if let Some(port) = &self.port_id {
543 msg.push_str(&format!("\n port: {}", port));
544 }
545
546 if let Some(param) = &self.parameter_id {
547 msg.push_str(&format!("\n parameter: {}", param));
548 }
549
550 for (key, value) in &self.extras {
551 msg.push_str(&format!("\n {}: {}", key, value));
552 }
553
554 msg
555 }
556}
557
558impl From<ParameterError> for ProcessError {
563 fn from(err: ParameterError) -> Self {
564 match err {
565 ParameterError::NotFound(name) => {
566 Self::parameter(format!("Parameter not found: {}", name))
567 }
568 ParameterError::TypeMismatch { expected, got } => {
569 Self::type_mismatch(expected.name(), got.name())
570 }
571 ParameterError::OutOfRange { value, min, max } => {
572 Self::parameter(format!("Value {} out of range [{}, {}]", value, min, max))
573 }
574 ParameterError::InvalidChoice(choice) => {
575 Self::parameter(format!("Invalid choice: {}", choice))
576 }
577 ParameterError::Duplicate(name) => {
578 Self::parameter(format!("Duplicate parameter: {}", name))
579 }
580 ParameterError::ReadOnly(name) => {
581 Self::parameter(format!("Parameter is read-only: {}", name))
582 }
583 _ => Self::parameter(err.to_string()),
584 }
585 }
586}
587
588impl From<PortError> for ProcessError {
589 fn from(err: PortError) -> Self {
590 match err {
591 PortError::NotFound(port) => Self::port_not_found(port),
592 PortError::DirectionMismatch { expected, got } => {
593 Self::type_mismatch(expected.name(), got.name())
594 }
595 PortError::TypeMismatch { expected, got } => {
596 Self::type_mismatch(expected.name(), got.name())
597 }
598 PortError::AlreadyConnected(port) => {
599 Self::connection(format!("Port already connected: {}", port))
600 }
601 PortError::MaxConnectionsReached(port) => {
602 Self::connection(format!("Max connections reached for port: {}", port))
603 }
604 PortError::InvalidIndex(idx) => {
605 Self::invalid_port(format!("Invalid port index: {}", idx))
606 }
607 }
608 }
609}
610
611impl From<ClockError> for ProcessError {
612 fn from(err: ClockError) -> Self {
613 match err {
614 ClockError::Hardware(msg) => Self::processing(format!("Hardware error: {}", msg)),
615 ClockError::InvalidSampleRate(sr) => {
616 Self::config(format!("Invalid sample rate: {}", sr))
617 }
618 ClockError::NotStarted => Self::processing("Clock not started"),
619 ClockError::AlreadyStarted => Self::processing("Clock already started"),
620 ClockError::Underflow => Self::buffer("Clock underflow"),
621 ClockError::Overflow => Self::buffer("Clock overflow"),
622 }
623 }
624}
625
626impl From<ConnectionError> for ProcessError {
627 fn from(err: ConnectionError) -> Self {
628 match err {
629 ConnectionError::SelfConnection => Self::connection("Cannot connect node to itself"),
630 ConnectionError::CycleDetected => Self::connection("Cycle detected in graph"),
631 ConnectionError::WouldCreateCycle => {
632 Self::connection("Connection would create a cycle")
633 }
634 ConnectionError::Invalid(msg) => Self::connection(msg),
635 }
636 }
637}
638
639impl From<std::io::Error> for ProcessError {
640 fn from(err: std::io::Error) -> Self {
641 Self::Processing(format!("IO error: {}", err))
642 }
643}
644
645impl From<crate::error::Error> for ProcessError {
646 fn from(err: crate::error::Error) -> Self {
647 Self::Processing(err.to_string())
648 }
649}
650
651#[cfg(test)]
656mod tests {
657 use super::*;
658 use crate::traits::{PortDirection, PortType};
659
660 #[test]
661 fn test_process_error_creation() {
662 let err = ProcessError::processing("test error");
663 assert!(matches!(err, ProcessError::Processing(_)));
664 assert_eq!(err.code(), "ERR_PROCESSING");
665 assert!(err.is_recoverable());
666
667 let err = ProcessError::node_not_found(42);
668 assert!(matches!(err, ProcessError::NodeNotFound(42)));
669 assert_eq!(err.code(), "ERR_NODE_NOT_FOUND");
670 assert!(!err.is_recoverable());
671 }
672
673 #[test]
674 fn test_parameter_error_creation() {
675 let err = ParameterError::not_found("gain");
676 assert!(matches!(err, ParameterError::NotFound(_)));
677
678 let err = ParameterError::out_of_range(2.0, 0.0, 1.0);
679 assert!(matches!(err, ParameterError::OutOfRange { value: 2.0, .. }));
680 }
681
682 #[test]
683 fn test_port_error_creation() {
684 let err = PortError::direction_mismatch(PortDirection::Input, PortDirection::Output);
685 assert!(matches!(err, PortError::DirectionMismatch { .. }));
686
687 let err = PortError::type_mismatch(PortType::Signal, PortType::Control);
688 assert!(matches!(err, PortError::TypeMismatch { .. }));
689 }
690
691 #[test]
692 fn test_error_conversions() {
693 let param_err = ParameterError::not_found("test");
694 let proc_err: ProcessError = param_err.into();
695 assert!(matches!(proc_err, ProcessError::Parameter(_)));
696
697 let port_err = PortError::not_found("port");
698 let proc_err: ProcessError = port_err.into();
699 assert!(matches!(proc_err, ProcessError::PortNotFound(_)));
700
701 let clock_err = ClockError::Underflow;
702 let proc_err: ProcessError = clock_err.into();
703 assert!(matches!(proc_err, ProcessError::Buffer(_)));
704 }
705
706 #[test]
707 fn test_error_context() {
708 let ctx = ErrorContext::new()
709 .with_location("test.rs", 42)
710 .with_node(crate::traits::NodeId(1))
711 .with_extra("sample_rate", "44100");
712
713 let err = ProcessError::processing("test");
714 let formatted = ctx.format(&err);
715
716 assert!(formatted.contains("test.rs:42"));
717 assert!(formatted.contains("node: Node(1)"));
718 assert!(formatted.contains("sample_rate: 44100"));
719 }
720
721 #[test]
722 fn test_recoverable_flags() {
723 assert!(ProcessError::processing("test").is_recoverable());
724 assert!(ProcessError::parameter("test").is_recoverable());
725 assert!(ProcessError::buffer("test").is_recoverable());
726 assert!(!ProcessError::node_not_found(42).is_recoverable());
727 assert!(!ProcessError::port_not_found("port").is_recoverable());
728 }
729
730 #[test]
731 fn test_error_codes() {
732 assert_eq!(ProcessError::processing("").code(), "ERR_PROCESSING");
733 assert_eq!(ProcessError::node_not_found(0).code(), "ERR_NODE_NOT_FOUND");
734 }
735
736 #[test]
737 fn test_parameter_error_details() {
738 let err = ParameterError::out_of_range(1.5, 0.0, 1.0);
739 match err {
740 ParameterError::OutOfRange { value, min, max } => {
741 assert_eq!(value, 1.5);
742 assert_eq!(min, 0.0);
743 assert_eq!(max, 1.0);
744 }
745 _ => panic!("Wrong error type"),
746 }
747 }
748}