use serde::{Deserialize, Serialize};
use tracing::warn;
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[non_exhaustive]
pub enum NaadError {
#[error("invalid frequency {frequency}: must be > 0 and < {nyquist} (Nyquist)")]
InvalidFrequency {
frequency: f32,
nyquist: f32,
},
#[error("invalid sample rate {sample_rate}: must be > 0")]
InvalidSampleRate {
sample_rate: f32,
},
#[error("invalid parameter '{name}': {reason}")]
InvalidParameter {
name: String,
reason: String,
},
#[error("buffer overflow: attempted {attempted} but capacity is {capacity}")]
BufferOverflow {
attempted: usize,
capacity: usize,
},
#[error("computation error: {message}")]
ComputationError {
message: String,
},
}
pub type Result<T> = std::result::Result<T, NaadError>;
#[must_use]
pub(crate) fn validate_frequency(frequency: f32, sample_rate: f32) -> Option<NaadError> {
let nyquist = sample_rate / 2.0;
if frequency <= 0.0 || frequency >= nyquist || !frequency.is_finite() {
warn!(frequency, nyquist, "invalid frequency");
Some(NaadError::InvalidFrequency { frequency, nyquist })
} else {
None
}
}
#[must_use]
pub(crate) fn validate_sample_rate(sample_rate: f32) -> Option<NaadError> {
if sample_rate <= 0.0 || !sample_rate.is_finite() {
warn!(sample_rate, "invalid sample rate");
Some(NaadError::InvalidSampleRate { sample_rate })
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = NaadError::InvalidFrequency {
frequency: -1.0,
nyquist: 22050.0,
};
assert!(err.to_string().contains("-1"));
}
#[test]
fn test_error_display_all_variants() {
let cases: Vec<(NaadError, &str)> = vec![
(
NaadError::InvalidFrequency {
frequency: -1.0,
nyquist: 22050.0,
},
"invalid frequency",
),
(
NaadError::InvalidSampleRate { sample_rate: 0.0 },
"invalid sample rate",
),
(
NaadError::InvalidParameter {
name: "q".into(),
reason: "must be > 0".into(),
},
"invalid parameter",
),
(
NaadError::BufferOverflow {
attempted: 2048,
capacity: 1024,
},
"buffer overflow",
),
(
NaadError::ComputationError {
message: "division by zero".into(),
},
"computation error",
),
];
for (err, expected_prefix) in &cases {
let msg = err.to_string();
assert!(
msg.contains(expected_prefix),
"error '{msg}' should contain '{expected_prefix}'"
);
}
}
#[test]
fn test_serde_roundtrip() {
let err = NaadError::InvalidParameter {
name: "q".to_string(),
reason: "must be > 0".to_string(),
};
let json = serde_json::to_string(&err).unwrap();
let back: NaadError = serde_json::from_str(&json).unwrap();
assert_eq!(err.to_string(), back.to_string());
}
#[test]
fn test_serde_roundtrip_all_variants() {
let errors = vec![
NaadError::InvalidFrequency {
frequency: 50000.0,
nyquist: 22050.0,
},
NaadError::InvalidSampleRate { sample_rate: -1.0 },
NaadError::BufferOverflow {
attempted: 100,
capacity: 50,
},
NaadError::ComputationError {
message: "test".into(),
},
];
for err in &errors {
let json = serde_json::to_string(err).unwrap();
let back: NaadError = serde_json::from_str(&json).unwrap();
assert_eq!(err.to_string(), back.to_string());
}
}
#[test]
fn test_validate_frequency() {
assert!(validate_frequency(440.0, 44100.0).is_none());
assert!(validate_frequency(0.0, 44100.0).is_some());
assert!(validate_frequency(-1.0, 44100.0).is_some());
assert!(validate_frequency(23000.0, 44100.0).is_some());
assert!(validate_frequency(f32::NAN, 44100.0).is_some());
}
#[test]
fn test_validate_sample_rate() {
assert!(validate_sample_rate(44100.0).is_none());
assert!(validate_sample_rate(0.0).is_some());
assert!(validate_sample_rate(-1.0).is_some());
assert!(validate_sample_rate(f32::NAN).is_some());
assert!(validate_sample_rate(f32::INFINITY).is_some());
}
}