1#![allow(missing_docs)]
8
9use std::fmt;
10use thiserror::Error;
11
12pub type Result<T> = std::result::Result<T, CodecError>;
14
15#[derive(Error, Debug)]
17pub enum CodecError {
18 #[error("Invalid codec configuration: {details}")]
20 InvalidConfig { details: String },
21
22 #[error("Unsupported codec type: {codec_type}")]
24 UnsupportedCodec { codec_type: String },
25
26 #[error("Invalid audio format: {details}")]
28 InvalidFormat { details: String },
29
30 #[error("Invalid frame size: expected {expected}, got {actual}")]
32 InvalidFrameSize { expected: usize, actual: usize },
33
34 #[error("Invalid sample rate: {rate}Hz (supported: {supported:?})")]
36 InvalidSampleRate { rate: u32, supported: Vec<u32> },
37
38 #[error("Invalid channel count: {channels} (supported: {supported:?})")]
40 InvalidChannelCount { channels: u8, supported: Vec<u8> },
41
42 #[error("Invalid bitrate: {bitrate}bps (range: {min}-{max})")]
44 InvalidBitrate { bitrate: u32, min: u32, max: u32 },
45
46 #[error("Encoding failed: {reason}")]
48 EncodingFailed { reason: String },
49
50 #[error("Decoding failed: {reason}")]
52 DecodingFailed { reason: String },
53
54 #[error("Buffer too small: need {needed} bytes, got {actual}")]
56 BufferTooSmall { needed: usize, actual: usize },
57
58 #[error("Buffer overflow: attempted to write {size} bytes to {capacity} byte buffer")]
60 BufferOverflow { size: usize, capacity: usize },
61
62 #[error("Codec initialization failed: {reason}")]
64 InitializationFailed { reason: String },
65
66 #[error("Codec reset failed: {reason}")]
68 ResetFailed { reason: String },
69
70 #[error("Invalid payload data: {details}")]
72 InvalidPayload { details: String },
73
74 #[error("Codec not found: {name}")]
76 CodecNotFound { name: String },
77
78 #[error("Feature not enabled: {feature} (enable with --features {feature})")]
80 FeatureNotEnabled { feature: String },
81
82 #[error("SIMD operation failed: {reason}")]
84 SimdFailed { reason: String },
85
86 #[error("Math operation failed: {operation} - {reason}")]
88 MathError { operation: String, reason: String },
89
90 #[error("I/O operation failed: {reason}")]
92 IoError { reason: String },
93
94 #[error("External library error: {library} - {error}")]
96 ExternalLibraryError { library: String, error: String },
97
98 #[error("Internal error: {message} (this is a bug, please report it)")]
100 InternalError { message: String },
101}
102
103impl CodecError {
104 pub fn invalid_config(details: impl Into<String>) -> Self {
106 Self::InvalidConfig {
107 details: details.into(),
108 }
109 }
110
111 pub fn unsupported_codec(codec_type: impl Into<String>) -> Self {
113 Self::UnsupportedCodec {
114 codec_type: codec_type.into(),
115 }
116 }
117
118 pub fn invalid_format(details: impl Into<String>) -> Self {
120 Self::InvalidFormat {
121 details: details.into(),
122 }
123 }
124
125 pub fn encoding_failed(reason: impl Into<String>) -> Self {
127 Self::EncodingFailed {
128 reason: reason.into(),
129 }
130 }
131
132 pub fn decoding_failed(reason: impl Into<String>) -> Self {
134 Self::DecodingFailed {
135 reason: reason.into(),
136 }
137 }
138
139 pub fn initialization_failed(reason: impl Into<String>) -> Self {
141 Self::InitializationFailed {
142 reason: reason.into(),
143 }
144 }
145
146 pub fn feature_not_enabled(feature: impl Into<String>) -> Self {
148 Self::FeatureNotEnabled {
149 feature: feature.into(),
150 }
151 }
152
153 pub fn internal_error(message: impl Into<String>) -> Self {
155 Self::InternalError {
156 message: message.into(),
157 }
158 }
159
160 #[must_use]
162 pub const fn is_recoverable(&self) -> bool {
163 match self {
164 Self::InvalidConfig { .. }
166 | Self::UnsupportedCodec { .. }
167 | Self::InvalidFormat { .. }
168 | Self::InvalidSampleRate { .. }
169 | Self::InvalidChannelCount { .. }
170 | Self::InvalidBitrate { .. }
171 | Self::FeatureNotEnabled { .. }
172 | Self::CodecNotFound { .. }
173 | Self::InternalError { .. }
174 | Self::InitializationFailed { .. }
175 | Self::ResetFailed { .. } => false,
176
177 Self::InvalidFrameSize { .. }
179 | Self::EncodingFailed { .. }
180 | Self::DecodingFailed { .. }
181 | Self::BufferTooSmall { .. }
182 | Self::BufferOverflow { .. }
183 | Self::InvalidPayload { .. }
184 | Self::SimdFailed { .. }
185 | Self::MathError { .. }
186 | Self::IoError { .. }
187 | Self::ExternalLibraryError { .. } => true,
188 }
189 }
190
191 #[must_use]
193 pub const fn category(&self) -> ErrorCategory {
194 match self {
195 Self::InvalidConfig { .. }
196 | Self::UnsupportedCodec { .. }
197 | Self::InvalidFormat { .. }
198 | Self::InvalidSampleRate { .. }
199 | Self::InvalidChannelCount { .. }
200 | Self::InvalidBitrate { .. }
201 | Self::FeatureNotEnabled { .. }
202 | Self::CodecNotFound { .. } => ErrorCategory::Configuration,
203
204 Self::EncodingFailed { .. }
205 | Self::DecodingFailed { .. }
206 | Self::InvalidFrameSize { .. }
207 | Self::InvalidPayload { .. } => ErrorCategory::Processing,
208
209 Self::BufferTooSmall { .. } | Self::BufferOverflow { .. } => ErrorCategory::Memory,
210
211 Self::InitializationFailed { .. } | Self::ResetFailed { .. } => {
212 ErrorCategory::Initialization
213 }
214
215 Self::SimdFailed { .. } | Self::MathError { .. } => ErrorCategory::Computation,
216
217 Self::IoError { .. } => ErrorCategory::Io,
218
219 Self::ExternalLibraryError { .. } => ErrorCategory::External,
220
221 Self::InternalError { .. } => ErrorCategory::Internal,
222 }
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum ErrorCategory {
229 Configuration,
231 Processing,
233 Memory,
235 Initialization,
237 Computation,
239 Io,
241 External,
243 Internal,
245}
246
247impl fmt::Display for ErrorCategory {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 match self {
250 Self::Configuration => write!(f, "Configuration"),
251 Self::Processing => write!(f, "Processing"),
252 Self::Memory => write!(f, "Memory"),
253 Self::Initialization => write!(f, "Initialization"),
254 Self::Computation => write!(f, "Computation"),
255 Self::Io => write!(f, "I/O"),
256 Self::External => write!(f, "External"),
257 Self::Internal => write!(f, "Internal"),
258 }
259 }
260}
261
262impl From<std::io::Error> for CodecError {
264 fn from(error: std::io::Error) -> Self {
265 Self::IoError {
266 reason: error.to_string(),
267 }
268 }
269}
270
271impl From<std::num::ParseIntError> for CodecError {
273 fn from(error: std::num::ParseIntError) -> Self {
274 Self::MathError {
275 operation: "parse_int".to_string(),
276 reason: error.to_string(),
277 }
278 }
279}
280
281impl From<std::num::ParseFloatError> for CodecError {
283 fn from(error: std::num::ParseFloatError) -> Self {
284 Self::MathError {
285 operation: "parse_float".to_string(),
286 reason: error.to_string(),
287 }
288 }
289}
290
291impl From<&str> for CodecError {
292 fn from(s: &str) -> Self {
293 Self::InvalidConfig {
294 details: s.to_string(),
295 }
296 }
297}
298
299impl From<String> for CodecError {
300 fn from(s: String) -> Self {
301 Self::InvalidConfig { details: s }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn test_error_creation() {
311 let err = CodecError::invalid_config("test message");
312 assert!(matches!(err, CodecError::InvalidConfig { .. }));
313 assert_eq!(err.category(), ErrorCategory::Configuration);
314 }
315
316 #[test]
317 fn test_error_recoverability() {
318 let recoverable = CodecError::EncodingFailed {
319 reason: "test".to_string(),
320 };
321 assert!(recoverable.is_recoverable());
322
323 let non_recoverable = CodecError::InvalidConfig {
324 details: "test".to_string(),
325 };
326 assert!(!non_recoverable.is_recoverable());
327 }
328
329 #[test]
330 fn test_error_categories() {
331 assert_eq!(
332 CodecError::invalid_config("test").category(),
333 ErrorCategory::Configuration
334 );
335 assert_eq!(
336 CodecError::encoding_failed("test").category(),
337 ErrorCategory::Processing
338 );
339 assert_eq!(
340 CodecError::BufferTooSmall {
341 needed: 100,
342 actual: 50
343 }
344 .category(),
345 ErrorCategory::Memory
346 );
347 }
348
349 #[test]
350 fn test_error_display() {
351 let err = CodecError::InvalidFrameSize {
352 expected: 160,
353 actual: 80,
354 };
355 let display = format!("{err}");
356 assert!(display.contains("expected 160"));
357 assert!(display.contains("got 80"));
358 }
359
360 #[test]
361 fn test_error_conversion() {
362 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
363 let codec_err: CodecError = io_err.into();
364 assert!(matches!(codec_err, CodecError::IoError { .. }));
365 }
366}