oxidize_pdf/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum PdfError {
5    #[error("IO error: {0}")]
6    Io(#[from] std::io::Error),
7
8    #[error("Invalid PDF structure: {0}")]
9    InvalidStructure(String),
10
11    #[error("Invalid object reference: {0}")]
12    InvalidReference(String),
13
14    #[error("Encoding error: {0}")]
15    EncodingError(String),
16
17    #[error("Font error: {0}")]
18    FontError(String),
19
20    #[error("Compression error: {0}")]
21    CompressionError(String),
22
23    #[error("Invalid image: {0}")]
24    InvalidImage(String),
25
26    #[error("Invalid object reference: {0} {1} R")]
27    InvalidObjectReference(u32, u16),
28
29    #[error("Parse error: {0}")]
30    ParseError(String),
31
32    #[error("Invalid page number: {0}")]
33    InvalidPageNumber(u32),
34
35    #[error("Invalid format: {0}")]
36    InvalidFormat(String),
37
38    #[error("Invalid header")]
39    InvalidHeader,
40
41    #[error("Content stream too large: {0} bytes")]
42    ContentStreamTooLarge(usize),
43
44    #[error("Operation cancelled")]
45    OperationCancelled,
46}
47
48pub type Result<T> = std::result::Result<T, PdfError>;
49
50// Separate error type for oxidize-pdf-core
51#[derive(Error, Debug)]
52pub enum OxidizePdfError {
53    #[error("IO error: {0}")]
54    Io(#[from] std::io::Error),
55
56    #[error("Parse error: {0}")]
57    ParseError(String),
58
59    #[error("Invalid PDF structure: {0}")]
60    InvalidStructure(String),
61
62    #[error("Encoding error: {0}")]
63    EncodingError(String),
64
65    #[error("Other error: {0}")]
66    Other(String),
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use std::io::{Error as IoError, ErrorKind};
73
74    #[test]
75    fn test_pdf_error_display() {
76        let error = PdfError::InvalidStructure("test message".to_string());
77        assert_eq!(error.to_string(), "Invalid PDF structure: test message");
78    }
79
80    #[test]
81    fn test_pdf_error_debug() {
82        let error = PdfError::InvalidReference("object 1 0".to_string());
83        let debug_str = format!("{:?}", error);
84        assert!(debug_str.contains("InvalidReference"));
85        assert!(debug_str.contains("object 1 0"));
86    }
87
88    #[test]
89    fn test_pdf_error_from_io_error() {
90        let io_error = IoError::new(ErrorKind::NotFound, "file not found");
91        let pdf_error = PdfError::from(io_error);
92
93        match pdf_error {
94            PdfError::Io(ref err) => {
95                assert_eq!(err.kind(), ErrorKind::NotFound);
96            }
97            _ => panic!("Expected IO error variant"),
98        }
99    }
100
101    #[test]
102    fn test_all_pdf_error_variants() {
103        let errors = vec![
104            PdfError::InvalidStructure("structure error".to_string()),
105            PdfError::InvalidObjectReference(1, 0),
106            PdfError::EncodingError("encoding error".to_string()),
107            PdfError::FontError("font error".to_string()),
108            PdfError::CompressionError("compression error".to_string()),
109            PdfError::InvalidImage("image error".to_string()),
110            PdfError::ParseError("parse error".to_string()),
111            PdfError::InvalidPageNumber(999),
112            PdfError::InvalidFormat("format error".to_string()),
113            PdfError::InvalidHeader,
114            PdfError::ContentStreamTooLarge(1024 * 1024),
115        ];
116
117        // Test that all variants can be created and displayed
118        for error in errors {
119            let error_string = error.to_string();
120            assert!(!error_string.is_empty());
121        }
122    }
123
124    #[test]
125    fn test_oxidize_pdf_error_display() {
126        let error = OxidizePdfError::ParseError("parsing failed".to_string());
127        assert_eq!(error.to_string(), "Parse error: parsing failed");
128    }
129
130    #[test]
131    fn test_oxidize_pdf_error_debug() {
132        let error = OxidizePdfError::InvalidStructure("malformed PDF".to_string());
133        let debug_str = format!("{:?}", error);
134        assert!(debug_str.contains("InvalidStructure"));
135        assert!(debug_str.contains("malformed PDF"));
136    }
137
138    #[test]
139    fn test_oxidize_pdf_error_from_io_error() {
140        let io_error = IoError::new(ErrorKind::PermissionDenied, "access denied");
141        let pdf_error = OxidizePdfError::from(io_error);
142
143        match pdf_error {
144            OxidizePdfError::Io(ref err) => {
145                assert_eq!(err.kind(), ErrorKind::PermissionDenied);
146            }
147            _ => panic!("Expected IO error variant"),
148        }
149    }
150
151    #[test]
152    fn test_all_oxidize_pdf_error_variants() {
153        let errors = vec![
154            OxidizePdfError::ParseError("parse error".to_string()),
155            OxidizePdfError::InvalidStructure("structure error".to_string()),
156            OxidizePdfError::EncodingError("encoding error".to_string()),
157            OxidizePdfError::Other("other error".to_string()),
158        ];
159
160        // Test that all variants can be created and displayed
161        for error in errors {
162            let error_string = error.to_string();
163            assert!(!error_string.is_empty());
164            assert!(error_string.contains("error"));
165        }
166    }
167
168    #[test]
169    fn test_result_type_ok() {
170        let result: Result<i32> = Ok(42);
171        assert!(result.is_ok());
172        assert_eq!(result.unwrap(), 42);
173    }
174
175    #[test]
176    fn test_result_type_err() {
177        let result: Result<i32> = Err(PdfError::InvalidStructure("test".to_string()));
178        assert!(result.is_err());
179
180        let error = result.unwrap_err();
181        match error {
182            PdfError::InvalidStructure(msg) => assert_eq!(msg, "test"),
183            _ => panic!("Expected InvalidStructure variant"),
184        }
185    }
186
187    #[test]
188    fn test_error_chain_display() {
189        // Test that error messages are properly formatted
190        let errors = [
191            (
192                "Invalid PDF structure: corrupted header",
193                PdfError::InvalidStructure("corrupted header".to_string()),
194            ),
195            (
196                "Invalid object reference: 999 0 R",
197                PdfError::InvalidObjectReference(999, 0),
198            ),
199            (
200                "Encoding error: unsupported encoding",
201                PdfError::EncodingError("unsupported encoding".to_string()),
202            ),
203            (
204                "Font error: missing font",
205                PdfError::FontError("missing font".to_string()),
206            ),
207            (
208                "Compression error: deflate failed",
209                PdfError::CompressionError("deflate failed".to_string()),
210            ),
211            (
212                "Invalid image: corrupt JPEG",
213                PdfError::InvalidImage("corrupt JPEG".to_string()),
214            ),
215        ];
216
217        for (expected, error) in errors {
218            assert_eq!(error.to_string(), expected);
219        }
220    }
221
222    #[test]
223    fn test_oxidize_pdf_error_chain_display() {
224        // Test that OxidizePdfError messages are properly formatted
225        let errors = [
226            (
227                "Parse error: unexpected token",
228                OxidizePdfError::ParseError("unexpected token".to_string()),
229            ),
230            (
231                "Invalid PDF structure: missing xref",
232                OxidizePdfError::InvalidStructure("missing xref".to_string()),
233            ),
234            (
235                "Encoding error: invalid UTF-8",
236                OxidizePdfError::EncodingError("invalid UTF-8".to_string()),
237            ),
238            (
239                "Other error: unknown issue",
240                OxidizePdfError::Other("unknown issue".to_string()),
241            ),
242        ];
243
244        for (expected, error) in errors {
245            assert_eq!(error.to_string(), expected);
246        }
247    }
248
249    #[test]
250    fn test_error_send_sync() {
251        // Ensure error types implement Send + Sync for thread safety
252        fn assert_send_sync<T: Send + Sync>() {}
253        assert_send_sync::<PdfError>();
254        assert_send_sync::<OxidizePdfError>();
255    }
256
257    #[test]
258    fn test_error_struct_creation() {
259        // Test creating errors with string messages
260        let errors = vec![
261            PdfError::InvalidStructure("test".to_string()),
262            PdfError::InvalidObjectReference(1, 0),
263            PdfError::EncodingError("encoding".to_string()),
264            PdfError::FontError("font".to_string()),
265            PdfError::CompressionError("compression".to_string()),
266            PdfError::InvalidImage("image".to_string()),
267            PdfError::ParseError("parse".to_string()),
268            PdfError::InvalidPageNumber(1),
269            PdfError::InvalidFormat("format".to_string()),
270            PdfError::InvalidHeader,
271            PdfError::ContentStreamTooLarge(1024),
272            PdfError::OperationCancelled,
273        ];
274
275        // Verify each error can be created and has the expected message structure
276        for error in errors {
277            let msg = error.to_string();
278            assert!(!msg.is_empty(), "Error message should not be empty");
279
280            // Check that the message makes sense for the error type
281            match &error {
282                PdfError::OperationCancelled => assert!(msg.contains("cancelled")),
283                PdfError::ContentStreamTooLarge(_) => assert!(msg.contains("too large")),
284                _ => assert!(msg.contains("error") || msg.contains("Invalid")),
285            }
286        }
287    }
288
289    #[test]
290    fn test_oxidize_pdf_error_struct_creation() {
291        // Test creating OxidizePdfError with string messages
292        let errors = vec![
293            OxidizePdfError::ParseError("test".to_string()),
294            OxidizePdfError::InvalidStructure("structure".to_string()),
295            OxidizePdfError::EncodingError("encoding".to_string()),
296            OxidizePdfError::Other("other".to_string()),
297        ];
298
299        // Verify each error can be created and has the expected message structure
300        for error in errors {
301            let msg = error.to_string();
302            assert!(msg.contains("error") || msg.contains("Invalid"));
303        }
304    }
305
306    #[test]
307    fn test_error_equality() {
308        let error1 = PdfError::InvalidStructure("test".to_string());
309        let error2 = PdfError::InvalidStructure("test".to_string());
310        let error3 = PdfError::InvalidStructure("different".to_string());
311
312        // Note: thiserror doesn't automatically derive PartialEq, so we test the display output
313        assert_eq!(error1.to_string(), error2.to_string());
314        assert_ne!(error1.to_string(), error3.to_string());
315    }
316
317    #[test]
318    fn test_io_error_preservation() {
319        // Test that IO error details are preserved through conversion
320        let original_io_error = IoError::new(ErrorKind::UnexpectedEof, "sudden EOF");
321        let pdf_error = PdfError::from(original_io_error);
322
323        if let PdfError::Io(io_err) = pdf_error {
324            assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof);
325            assert_eq!(io_err.to_string(), "sudden EOF");
326        } else {
327            panic!("IO error should be preserved as PdfError::Io");
328        }
329    }
330
331    #[test]
332    fn test_oxidize_pdf_error_io_error_preservation() {
333        // Test that IO error details are preserved through conversion
334        let original_io_error = IoError::new(ErrorKind::InvalidData, "corrupted data");
335        let oxidize_error = OxidizePdfError::from(original_io_error);
336
337        if let OxidizePdfError::Io(io_err) = oxidize_error {
338            assert_eq!(io_err.kind(), ErrorKind::InvalidData);
339            assert_eq!(io_err.to_string(), "corrupted data");
340        } else {
341            panic!("IO error should be preserved as OxidizePdfError::Io");
342        }
343    }
344}