use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq)]
pub enum ProcessError {
#[error("Processing error: {0}")]
Processing(String),
#[error("Parameter error: {0}")]
Parameter(String),
#[error("Buffer error: {0}")]
Buffer(String),
#[error("Type mismatch: expected {expected}, got {got}")]
TypeMismatch {
expected: &'static str,
got: &'static str,
},
#[error("Sample rate mismatch: expected {expected}, got {got}")]
SampleRateMismatch {
expected: f32,
got: f32,
},
#[error("Configuration error: {0}")]
Config(String),
#[error("Not initialized")]
NotInitialized,
#[error("Already initialized")]
AlreadyInitialized,
#[error("Unsupported operation: {0}")]
Unsupported(String),
#[error("Operation timed out")]
Timeout,
#[error("Realtime violation: {0}")]
RealtimeViolation(String),
#[error("Internal error: {0}")]
Internal(String),
}
pub type ProcessResult<T> = Result<T, ProcessError>;
impl ProcessError {
pub fn processing(msg: impl Into<String>) -> Self {
Self::Processing(msg.into())
}
pub fn parameter(msg: impl Into<String>) -> Self {
Self::Parameter(msg.into())
}
pub fn buffer(msg: impl Into<String>) -> Self {
Self::Buffer(msg.into())
}
pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
Self::TypeMismatch { expected, got }
}
pub fn sample_rate_mismatch(expected: f32, got: f32) -> Self {
Self::SampleRateMismatch { expected, got }
}
pub fn config(msg: impl Into<String>) -> Self {
Self::Config(msg.into())
}
pub fn unsupported(msg: impl Into<String>) -> Self {
Self::Unsupported(msg.into())
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::Internal(msg.into())
}
pub fn is_recoverable(&self) -> bool {
match self {
Self::Processing(_) => true,
Self::Parameter(_) => true,
Self::Buffer(_) => true,
Self::TypeMismatch { .. } => false,
Self::SampleRateMismatch { .. } => false,
Self::Config(_) => false,
Self::NotInitialized => true,
Self::AlreadyInitialized => true,
Self::Unsupported(_) => false,
Self::Timeout => true,
Self::RealtimeViolation(_) => false,
Self::Internal(_) => false,
}
}
pub fn code(&self) -> &'static str {
match self {
Self::Processing(_) => "ERR_PROCESSING",
Self::Parameter(_) => "ERR_PARAMETER",
Self::Buffer(_) => "ERR_BUFFER",
Self::TypeMismatch { .. } => "ERR_TYPE_MISMATCH",
Self::SampleRateMismatch { .. } => "ERR_SAMPLE_RATE",
Self::Config(_) => "ERR_CONFIG",
Self::NotInitialized => "ERR_NOT_INIT",
Self::AlreadyInitialized => "ERR_ALREADY_INIT",
Self::Unsupported(_) => "ERR_UNSUPPORTED",
Self::Timeout => "ERR_TIMEOUT",
Self::RealtimeViolation(_) => "ERR_RT_VIOLATION",
Self::Internal(_) => "ERR_INTERNAL",
}
}
}
#[derive(Error, Debug, Clone, PartialEq)]
pub enum ParameterError {
#[error("Parameter name cannot be empty")]
Empty,
#[error("Parameter name cannot contain '{0}'")]
InvalidCharacter(char),
#[error("Parameter name too long (max {max} characters)")]
TooLong {
max: usize,
},
#[error("Parameter name must start with a letter")]
MustStartWithLetter,
#[error("Parameter '{0}' not found")]
NotFound(String),
#[error("Parameter type mismatch: expected {expected:?}, got {got:?}")]
TypeMismatch {
expected: crate::traits::ParamType,
got: crate::traits::ParamType,
},
#[error("Value {value} out of range [{min}, {max}]")]
OutOfRange {
value: f32,
min: f32,
max: f32,
},
#[error("Invalid choice '{0}'")]
InvalidChoice(String),
#[error("Parameter '{0}' already exists")]
Duplicate(String),
#[error("Parameter '{0}' is read-only")]
ReadOnly(String),
}
pub type ParameterResult<T> = Result<T, ParameterError>;
impl ParameterError {
pub fn not_found(name: impl Into<String>) -> Self {
Self::NotFound(name.into())
}
pub fn type_mismatch(
expected: crate::traits::ParamType,
got: crate::traits::ParamType,
) -> Self {
Self::TypeMismatch { expected, got }
}
pub fn out_of_range(value: f32, min: f32, max: f32) -> Self {
Self::OutOfRange { value, min, max }
}
pub fn invalid_choice(choice: impl Into<String>) -> Self {
Self::InvalidChoice(choice.into())
}
pub fn duplicate(name: impl Into<String>) -> Self {
Self::Duplicate(name.into())
}
pub fn read_only(name: impl Into<String>) -> Self {
Self::ReadOnly(name.into())
}
}
#[derive(Error, Debug, Clone, PartialEq)]
pub enum ClockError {
#[error("Hardware error: {0}")]
Hardware(String),
#[error("Invalid sample rate: {0}")]
InvalidSampleRate(f32),
#[error("Clock not started")]
NotStarted,
#[error("Clock already started")]
AlreadyStarted,
#[error("Clock underflow")]
Underflow,
#[error("Clock overflow")]
Overflow,
}
pub type ClockResult<T> = Result<T, ClockError>;
impl From<ParameterError> for ProcessError {
fn from(err: ParameterError) -> Self {
match err {
ParameterError::NotFound(name) => {
Self::parameter(format!("Parameter not found: {}", name))
}
ParameterError::TypeMismatch { expected, got } => {
Self::type_mismatch(expected.name(), got.name())
}
ParameterError::OutOfRange { value, min, max } => {
Self::parameter(format!("Value {} out of range [{}, {}]", value, min, max))
}
ParameterError::InvalidChoice(choice) => {
Self::parameter(format!("Invalid choice: {}", choice))
}
ParameterError::Duplicate(name) => {
Self::parameter(format!("Duplicate parameter: {}", name))
}
ParameterError::ReadOnly(name) => {
Self::parameter(format!("Parameter is read-only: {}", name))
}
_ => Self::parameter(err.to_string()),
}
}
}
impl From<ClockError> for ProcessError {
fn from(err: ClockError) -> Self {
match err {
ClockError::Hardware(msg) => Self::processing(format!("Hardware error: {}", msg)),
ClockError::InvalidSampleRate(sr) => {
Self::config(format!("Invalid sample rate: {}", sr))
}
ClockError::NotStarted => Self::processing("Clock not started"),
ClockError::AlreadyStarted => Self::processing("Clock already started"),
ClockError::Underflow => Self::buffer("Clock underflow"),
ClockError::Overflow => Self::buffer("Clock overflow"),
}
}
}
impl From<std::io::Error> for ProcessError {
fn from(err: std::io::Error) -> Self {
Self::Processing(format!("IO error: {}", err))
}
}
impl From<crate::error::Error> for ProcessError {
fn from(err: crate::error::Error) -> Self {
Self::Processing(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_error_creation() {
let err = ProcessError::processing("test error");
assert!(matches!(err, ProcessError::Processing(_)));
assert_eq!(err.code(), "ERR_PROCESSING");
assert!(err.is_recoverable());
}
#[test]
fn test_parameter_error_creation() {
let err = ParameterError::not_found("gain");
assert!(matches!(err, ParameterError::NotFound(_)));
let err = ParameterError::out_of_range(2.0, 0.0, 1.0);
assert!(matches!(err, ParameterError::OutOfRange { value: 2.0, .. }));
}
#[test]
fn test_error_conversions() {
let param_err = ParameterError::not_found("test");
let proc_err: ProcessError = param_err.into();
assert!(matches!(proc_err, ProcessError::Parameter(_)));
let clock_err = ClockError::Underflow;
let proc_err: ProcessError = clock_err.into();
assert!(matches!(proc_err, ProcessError::Buffer(_)));
}
#[test]
fn test_recoverable_flags() {
assert!(ProcessError::processing("test").is_recoverable());
assert!(ProcessError::parameter("test").is_recoverable());
assert!(ProcessError::buffer("test").is_recoverable());
}
#[test]
fn test_error_codes() {
assert_eq!(ProcessError::processing("").code(), "ERR_PROCESSING");
}
#[test]
fn test_parameter_error_details() {
let err = ParameterError::out_of_range(1.5, 0.0, 1.0);
match err {
ParameterError::OutOfRange { value, min, max } => {
assert_eq!(value, 1.5);
assert_eq!(min, 0.0);
assert_eq!(max, 1.0);
}
_ => panic!("Wrong error type"),
}
}
}