Skip to main content

resonant_analysis/
error.rs

1//! Error types for audio analysis operations.
2
3use core::fmt;
4
5/// Errors that can occur during audio analysis.
6///
7/// Implements [`serde::Serialize`] (but not `Deserialize`) when the `serde`
8/// feature is enabled. The `InvalidParameter` variant stores `&'static str`
9/// fields which cannot be reconstructed from a deserialised `String`.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12pub enum AnalysisError {
13    /// The input buffer was empty.
14    EmptyInput,
15    /// A parameter had an invalid value.
16    InvalidParameter {
17        /// Which parameter was invalid.
18        name: &'static str,
19        /// Why it was invalid.
20        reason: &'static str,
21    },
22    /// An FFT operation failed.
23    Fft(resonant_fft::FftError),
24}
25
26impl fmt::Display for AnalysisError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::EmptyInput => write!(f, "input is empty"),
30            Self::InvalidParameter { name, reason } => {
31                write!(f, "invalid parameter `{name}`: {reason}")
32            }
33            Self::Fft(e) => write!(f, "FFT error: {e}"),
34        }
35    }
36}
37
38impl From<resonant_fft::FftError> for AnalysisError {
39    fn from(e: resonant_fft::FftError) -> Self {
40        Self::Fft(e)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn display_empty_input() {
50        let e = AnalysisError::EmptyInput;
51        assert_eq!(e.to_string(), "input is empty");
52    }
53
54    #[test]
55    fn display_invalid_parameter() {
56        let e = AnalysisError::InvalidParameter {
57            name: "window_size",
58            reason: "must be positive",
59        };
60        assert_eq!(
61            e.to_string(),
62            "invalid parameter `window_size`: must be positive"
63        );
64    }
65
66    #[test]
67    fn display_fft_error() {
68        let e = AnalysisError::Fft(resonant_fft::FftError::Empty);
69        assert_eq!(e.to_string(), "FFT error: FFT input is empty");
70    }
71
72    #[test]
73    fn from_fft_error() {
74        let fft_err = resonant_fft::FftError::NotPowerOfTwo(3);
75        let e: AnalysisError = fft_err.into();
76        assert_eq!(
77            e,
78            AnalysisError::Fft(resonant_fft::FftError::NotPowerOfTwo(3))
79        );
80    }
81
82    #[test]
83    fn clone_and_eq() {
84        let e = AnalysisError::EmptyInput;
85        assert_eq!(e.clone(), e);
86    }
87
88    #[cfg(feature = "serde")]
89    #[test]
90    fn analysis_error_serialize_to_json() {
91        let cases = [
92            AnalysisError::EmptyInput,
93            AnalysisError::InvalidParameter {
94                name: "window_size",
95                reason: "must be positive",
96            },
97            AnalysisError::Fft(resonant_fft::FftError::Empty),
98        ];
99        for e in &cases {
100            let json = serde_json::to_string(e)
101                .unwrap_or_else(|err| panic!("serialize AnalysisError: {err}"));
102            assert!(!json.is_empty());
103        }
104    }
105}