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    #[error("Invalid operation: {0}")]
54    InvalidOperation(String),
55
56    #[error("Duplicate field: {0}")]
57    DuplicateField(String),
58
59    #[error("Field not found: {0}")]
60    FieldNotFound(String),
61
62    #[error("External validation error: {0}")]
63    ExternalValidationError(String),
64
65    #[error("Internal error: {0}")]
66    Internal(String),
67
68    #[error("Serialization error: {0}")]
69    SerializationError(String),
70}
71
72pub type Result<T> = std::result::Result<T, PdfError>;
73
74// Convert AesError to PdfError
75impl From<crate::encryption::AesError> for PdfError {
76    fn from(err: crate::encryption::AesError) -> Self {
77        PdfError::EncryptionError(err.to_string())
78    }
79}
80
81impl From<crate::parser::ParseError> for PdfError {
82    fn from(err: crate::parser::ParseError) -> Self {
83        PdfError::ParseError(err.to_string())
84    }
85}
86
87// Separate error type for oxidize-pdf-core
88#[derive(Error, Debug)]
89pub enum OxidizePdfError {
90    #[error("IO error: {0}")]
91    Io(#[from] std::io::Error),
92
93    #[error("Parse error: {0}")]
94    ParseError(String),
95
96    #[error("Invalid PDF structure: {0}")]
97    InvalidStructure(String),
98
99    #[error("Encoding error: {0}")]
100    EncodingError(String),
101
102    #[error("Other error: {0}")]
103    Other(String),
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use std::io::{Error as IoError, ErrorKind};
110
111    #[test]
112    fn test_pdf_error_display() {
113        let error = PdfError::InvalidStructure("test message".to_string());
114        assert_eq!(error.to_string(), "Invalid PDF structure: test message");
115    }
116
117    #[test]
118    fn test_pdf_error_debug() {
119        let error = PdfError::InvalidReference("object 1 0".to_string());
120        let debug_str = format!("{error:?}");
121        assert!(debug_str.contains("InvalidReference"));
122        assert!(debug_str.contains("object 1 0"));
123    }
124
125    #[test]
126    fn test_pdf_error_from_io_error() {
127        let io_error = IoError::new(ErrorKind::NotFound, "file not found");
128        let pdf_error = PdfError::from(io_error);
129
130        match pdf_error {
131            PdfError::Io(ref err) => {
132                assert_eq!(err.kind(), ErrorKind::NotFound);
133            }
134            _ => panic!("Expected IO error variant"),
135        }
136    }
137
138    #[test]
139    fn test_all_pdf_error_variants() {
140        let errors = vec![
141            PdfError::InvalidStructure("structure error".to_string()),
142            PdfError::InvalidObjectReference(1, 0),
143            PdfError::EncodingError("encoding error".to_string()),
144            PdfError::FontError("font error".to_string()),
145            PdfError::CompressionError("compression error".to_string()),
146            PdfError::InvalidImage("image error".to_string()),
147            PdfError::ParseError("parse error".to_string()),
148            PdfError::InvalidPageNumber(999),
149            PdfError::InvalidFormat("format error".to_string()),
150            PdfError::InvalidHeader,
151            PdfError::ContentStreamTooLarge(1024 * 1024),
152        ];
153
154        // Test that all variants can be created and displayed
155        for error in errors {
156            let error_string = error.to_string();
157            assert!(!error_string.is_empty());
158        }
159    }
160
161    #[test]
162    fn test_oxidize_pdf_error_display() {
163        let error = OxidizePdfError::ParseError("parsing failed".to_string());
164        assert_eq!(error.to_string(), "Parse error: parsing failed");
165    }
166
167    #[test]
168    fn test_oxidize_pdf_error_debug() {
169        let error = OxidizePdfError::InvalidStructure("malformed PDF".to_string());
170        let debug_str = format!("{error:?}");
171        assert!(debug_str.contains("InvalidStructure"));
172        assert!(debug_str.contains("malformed PDF"));
173    }
174
175    #[test]
176    fn test_oxidize_pdf_error_from_io_error() {
177        let io_error = IoError::new(ErrorKind::PermissionDenied, "access denied");
178        let pdf_error = OxidizePdfError::from(io_error);
179
180        match pdf_error {
181            OxidizePdfError::Io(ref err) => {
182                assert_eq!(err.kind(), ErrorKind::PermissionDenied);
183            }
184            _ => panic!("Expected IO error variant"),
185        }
186    }
187
188    #[test]
189    fn test_all_oxidize_pdf_error_variants() {
190        let errors = vec![
191            OxidizePdfError::ParseError("parse error".to_string()),
192            OxidizePdfError::InvalidStructure("structure error".to_string()),
193            OxidizePdfError::EncodingError("encoding error".to_string()),
194            OxidizePdfError::Other("other error".to_string()),
195        ];
196
197        // Test that all variants can be created and displayed
198        for error in errors {
199            let error_string = error.to_string();
200            assert!(!error_string.is_empty());
201            assert!(error_string.contains("error"));
202        }
203    }
204
205    #[test]
206    fn test_result_type_ok() {
207        let result: Result<i32> = Ok(42);
208        assert!(result.is_ok());
209        assert_eq!(result.unwrap(), 42);
210    }
211
212    #[test]
213    fn test_result_type_err() {
214        let result: Result<i32> = Err(PdfError::InvalidStructure("test".to_string()));
215        assert!(result.is_err());
216
217        let error = result.unwrap_err();
218        match error {
219            PdfError::InvalidStructure(msg) => assert_eq!(msg, "test"),
220            _ => panic!("Expected InvalidStructure variant"),
221        }
222    }
223
224    #[test]
225    fn test_error_chain_display() {
226        // Test that error messages are properly formatted
227        let errors = [
228            (
229                "Invalid PDF structure: corrupted header",
230                PdfError::InvalidStructure("corrupted header".to_string()),
231            ),
232            (
233                "Invalid object reference: 999 0 R",
234                PdfError::InvalidObjectReference(999, 0),
235            ),
236            (
237                "Encoding error: unsupported encoding",
238                PdfError::EncodingError("unsupported encoding".to_string()),
239            ),
240            (
241                "Font error: missing font",
242                PdfError::FontError("missing font".to_string()),
243            ),
244            (
245                "Compression error: deflate failed",
246                PdfError::CompressionError("deflate failed".to_string()),
247            ),
248            (
249                "Invalid image: corrupt JPEG",
250                PdfError::InvalidImage("corrupt JPEG".to_string()),
251            ),
252        ];
253
254        for (expected, error) in errors {
255            assert_eq!(error.to_string(), expected);
256        }
257    }
258
259    #[test]
260    fn test_oxidize_pdf_error_chain_display() {
261        // Test that OxidizePdfError messages are properly formatted
262        let errors = [
263            (
264                "Parse error: unexpected token",
265                OxidizePdfError::ParseError("unexpected token".to_string()),
266            ),
267            (
268                "Invalid PDF structure: missing xref",
269                OxidizePdfError::InvalidStructure("missing xref".to_string()),
270            ),
271            (
272                "Encoding error: invalid UTF-8",
273                OxidizePdfError::EncodingError("invalid UTF-8".to_string()),
274            ),
275            (
276                "Other error: unknown issue",
277                OxidizePdfError::Other("unknown issue".to_string()),
278            ),
279        ];
280
281        for (expected, error) in errors {
282            assert_eq!(error.to_string(), expected);
283        }
284    }
285
286    #[test]
287    fn test_error_send_sync() {
288        // Ensure error types implement Send + Sync for thread safety
289        fn assert_send_sync<T: Send + Sync>() {}
290        assert_send_sync::<PdfError>();
291        assert_send_sync::<OxidizePdfError>();
292    }
293
294    #[test]
295    fn test_error_struct_creation() {
296        // Test creating errors with string messages
297        let errors = vec![
298            PdfError::InvalidStructure("test".to_string()),
299            PdfError::InvalidObjectReference(1, 0),
300            PdfError::EncodingError("encoding".to_string()),
301            PdfError::FontError("font".to_string()),
302            PdfError::CompressionError("compression".to_string()),
303            PdfError::InvalidImage("image".to_string()),
304            PdfError::ParseError("parse".to_string()),
305            PdfError::InvalidPageNumber(1),
306            PdfError::InvalidFormat("format".to_string()),
307            PdfError::InvalidHeader,
308            PdfError::ContentStreamTooLarge(1024),
309            PdfError::OperationCancelled,
310        ];
311
312        // Verify each error can be created and has the expected message structure
313        for error in errors {
314            let msg = error.to_string();
315            assert!(!msg.is_empty(), "Error message should not be empty");
316
317            // Check that the message makes sense for the error type
318            match &error {
319                PdfError::OperationCancelled => assert!(msg.contains("cancelled")),
320                PdfError::ContentStreamTooLarge(_) => assert!(msg.contains("too large")),
321                _ => assert!(msg.contains("error") || msg.contains("Invalid")),
322            }
323        }
324    }
325
326    #[test]
327    fn test_oxidize_pdf_error_struct_creation() {
328        // Test creating OxidizePdfError with string messages
329        let errors = vec![
330            OxidizePdfError::ParseError("test".to_string()),
331            OxidizePdfError::InvalidStructure("structure".to_string()),
332            OxidizePdfError::EncodingError("encoding".to_string()),
333            OxidizePdfError::Other("other".to_string()),
334        ];
335
336        // Verify each error can be created and has the expected message structure
337        for error in errors {
338            let msg = error.to_string();
339            assert!(msg.contains("error") || msg.contains("Invalid"));
340        }
341    }
342
343    #[test]
344    fn test_error_equality() {
345        let error1 = PdfError::InvalidStructure("test".to_string());
346        let error2 = PdfError::InvalidStructure("test".to_string());
347        let error3 = PdfError::InvalidStructure("different".to_string());
348
349        // Note: thiserror doesn't automatically derive PartialEq, so we test the display output
350        assert_eq!(error1.to_string(), error2.to_string());
351        assert_ne!(error1.to_string(), error3.to_string());
352    }
353
354    #[test]
355    fn test_io_error_preservation() {
356        // Test that IO error details are preserved through conversion
357        let original_io_error = IoError::new(ErrorKind::UnexpectedEof, "sudden EOF");
358        let pdf_error = PdfError::from(original_io_error);
359
360        if let PdfError::Io(io_err) = pdf_error {
361            assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof);
362            assert_eq!(io_err.to_string(), "sudden EOF");
363        } else {
364            panic!("IO error should be preserved as PdfError::Io");
365        }
366    }
367
368    #[test]
369    fn test_oxidize_pdf_error_io_error_preservation() {
370        // Test that IO error details are preserved through conversion
371        let original_io_error = IoError::new(ErrorKind::InvalidData, "corrupted data");
372        let oxidize_error = OxidizePdfError::from(original_io_error);
373
374        if let OxidizePdfError::Io(io_err) = oxidize_error {
375            assert_eq!(io_err.kind(), ErrorKind::InvalidData);
376            assert_eq!(io_err.to_string(), "corrupted data");
377        } else {
378            panic!("IO error should be preserved as OxidizePdfError::Io");
379        }
380    }
381
382    #[test]
383    fn test_operation_cancelled_error() {
384        // Test the OperationCancelled variant (line 44-45)
385        let error = PdfError::OperationCancelled;
386        assert_eq!(error.to_string(), "Operation cancelled");
387
388        // Test in a Result context
389        let result: Result<()> = Err(PdfError::OperationCancelled);
390        assert!(result.is_err());
391        if let Err(PdfError::OperationCancelled) = result {
392            // Variant matched correctly
393        } else {
394            panic!("Expected OperationCancelled variant");
395        }
396    }
397
398    #[test]
399    fn test_encryption_error() {
400        // Test the EncryptionError variant (line 47-48)
401        let error = PdfError::EncryptionError("AES decryption failed".to_string());
402        assert_eq!(error.to_string(), "Encryption error: AES decryption failed");
403
404        // Test debug format
405        let debug_str = format!("{:?}", error);
406        assert!(debug_str.contains("EncryptionError"));
407        assert!(debug_str.contains("AES decryption failed"));
408    }
409
410    #[test]
411    fn test_permission_denied_error() {
412        // Test the PermissionDenied variant (line 50-51)
413        let error = PdfError::PermissionDenied("Cannot modify protected document".to_string());
414        assert_eq!(
415            error.to_string(),
416            "Permission denied: Cannot modify protected document"
417        );
418
419        // Test that it's different from InvalidOperation
420        let other_error = PdfError::InvalidOperation("Cannot modify".to_string());
421        assert_ne!(error.to_string(), other_error.to_string());
422    }
423
424    #[test]
425    fn test_invalid_operation_error() {
426        // Test the InvalidOperation variant (line 53-54)
427        let error =
428            PdfError::InvalidOperation("Cannot perform operation on encrypted PDF".to_string());
429        assert_eq!(
430            error.to_string(),
431            "Invalid operation: Cannot perform operation on encrypted PDF"
432        );
433
434        // Test in match expression
435        match error {
436            PdfError::InvalidOperation(msg) => {
437                assert!(msg.contains("encrypted"));
438            }
439            _ => panic!("Expected InvalidOperation variant"),
440        }
441    }
442
443    #[test]
444    fn test_duplicate_field_error() {
445        // Test the DuplicateField variant (line 56-57)
446        let field_name = "email_address";
447        let error = PdfError::DuplicateField(field_name.to_string());
448        assert_eq!(error.to_string(), "Duplicate field: email_address");
449
450        // Test that it handles empty field names
451        let empty_error = PdfError::DuplicateField(String::new());
452        assert_eq!(empty_error.to_string(), "Duplicate field: ");
453    }
454
455    #[test]
456    fn test_field_not_found_error() {
457        // Test the FieldNotFound variant (line 59-60)
458        let field_name = "signature_field";
459        let error = PdfError::FieldNotFound(field_name.to_string());
460        assert_eq!(error.to_string(), "Field not found: signature_field");
461
462        // Test with special characters
463        let special_field = "field[0].subfield";
464        let special_error = PdfError::FieldNotFound(special_field.to_string());
465        assert_eq!(
466            special_error.to_string(),
467            "Field not found: field[0].subfield"
468        );
469    }
470
471    #[test]
472    fn test_aes_error_conversion() {
473        // Test the From<AesError> conversion (line 66-70)
474        // We need to simulate an AesError
475        use crate::encryption::AesError;
476
477        let aes_error = AesError::InvalidKeyLength {
478            expected: 32,
479            actual: 16,
480        };
481        let pdf_error: PdfError = aes_error.into();
482
483        match pdf_error {
484            PdfError::EncryptionError(msg) => {
485                assert!(msg.contains("Invalid key length") || msg.contains("InvalidKeyLength"));
486            }
487            _ => panic!("Expected EncryptionError from AesError conversion"),
488        }
489    }
490
491    #[test]
492    fn test_parse_error_conversion() {
493        // Test the From<ParseError> conversion (line 72-76)
494        use crate::parser::ParseError;
495
496        let parse_error = ParseError::InvalidXRef;
497        let pdf_error: PdfError = parse_error.into();
498
499        match pdf_error {
500            PdfError::ParseError(msg) => {
501                assert!(msg.contains("XRef") || msg.contains("Invalid"));
502            }
503            _ => panic!("Expected ParseError from ParseError conversion"),
504        }
505    }
506}