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