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