Skip to main content

rill_core/traits/
error.rs

1//! # Error Types for Rill Traits
2//!
3//! This module defines the error types used throughout the Rill ecosystem.
4//! All errors implement `std::error::Error` and are designed to be:
5//! - Thread-safe (`Send + Sync`)
6//! - Cloneable for passing between threads
7//! - Human-readable with detailed context
8//! - Real-time safe (no allocations in error paths)
9
10use thiserror::Error;
11
12// ============================================================================
13// Core Process Error
14// ============================================================================
15
16/// Main error type for signal processing operations
17///
18/// This error can occur during node processing, parameter changes,
19/// or any other operation in the signal graph.
20#[derive(Error, Debug, Clone, PartialEq)]
21pub enum ProcessError {
22    /// Error during signal processing
23    #[error("Processing error: {0}")]
24    Processing(String),
25
26    /// Error with a parameter (invalid value, out of range, etc.)
27    #[error("Parameter error: {0}")]
28    Parameter(String),
29
30    /// Buffer operation failed
31    #[error("Buffer error: {0}")]
32    Buffer(String),
33
34    /// Type mismatch (e.g., trying to connect signal to control)
35    #[error("Type mismatch: expected {expected}, got {got}")]
36    TypeMismatch {
37        /// Expected type
38        expected: &'static str,
39        /// Actual type
40        got: &'static str,
41    },
42
43    /// Sample rate mismatch
44    #[error("Sample rate mismatch: expected {expected}, got {got}")]
45    SampleRateMismatch {
46        /// Expected sample rate
47        expected: f32,
48        /// Actual sample rate
49        got: f32,
50    },
51
52    /// Configuration error
53    #[error("Configuration error: {0}")]
54    Config(String),
55
56    /// Not initialized
57    #[error("Not initialized")]
58    NotInitialized,
59
60    /// Already initialized
61    #[error("Already initialized")]
62    AlreadyInitialized,
63
64    /// Unsupported operation
65    #[error("Unsupported operation: {0}")]
66    Unsupported(String),
67
68    /// Timeout occurred
69    #[error("Operation timed out")]
70    Timeout,
71
72    /// Real-time violation — operation exceeded its time budget or
73    /// performed an illegal action (allocation, blocking, etc.)
74    #[error("Realtime violation: {0}")]
75    RealtimeViolation(String),
76
77    /// Internal error (for implementation-specific errors)
78    #[error("Internal error: {0}")]
79    Internal(String),
80}
81
82/// Result type for signal processing operations
83pub type ProcessResult<T> = Result<T, ProcessError>;
84
85impl ProcessError {
86    /// Create a new processing error with a formatted message
87    pub fn processing(msg: impl Into<String>) -> Self {
88        Self::Processing(msg.into())
89    }
90
91    /// Create a new parameter error with a formatted message
92    pub fn parameter(msg: impl Into<String>) -> Self {
93        Self::Parameter(msg.into())
94    }
95
96    /// Create a new buffer error
97    pub fn buffer(msg: impl Into<String>) -> Self {
98        Self::Buffer(msg.into())
99    }
100
101    /// Create a new type mismatch error
102    pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
103        Self::TypeMismatch { expected, got }
104    }
105
106    /// Create a new sample rate mismatch error
107    pub fn sample_rate_mismatch(expected: f32, got: f32) -> Self {
108        Self::SampleRateMismatch { expected, got }
109    }
110
111    /// Create a new configuration error
112    pub fn config(msg: impl Into<String>) -> Self {
113        Self::Config(msg.into())
114    }
115
116    /// Create a new unsupported operation error
117    pub fn unsupported(msg: impl Into<String>) -> Self {
118        Self::Unsupported(msg.into())
119    }
120
121    /// Create a new internal error
122    pub fn internal(msg: impl Into<String>) -> Self {
123        Self::Internal(msg.into())
124    }
125
126    /// Check if this error is recoverable
127    ///
128    /// Recoverable errors are those that don't require stopping the signal thread,
129    /// such as temporary buffer underflows or parameter errors.
130    pub fn is_recoverable(&self) -> bool {
131        match self {
132            Self::Processing(_) => true,
133            Self::Parameter(_) => true,
134            Self::Buffer(_) => true,
135            Self::TypeMismatch { .. } => false,
136            Self::SampleRateMismatch { .. } => false,
137            Self::Config(_) => false,
138            Self::NotInitialized => true,
139            Self::AlreadyInitialized => true,
140            Self::Unsupported(_) => false,
141            Self::Timeout => true,
142            Self::RealtimeViolation(_) => false,
143            Self::Internal(_) => false,
144        }
145    }
146
147    /// Get a short error code for this error (useful for logging)
148    pub fn code(&self) -> &'static str {
149        match self {
150            Self::Processing(_) => "ERR_PROCESSING",
151            Self::Parameter(_) => "ERR_PARAMETER",
152            Self::Buffer(_) => "ERR_BUFFER",
153            Self::TypeMismatch { .. } => "ERR_TYPE_MISMATCH",
154            Self::SampleRateMismatch { .. } => "ERR_SAMPLE_RATE",
155            Self::Config(_) => "ERR_CONFIG",
156            Self::NotInitialized => "ERR_NOT_INIT",
157            Self::AlreadyInitialized => "ERR_ALREADY_INIT",
158            Self::Unsupported(_) => "ERR_UNSUPPORTED",
159            Self::Timeout => "ERR_TIMEOUT",
160            Self::RealtimeViolation(_) => "ERR_RT_VIOLATION",
161            Self::Internal(_) => "ERR_INTERNAL",
162        }
163    }
164}
165
166// ============================================================================
167// Parameter Error
168// ============================================================================
169
170/// Errors that can occur during parameter operations
171#[derive(Error, Debug, Clone, PartialEq)]
172pub enum ParameterError {
173    /// Parameter name is empty
174    #[error("Parameter name cannot be empty")]
175    Empty,
176
177    /// Parameter name contains invalid character
178    #[error("Parameter name cannot contain '{0}'")]
179    InvalidCharacter(char),
180
181    /// Parameter name is too long
182    #[error("Parameter name too long (max {max} characters)")]
183    TooLong {
184        /// Maximum allowed length
185        max: usize,
186    },
187
188    /// Parameter name must start with a letter
189    #[error("Parameter name must start with a letter")]
190    MustStartWithLetter,
191
192    /// Parameter not found
193    #[error("Parameter '{0}' not found")]
194    NotFound(String),
195
196    /// Parameter type mismatch
197    #[error("Parameter type mismatch: expected {expected:?}, got {got:?}")]
198    TypeMismatch {
199        /// Expected parameter type
200        expected: crate::traits::ParamType,
201        /// Actual parameter type
202        got: crate::traits::ParamType,
203    },
204
205    /// Value out of range
206    #[error("Value {value} out of range [{min}, {max}]")]
207    OutOfRange {
208        /// The value that was out of range
209        value: f32,
210        /// Minimum allowed value
211        min: f32,
212        /// Maximum allowed value
213        max: f32,
214    },
215
216    /// Invalid choice (for Choice parameters)
217    #[error("Invalid choice '{0}'")]
218    InvalidChoice(String),
219
220    /// Duplicate parameter
221    #[error("Parameter '{0}' already exists")]
222    Duplicate(String),
223
224    /// Parameter is read-only
225    #[error("Parameter '{0}' is read-only")]
226    ReadOnly(String),
227}
228
229/// Result type for parameter operations
230pub type ParameterResult<T> = Result<T, ParameterError>;
231
232impl ParameterError {
233    /// Create a new not found error
234    pub fn not_found(name: impl Into<String>) -> Self {
235        Self::NotFound(name.into())
236    }
237
238    /// Create a new type mismatch error
239    pub fn type_mismatch(
240        expected: crate::traits::ParamType,
241        got: crate::traits::ParamType,
242    ) -> Self {
243        Self::TypeMismatch { expected, got }
244    }
245
246    /// Create a new out of range error
247    pub fn out_of_range(value: f32, min: f32, max: f32) -> Self {
248        Self::OutOfRange { value, min, max }
249    }
250
251    /// Create a new invalid choice error
252    pub fn invalid_choice(choice: impl Into<String>) -> Self {
253        Self::InvalidChoice(choice.into())
254    }
255
256    /// Create a new duplicate parameter error
257    pub fn duplicate(name: impl Into<String>) -> Self {
258        Self::Duplicate(name.into())
259    }
260
261    /// Create a new read-only error
262    pub fn read_only(name: impl Into<String>) -> Self {
263        Self::ReadOnly(name.into())
264    }
265}
266
267// ============================================================================
268// Clock Error
269// ============================================================================
270
271/// Errors that can occur during clock operations
272#[derive(Error, Debug, Clone, PartialEq)]
273pub enum ClockError {
274    /// Hardware error (ALSA, JACK, etc.)
275    #[error("Hardware error: {0}")]
276    Hardware(String),
277
278    /// Invalid sample rate
279    #[error("Invalid sample rate: {0}")]
280    InvalidSampleRate(f32),
281
282    /// Clock not started
283    #[error("Clock not started")]
284    NotStarted,
285
286    /// Clock already started
287    #[error("Clock already started")]
288    AlreadyStarted,
289
290    /// Clock underflow
291    #[error("Clock underflow")]
292    Underflow,
293
294    /// Clock overflow
295    #[error("Clock overflow")]
296    Overflow,
297}
298
299/// Result type for clock operations
300pub type ClockResult<T> = Result<T, ClockError>;
301
302// ============================================================================
303// Conversion Implementations
304// ============================================================================
305
306impl From<ParameterError> for ProcessError {
307    fn from(err: ParameterError) -> Self {
308        match err {
309            ParameterError::NotFound(name) => {
310                Self::parameter(format!("Parameter not found: {}", name))
311            }
312            ParameterError::TypeMismatch { expected, got } => {
313                Self::type_mismatch(expected.name(), got.name())
314            }
315            ParameterError::OutOfRange { value, min, max } => {
316                Self::parameter(format!("Value {} out of range [{}, {}]", value, min, max))
317            }
318            ParameterError::InvalidChoice(choice) => {
319                Self::parameter(format!("Invalid choice: {}", choice))
320            }
321            ParameterError::Duplicate(name) => {
322                Self::parameter(format!("Duplicate parameter: {}", name))
323            }
324            ParameterError::ReadOnly(name) => {
325                Self::parameter(format!("Parameter is read-only: {}", name))
326            }
327            _ => Self::parameter(err.to_string()),
328        }
329    }
330}
331
332impl From<ClockError> for ProcessError {
333    fn from(err: ClockError) -> Self {
334        match err {
335            ClockError::Hardware(msg) => Self::processing(format!("Hardware error: {}", msg)),
336            ClockError::InvalidSampleRate(sr) => {
337                Self::config(format!("Invalid sample rate: {}", sr))
338            }
339            ClockError::NotStarted => Self::processing("Clock not started"),
340            ClockError::AlreadyStarted => Self::processing("Clock already started"),
341            ClockError::Underflow => Self::buffer("Clock underflow"),
342            ClockError::Overflow => Self::buffer("Clock overflow"),
343        }
344    }
345}
346
347impl From<std::io::Error> for ProcessError {
348    fn from(err: std::io::Error) -> Self {
349        Self::Processing(format!("IO error: {}", err))
350    }
351}
352
353impl From<crate::error::Error> for ProcessError {
354    fn from(err: crate::error::Error) -> Self {
355        Self::Processing(err.to_string())
356    }
357}
358
359// ============================================================================
360// Tests
361// ============================================================================
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn test_process_error_creation() {
369        let err = ProcessError::processing("test error");
370        assert!(matches!(err, ProcessError::Processing(_)));
371        assert_eq!(err.code(), "ERR_PROCESSING");
372        assert!(err.is_recoverable());
373    }
374
375    #[test]
376    fn test_parameter_error_creation() {
377        let err = ParameterError::not_found("gain");
378        assert!(matches!(err, ParameterError::NotFound(_)));
379
380        let err = ParameterError::out_of_range(2.0, 0.0, 1.0);
381        assert!(matches!(err, ParameterError::OutOfRange { value: 2.0, .. }));
382    }
383
384    #[test]
385    fn test_error_conversions() {
386        let param_err = ParameterError::not_found("test");
387        let proc_err: ProcessError = param_err.into();
388        assert!(matches!(proc_err, ProcessError::Parameter(_)));
389
390        let clock_err = ClockError::Underflow;
391        let proc_err: ProcessError = clock_err.into();
392        assert!(matches!(proc_err, ProcessError::Buffer(_)));
393    }
394
395    #[test]
396    fn test_recoverable_flags() {
397        assert!(ProcessError::processing("test").is_recoverable());
398        assert!(ProcessError::parameter("test").is_recoverable());
399        assert!(ProcessError::buffer("test").is_recoverable());
400    }
401
402    #[test]
403    fn test_error_codes() {
404        assert_eq!(ProcessError::processing("").code(), "ERR_PROCESSING");
405    }
406
407    #[test]
408    fn test_parameter_error_details() {
409        let err = ParameterError::out_of_range(1.5, 0.0, 1.0);
410        match err {
411            ParameterError::OutOfRange { value, min, max } => {
412                assert_eq!(value, 1.5);
413                assert_eq!(min, 0.0);
414                assert_eq!(max, 1.0);
415            }
416            _ => panic!("Wrong error type"),
417        }
418    }
419}