Skip to main content

audio_resample_bsd/
error.rs

1//! Resampling error types and the [`Result`] alias.
2//!
3//! [`ResampleError`] is the set of classifiable errors that a [`Resampler`]
4//! implementation reports during processing. rubato runtime errors are wrapped
5//! as a string in [`ResampleError::ResampleFailed`] to avoid the burden of
6//! matching rubato's `#[non_exhaustive]` error enum.
7
8use thiserror::Error;
9
10/// Errors that can arise while validating or resampling audio data.
11#[derive(Debug, Clone, PartialEq, Error)]
12pub enum ResampleError {
13    /// The sample rate is not a positive, finite, supported value.
14    #[error("invalid sample rate: {rate} Hz")]
15    InvalidSampleRate {
16        /// The rejected sample rate (Hz).
17        rate: f64,
18    },
19
20    /// The channel count is zero or otherwise unsupported.
21    #[error("invalid channel count: {0}")]
22    InvalidChannelCount(usize),
23
24    /// The fixed chunk size is zero or otherwise unsupported.
25    #[error("invalid chunk size: {0}")]
26    InvalidChunkSize(usize),
27
28    /// The destination buffer was smaller than required.
29    #[error("buffer too small: needed {needed} samples, have {have}")]
30    BufferTooSmall {
31        /// Required number of samples.
32        needed: usize,
33        /// Samples actually available.
34        have: usize,
35    },
36
37    /// The input length did not match the implementation's fixed chunk ×
38    /// channels.
39    #[error("input length mismatch: expected {expected} samples, got {actual}")]
40    InputLengthMismatch {
41        /// Expected input sample count (`chunk × channels`).
42        expected: usize,
43        /// Actual input sample count.
44        actual: usize,
45    },
46
47    /// A rubato runtime error. The original message is wrapped as a string to
48    /// avoid matching rubato's non-exhaustive error variants.
49    #[error("resample failed: {0}")]
50    ResampleFailed(String),
51
52    /// Forwarded `audio-core-bsd` error.
53    #[error(transparent)]
54    Core(#[from] audio_core_bsd::AudioError),
55}
56
57/// Convenience alias used by consumers of this crate.
58pub type Result<T> = core::result::Result<T, ResampleError>;
59
60#[cfg(test)]
61mod tests {
62    use super::{ResampleError, Result};
63
64    #[test]
65    fn invalid_sample_rate_display_includes_value() {
66        let msg = ResampleError::InvalidSampleRate { rate: 0.0 }.to_string();
67        assert_eq!(msg, "invalid sample rate: 0 Hz");
68    }
69
70    #[test]
71    fn invalid_channel_count_display_includes_value() {
72        let msg = ResampleError::InvalidChannelCount(0).to_string();
73        assert_eq!(msg, "invalid channel count: 0");
74    }
75
76    #[test]
77    fn invalid_chunk_size_display_includes_value() {
78        let msg = ResampleError::InvalidChunkSize(0).to_string();
79        assert_eq!(msg, "invalid chunk size: 0");
80    }
81
82    #[test]
83    fn buffer_too_small_display_includes_both_counts() {
84        let msg = ResampleError::BufferTooSmall {
85            needed: 100,
86            have: 50,
87        }
88        .to_string();
89        assert_eq!(msg, "buffer too small: needed 100 samples, have 50");
90    }
91
92    #[test]
93    fn input_length_mismatch_display_includes_both_counts() {
94        let msg = ResampleError::InputLengthMismatch {
95            expected: 1024,
96            actual: 512,
97        }
98        .to_string();
99        assert_eq!(msg, "input length mismatch: expected 1024 samples, got 512",);
100    }
101
102    #[test]
103    fn resample_failed_display_wraps_message() {
104        let msg = ResampleError::ResampleFailed("rubato internal".to_string()).to_string();
105        assert_eq!(msg, "resample failed: rubato internal");
106    }
107
108    #[test]
109    fn core_variant_derives_from_audio_error() {
110        let core_err = audio_core_bsd::AudioError::InvalidChannelCount(0);
111        let resampled: ResampleError = core_err.into();
112        let msg = resampled.to_string();
113        // audio-core-bsd's InvalidChannelCount(0) message is forwarded via
114        // `#[error(transparent)]`.
115        assert_eq!(msg, "invalid channel count: 0");
116    }
117
118    #[test]
119    fn result_alias_ok_carries_value() {
120        let ok: Result<u32> = Ok(42);
121        assert_eq!(ok.map(|v| v + 1), Ok(43));
122    }
123
124    #[test]
125    fn result_alias_err_carries_resample_error() {
126        let err: Result<u32> = Err(ResampleError::InvalidChannelCount(0));
127        assert_eq!(err.err(), Some(ResampleError::InvalidChannelCount(0)));
128    }
129
130    #[test]
131    fn clone_and_partial_eq_hold_across_variants() {
132        let a = ResampleError::BufferTooSmall { needed: 2, have: 1 };
133        assert_eq!(a.clone(), a);
134        // Inequality across variants (both wrap the integer `1`).
135        assert_ne!(
136            ResampleError::InvalidChannelCount(1),
137            ResampleError::InvalidChunkSize(1),
138        );
139    }
140
141    #[test]
142    fn resample_failed_clone_preserves_message() {
143        let a = ResampleError::ResampleFailed("boom".to_string());
144        let b = a.clone();
145        assert_eq!(a, b);
146    }
147}