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}