Skip to main content

oximedia_core/
error.rs

1//! Error types for `OxiMedia`.
2//!
3//! This module provides the [`OxiError`] type which represents all errors
4//! that can occur during multimedia processing, and the [`OxiResult`] type
5//! alias for convenient use.
6//!
7//! # Patent Protection
8//!
9//! The [`PatentViolation`](OxiError::PatentViolation) error is returned when
10//! attempting to use patent-encumbered codecs (H.264, H.265, AAC, etc.).
11//! `OxiMedia` only supports Green List codecs.
12
13use std::io;
14
15/// Error type for `OxiMedia` operations.
16///
17/// This enum covers all possible errors that can occur during multimedia
18/// processing, including I/O errors, parsing errors, codec errors, and
19/// patent violations.
20///
21/// # Examples
22///
23/// ```
24/// use oximedia_core::error::{OxiError, OxiResult};
25///
26/// fn parse_data(data: &[u8]) -> OxiResult<()> {
27///     if data.is_empty() {
28///         return Err(OxiError::Parse {
29///             offset: 0,
30///             message: "Empty data".to_string(),
31///         });
32///     }
33///     Ok(())
34/// }
35/// ```
36#[derive(Debug, thiserror::Error)]
37pub enum OxiError {
38    /// I/O error during file or stream operations.
39    #[error("I/O error: {0}")]
40    Io(#[from] io::Error),
41
42    /// Parse error at a specific offset in the data.
43    #[error("Parse error at offset {offset}: {message}")]
44    Parse {
45        /// Byte offset where the error occurred.
46        offset: u64,
47        /// Description of the parse error.
48        message: String,
49    },
50
51    /// Codec-related error.
52    #[error("Codec error: {0}")]
53    Codec(String),
54
55    /// Unsupported format or feature.
56    #[error("Unsupported format: {0}")]
57    Unsupported(String),
58
59    /// Attempted to use a patent-encumbered codec.
60    ///
61    /// `OxiMedia` only supports patent-free (Green List) codecs.
62    /// This error is returned when attempting to use codecs like
63    /// H.264, H.265, AAC, etc.
64    #[error("Patent-encumbered codec detected: {0}")]
65    PatentViolation(String),
66
67    /// End of stream reached.
68    #[error("End of stream")]
69    Eof,
70
71    /// Buffer is too small for the requested operation.
72    #[error("Buffer too small: need {needed}, have {have}")]
73    BufferTooSmall {
74        /// Required buffer size in bytes.
75        needed: usize,
76        /// Available buffer size in bytes.
77        have: usize,
78    },
79
80    /// Unexpected end of file during read operation.
81    ///
82    /// This is returned when attempting to read beyond the end of available data.
83    #[error("Unexpected end of file")]
84    UnexpectedEof,
85
86    /// Invalid data encountered during parsing.
87    #[error("Invalid data: {0}")]
88    InvalidData(String),
89
90    /// Format could not be recognized.
91    #[error("Unknown format")]
92    UnknownFormat,
93}
94
95impl OxiError {
96    /// Creates a new parse error at the given offset.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// use oximedia_core::error::OxiError;
102    ///
103    /// let err = OxiError::parse(42, "Invalid header");
104    /// assert!(matches!(err, OxiError::Parse { offset: 42, .. }));
105    /// ```
106    #[must_use]
107    pub fn parse(offset: u64, message: impl Into<String>) -> Self {
108        Self::Parse {
109            offset,
110            message: message.into(),
111        }
112    }
113
114    /// Creates a new codec error.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use oximedia_core::error::OxiError;
120    ///
121    /// let err = OxiError::codec("Invalid frame data");
122    /// assert!(matches!(err, OxiError::Codec(_)));
123    /// ```
124    #[must_use]
125    pub fn codec(message: impl Into<String>) -> Self {
126        Self::Codec(message.into())
127    }
128
129    /// Creates a new unsupported format error.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use oximedia_core::error::OxiError;
135    ///
136    /// let err = OxiError::unsupported("H.265 is not supported");
137    /// assert!(matches!(err, OxiError::Unsupported(_)));
138    /// ```
139    #[must_use]
140    pub fn unsupported(message: impl Into<String>) -> Self {
141        Self::Unsupported(message.into())
142    }
143
144    /// Creates a new patent violation error.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use oximedia_core::error::OxiError;
150    ///
151    /// let err = OxiError::patent_violation("H.264");
152    /// assert!(matches!(err, OxiError::PatentViolation(_)));
153    /// ```
154    #[must_use]
155    pub fn patent_violation(codec_name: impl Into<String>) -> Self {
156        Self::PatentViolation(codec_name.into())
157    }
158
159    /// Creates a buffer too small error.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use oximedia_core::error::OxiError;
165    ///
166    /// let err = OxiError::buffer_too_small(1024, 512);
167    /// assert!(matches!(err, OxiError::BufferTooSmall { needed: 1024, have: 512 }));
168    /// ```
169    #[must_use]
170    pub fn buffer_too_small(needed: usize, have: usize) -> Self {
171        Self::BufferTooSmall { needed, have }
172    }
173
174    /// Returns true if this is an end-of-stream error.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use oximedia_core::error::OxiError;
180    ///
181    /// assert!(OxiError::Eof.is_eof());
182    /// assert!(!OxiError::codec("test").is_eof());
183    /// ```
184    #[must_use]
185    pub const fn is_eof(&self) -> bool {
186        matches!(self, Self::Eof)
187    }
188
189    /// Returns true if this is a patent violation error.
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// use oximedia_core::error::OxiError;
195    ///
196    /// assert!(OxiError::patent_violation("H.264").is_patent_violation());
197    /// assert!(!OxiError::Eof.is_patent_violation());
198    /// ```
199    #[must_use]
200    pub const fn is_patent_violation(&self) -> bool {
201        matches!(self, Self::PatentViolation(_))
202    }
203
204    /// Creates an invalid data error.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use oximedia_core::error::OxiError;
210    ///
211    /// let err = OxiError::invalid_data("Malformed header");
212    /// assert!(matches!(err, OxiError::InvalidData(_)));
213    /// ```
214    #[must_use]
215    pub fn invalid_data(message: impl Into<String>) -> Self {
216        Self::InvalidData(message.into())
217    }
218
219    /// Returns true if this is an unexpected EOF error.
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use oximedia_core::error::OxiError;
225    ///
226    /// assert!(OxiError::UnexpectedEof.is_unexpected_eof());
227    /// assert!(!OxiError::Eof.is_unexpected_eof());
228    /// ```
229    #[must_use]
230    pub const fn is_unexpected_eof(&self) -> bool {
231        matches!(self, Self::UnexpectedEof)
232    }
233}
234
235/// Result type alias for `OxiMedia` operations.
236///
237/// This is a convenience alias for `Result<T, OxiError>`.
238///
239/// # Examples
240///
241/// ```
242/// use oximedia_core::error::OxiResult;
243///
244/// fn process() -> OxiResult<u32> {
245///     Ok(42)
246/// }
247/// ```
248pub type OxiResult<T> = Result<T, OxiError>;
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn test_parse_error() {
256        let err = OxiError::parse(100, "Invalid magic bytes");
257        assert!(matches!(err, OxiError::Parse { offset: 100, .. }));
258        let msg = format!("{err}");
259        assert!(msg.contains("100"));
260        assert!(msg.contains("Invalid magic bytes"));
261    }
262
263    #[test]
264    fn test_codec_error() {
265        let err = OxiError::codec("Frame decode failed");
266        assert!(matches!(err, OxiError::Codec(_)));
267        assert!(format!("{err}").contains("Frame decode failed"));
268    }
269
270    #[test]
271    fn test_unsupported_error() {
272        let err = OxiError::unsupported("H.265 codec");
273        assert!(format!("{err}").contains("H.265 codec"));
274    }
275
276    #[test]
277    fn test_patent_violation() {
278        let err = OxiError::patent_violation("H.264");
279        assert!(err.is_patent_violation());
280        assert!(format!("{err}").contains("H.264"));
281    }
282
283    #[test]
284    fn test_buffer_too_small() {
285        let err = OxiError::buffer_too_small(1024, 512);
286        assert!(format!("{err}").contains("1024"));
287        assert!(format!("{err}").contains("512"));
288    }
289
290    #[test]
291    fn test_eof() {
292        let err = OxiError::Eof;
293        assert!(err.is_eof());
294        assert!(!OxiError::codec("test").is_eof());
295    }
296
297    #[test]
298    fn test_io_error_from() {
299        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
300        let err: OxiError = io_err.into();
301        assert!(matches!(err, OxiError::Io(_)));
302    }
303}