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 timestamp: std::time::SystemTime,
459
460 pub node_id: Option<crate::traits::NodeId>,
462
463 pub port_id: Option<String>,
465
466 pub parameter_id: Option<String>,
468
469 pub extras: Vec<(String, String)>,
471}
472
473impl Default for ErrorContext {
474 fn default() -> Self {
475 Self {
476 location: None,
477 timestamp: std::time::SystemTime::now(),
478 node_id: None,
479 port_id: None,
480 parameter_id: None,
481 extras: Vec::new(),
482 }
483 }
484}
485
486impl ErrorContext {
487 pub fn new() -> Self {
489 Self::default()
490 }
491
492 pub fn with_location(mut self, file: &str, line: u32) -> Self {
494 self.location = Some(format!("{}:{}", file, line));
495 self
496 }
497
498 pub fn with_node(mut self, node_id: crate::traits::NodeId) -> Self {
500 self.node_id = Some(node_id);
501 self
502 }
503
504 pub fn with_port(mut self, port_id: impl fmt::Display) -> Self {
506 self.port_id = Some(format!("{}", port_id));
507 self
508 }
509
510 pub fn with_parameter(mut self, param_id: impl AsRef<str>) -> Self {
512 self.parameter_id = Some(param_id.as_ref().to_string());
513 self
514 }
515
516 pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
518 self.extras.push((key.into(), value.into()));
519 self
520 }
521
522 pub fn format(&self, error: &impl std::error::Error) -> String {
524 let mut msg = format!("{}", error);
525
526 if let Some(loc) = &self.location {
527 msg.push_str(&format!("\n at {}", loc));
528 }
529
530 if let Some(node) = self.node_id {
531 msg.push_str(&format!("\n node: {}", node));
532 }
533
534 if let Some(port) = &self.port_id {
535 msg.push_str(&format!("\n port: {}", port));
536 }
537
538 if let Some(param) = &self.parameter_id {
539 msg.push_str(&format!("\n parameter: {}", param));
540 }
541
542 for (key, value) in &self.extras {
543 msg.push_str(&format!("\n {}: {}", key, value));
544 }
545
546 msg
547 }
548}
549
550impl From<ParameterError> for ProcessError {
555 fn from(err: ParameterError) -> Self {
556 match err {
557 ParameterError::NotFound(name) => {
558 Self::parameter(format!("Parameter not found: {}", name))
559 }
560 ParameterError::TypeMismatch { expected, got } => {
561 Self::type_mismatch(expected.name(), got.name())
562 }
563 ParameterError::OutOfRange { value, min, max } => {
564 Self::parameter(format!("Value {} out of range [{}, {}]", value, min, max))
565 }
566 ParameterError::InvalidChoice(choice) => {
567 Self::parameter(format!("Invalid choice: {}", choice))
568 }
569 ParameterError::Duplicate(name) => {
570 Self::parameter(format!("Duplicate parameter: {}", name))
571 }
572 ParameterError::ReadOnly(name) => {
573 Self::parameter(format!("Parameter is read-only: {}", name))
574 }
575 _ => Self::parameter(err.to_string()),
576 }
577 }
578}
579
580impl From<PortError> for ProcessError {
581 fn from(err: PortError) -> Self {
582 match err {
583 PortError::NotFound(port) => Self::port_not_found(port),
584 PortError::DirectionMismatch { expected, got } => {
585 Self::type_mismatch(expected.name(), got.name())
586 }
587 PortError::TypeMismatch { expected, got } => {
588 Self::type_mismatch(expected.name(), got.name())
589 }
590 PortError::AlreadyConnected(port) => {
591 Self::connection(format!("Port already connected: {}", port))
592 }
593 PortError::MaxConnectionsReached(port) => {
594 Self::connection(format!("Max connections reached for port: {}", port))
595 }
596 PortError::InvalidIndex(idx) => {
597 Self::invalid_port(format!("Invalid port index: {}", idx))
598 }
599 }
600 }
601}
602
603impl From<ClockError> for ProcessError {
604 fn from(err: ClockError) -> Self {
605 match err {
606 ClockError::Hardware(msg) => Self::processing(format!("Hardware error: {}", msg)),
607 ClockError::InvalidSampleRate(sr) => {
608 Self::config(format!("Invalid sample rate: {}", sr))
609 }
610 ClockError::NotStarted => Self::processing("Clock not started"),
611 ClockError::AlreadyStarted => Self::processing("Clock already started"),
612 ClockError::Underflow => Self::buffer("Clock underflow"),
613 ClockError::Overflow => Self::buffer("Clock overflow"),
614 }
615 }
616}
617
618impl From<ConnectionError> for ProcessError {
619 fn from(err: ConnectionError) -> Self {
620 match err {
621 ConnectionError::SelfConnection => Self::connection("Cannot connect node to itself"),
622 ConnectionError::CycleDetected => Self::connection("Cycle detected in graph"),
623 ConnectionError::WouldCreateCycle => {
624 Self::connection("Connection would create a cycle")
625 }
626 ConnectionError::Invalid(msg) => Self::connection(msg),
627 }
628 }
629}
630
631impl From<std::io::Error> for ProcessError {
632 fn from(err: std::io::Error) -> Self {
633 Self::Processing(format!("IO error: {}", err))
634 }
635}
636
637impl From<crate::error::Error> for ProcessError {
638 fn from(err: crate::error::Error) -> Self {
639 Self::Processing(err.to_string())
640 }
641}
642
643#[cfg(test)]
648mod tests {
649 use super::*;
650 use crate::traits::{PortDirection, PortType};
651
652 #[test]
653 fn test_process_error_creation() {
654 let err = ProcessError::processing("test error");
655 assert!(matches!(err, ProcessError::Processing(_)));
656 assert_eq!(err.code(), "ERR_PROCESSING");
657 assert!(err.is_recoverable());
658
659 let err = ProcessError::node_not_found(42);
660 assert!(matches!(err, ProcessError::NodeNotFound(42)));
661 assert_eq!(err.code(), "ERR_NODE_NOT_FOUND");
662 assert!(!err.is_recoverable());
663 }
664
665 #[test]
666 fn test_parameter_error_creation() {
667 let err = ParameterError::not_found("gain");
668 assert!(matches!(err, ParameterError::NotFound(_)));
669
670 let err = ParameterError::out_of_range(2.0, 0.0, 1.0);
671 assert!(matches!(err, ParameterError::OutOfRange { value: 2.0, .. }));
672 }
673
674 #[test]
675 fn test_port_error_creation() {
676 let err = PortError::direction_mismatch(PortDirection::Input, PortDirection::Output);
677 assert!(matches!(err, PortError::DirectionMismatch { .. }));
678
679 let err = PortError::type_mismatch(PortType::Signal, PortType::Control);
680 assert!(matches!(err, PortError::TypeMismatch { .. }));
681 }
682
683 #[test]
684 fn test_error_conversions() {
685 let param_err = ParameterError::not_found("test");
686 let proc_err: ProcessError = param_err.into();
687 assert!(matches!(proc_err, ProcessError::Parameter(_)));
688
689 let port_err = PortError::not_found("port");
690 let proc_err: ProcessError = port_err.into();
691 assert!(matches!(proc_err, ProcessError::PortNotFound(_)));
692
693 let clock_err = ClockError::Underflow;
694 let proc_err: ProcessError = clock_err.into();
695 assert!(matches!(proc_err, ProcessError::Buffer(_)));
696 }
697
698 #[test]
699 fn test_error_context() {
700 let ctx = ErrorContext::new()
701 .with_location("test.rs", 42)
702 .with_node(crate::traits::NodeId(1))
703 .with_extra("sample_rate", "44100");
704
705 let err = ProcessError::processing("test");
706 let formatted = ctx.format(&err);
707
708 assert!(formatted.contains("test.rs:42"));
709 assert!(formatted.contains("node: Node(1)"));
710 assert!(formatted.contains("sample_rate: 44100"));
711 }
712
713 #[test]
714 fn test_recoverable_flags() {
715 assert!(ProcessError::processing("test").is_recoverable());
716 assert!(ProcessError::parameter("test").is_recoverable());
717 assert!(ProcessError::buffer("test").is_recoverable());
718 assert!(!ProcessError::node_not_found(42).is_recoverable());
719 assert!(!ProcessError::port_not_found("port").is_recoverable());
720 }
721
722 #[test]
723 fn test_error_codes() {
724 assert_eq!(ProcessError::processing("").code(), "ERR_PROCESSING");
725 assert_eq!(ProcessError::node_not_found(0).code(), "ERR_NODE_NOT_FOUND");
726 }
727
728 #[test]
729 fn test_parameter_error_details() {
730 let err = ParameterError::out_of_range(1.5, 0.0, 1.0);
731 match err {
732 ParameterError::OutOfRange { value, min, max } => {
733 assert_eq!(value, 1.5);
734 assert_eq!(min, 0.0);
735 assert_eq!(max, 1.0);
736 }
737 _ => panic!("Wrong error type"),
738 }
739 }
740}