Skip to main content

wavekat_core/
error.rs

1/// Unified error type for `wavekat-core`.
2///
3/// Covers all fallible operations in this crate (currently WAV I/O).
4/// Downstream crates can convert via `From<CoreError>` for ergonomic `?` usage.
5///
6/// # Variants
7///
8/// - [`Io`](CoreError::Io) — file or stream I/O failure
9/// - [`Audio`](CoreError::Audio) — format or codec error (e.g. invalid WAV)
10#[derive(Debug)]
11pub enum CoreError {
12    /// File or stream I/O failure.
13    Io(std::io::Error),
14    /// Audio format or codec error (e.g. unsupported WAV format).
15    Audio(String),
16}
17
18impl std::fmt::Display for CoreError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            CoreError::Io(e) => write!(f, "{e}"),
22            CoreError::Audio(msg) => write!(f, "{msg}"),
23        }
24    }
25}
26
27impl std::error::Error for CoreError {
28    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
29        match self {
30            CoreError::Io(e) => Some(e),
31            CoreError::Audio(_) => None,
32        }
33    }
34}
35
36impl From<std::io::Error> for CoreError {
37    fn from(e: std::io::Error) -> Self {
38        CoreError::Io(e)
39    }
40}
41
42#[cfg(feature = "wav")]
43impl From<hound::Error> for CoreError {
44    fn from(e: hound::Error) -> Self {
45        match e {
46            hound::Error::IoError(io) => CoreError::Io(io),
47            other => CoreError::Audio(other.to_string()),
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use std::error::Error;
56
57    #[test]
58    fn display_io() {
59        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
60        let err = CoreError::Io(io_err);
61        assert_eq!(err.to_string(), "file missing");
62    }
63
64    #[test]
65    fn display_audio() {
66        let err = CoreError::Audio("bad format".into());
67        assert_eq!(err.to_string(), "bad format");
68    }
69
70    #[test]
71    fn source_io_returns_inner() {
72        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
73        let err = CoreError::Io(io_err);
74        assert!(err.source().is_some());
75    }
76
77    #[test]
78    fn source_audio_returns_none() {
79        let err = CoreError::Audio("oops".into());
80        assert!(err.source().is_none());
81    }
82
83    #[test]
84    fn from_io_error() {
85        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
86        let err = CoreError::from(io_err);
87        assert!(matches!(err, CoreError::Io(_)));
88        assert_eq!(err.to_string(), "denied");
89    }
90
91    #[cfg(feature = "wav")]
92    #[test]
93    fn from_hound_io_error() {
94        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
95        let hound_err = hound::Error::IoError(io_err);
96        let err = CoreError::from(hound_err);
97        assert!(matches!(err, CoreError::Io(_)));
98        assert_eq!(err.to_string(), "not found");
99    }
100
101    #[cfg(feature = "wav")]
102    #[test]
103    fn from_hound_format_error() {
104        let hound_err = hound::Error::Unsupported;
105        let err = CoreError::from(hound_err);
106        assert!(matches!(err, CoreError::Audio(_)));
107    }
108}