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 #[error("Table overflow: {rendered} rows fit, {dropped} dropped below floor y={bottom_y}")]
75 TableOverflow {
76 rendered: usize,
78 dropped: usize,
80 bottom_y: f64,
82 },
83}
84
85pub type Result<T> = std::result::Result<T, PdfError>;
86
87pub(crate) fn ensure_finite(name: &str, v: f64) -> std::result::Result<(), PdfError> {
94 if v.is_finite() {
95 Ok(())
96 } else {
97 Err(PdfError::InvalidStructure(format!(
98 "{name} must be a finite f64, got {v}"
99 )))
100 }
101}
102
103impl From<crate::encryption::AesError> for PdfError {
105 fn from(err: crate::encryption::AesError) -> Self {
106 PdfError::EncryptionError(err.to_string())
107 }
108}
109
110impl From<crate::parser::ParseError> for PdfError {
111 fn from(err: crate::parser::ParseError) -> Self {
112 PdfError::ParseError(err.to_string())
113 }
114}
115
116#[derive(Error, Debug)]
118pub enum OxidizePdfError {
119 #[error("IO error: {0}")]
120 Io(#[from] std::io::Error),
121
122 #[error("Parse error: {0}")]
123 ParseError(String),
124
125 #[error("Invalid PDF structure: {0}")]
126 InvalidStructure(String),
127
128 #[error("Encoding error: {0}")]
129 EncodingError(String),
130
131 #[error("Other error: {0}")]
132 Other(String),
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use std::io::{Error as IoError, ErrorKind};
139
140 #[test]
141 fn test_pdf_error_display() {
142 let error = PdfError::InvalidStructure("test message".to_string());
143 assert_eq!(error.to_string(), "Invalid PDF structure: test message");
144 }
145
146 #[test]
147 fn test_pdf_error_debug() {
148 let error = PdfError::InvalidReference("object 1 0".to_string());
149 let debug_str = format!("{error:?}");
150 assert!(debug_str.contains("InvalidReference"));
151 assert!(debug_str.contains("object 1 0"));
152 }
153
154 #[test]
155 fn test_pdf_error_from_io_error() {
156 let io_error = IoError::new(ErrorKind::NotFound, "file not found");
157 let pdf_error = PdfError::from(io_error);
158
159 match pdf_error {
160 PdfError::Io(ref err) => {
161 assert_eq!(err.kind(), ErrorKind::NotFound);
162 }
163 _ => panic!("Expected IO error variant"),
164 }
165 }
166
167 #[test]
168 fn test_all_pdf_error_variants() {
169 let errors = vec![
170 PdfError::InvalidStructure("structure error".to_string()),
171 PdfError::InvalidObjectReference(1, 0),
172 PdfError::EncodingError("encoding error".to_string()),
173 PdfError::FontError("font error".to_string()),
174 PdfError::CompressionError("compression error".to_string()),
175 PdfError::InvalidImage("image error".to_string()),
176 PdfError::ParseError("parse error".to_string()),
177 PdfError::InvalidPageNumber(999),
178 PdfError::InvalidFormat("format error".to_string()),
179 PdfError::InvalidHeader,
180 PdfError::ContentStreamTooLarge(1024 * 1024),
181 ];
182
183 for error in errors {
185 let error_string = error.to_string();
186 assert!(!error_string.is_empty());
187 }
188 }
189
190 #[test]
191 fn test_oxidize_pdf_error_display() {
192 let error = OxidizePdfError::ParseError("parsing failed".to_string());
193 assert_eq!(error.to_string(), "Parse error: parsing failed");
194 }
195
196 #[test]
197 fn test_oxidize_pdf_error_debug() {
198 let error = OxidizePdfError::InvalidStructure("malformed PDF".to_string());
199 let debug_str = format!("{error:?}");
200 assert!(debug_str.contains("InvalidStructure"));
201 assert!(debug_str.contains("malformed PDF"));
202 }
203
204 #[test]
205 fn test_oxidize_pdf_error_from_io_error() {
206 let io_error = IoError::new(ErrorKind::PermissionDenied, "access denied");
207 let pdf_error = OxidizePdfError::from(io_error);
208
209 match pdf_error {
210 OxidizePdfError::Io(ref err) => {
211 assert_eq!(err.kind(), ErrorKind::PermissionDenied);
212 }
213 _ => panic!("Expected IO error variant"),
214 }
215 }
216
217 #[test]
218 fn test_all_oxidize_pdf_error_variants() {
219 let errors = vec![
220 OxidizePdfError::ParseError("parse error".to_string()),
221 OxidizePdfError::InvalidStructure("structure error".to_string()),
222 OxidizePdfError::EncodingError("encoding error".to_string()),
223 OxidizePdfError::Other("other error".to_string()),
224 ];
225
226 for error in errors {
228 let error_string = error.to_string();
229 assert!(!error_string.is_empty());
230 assert!(error_string.contains("error"));
231 }
232 }
233
234 #[test]
235 fn test_result_type_ok() {
236 let result: Result<i32> = Ok(42);
237 assert!(result.is_ok());
238 assert_eq!(result.unwrap(), 42);
239 }
240
241 #[test]
242 fn test_result_type_err() {
243 let result: Result<i32> = Err(PdfError::InvalidStructure("test".to_string()));
244 assert!(result.is_err());
245
246 let error = result.unwrap_err();
247 match error {
248 PdfError::InvalidStructure(msg) => assert_eq!(msg, "test"),
249 _ => panic!("Expected InvalidStructure variant"),
250 }
251 }
252
253 #[test]
254 fn test_error_chain_display() {
255 let errors = [
257 (
258 "Invalid PDF structure: corrupted header",
259 PdfError::InvalidStructure("corrupted header".to_string()),
260 ),
261 (
262 "Invalid object reference: 999 0 R",
263 PdfError::InvalidObjectReference(999, 0),
264 ),
265 (
266 "Encoding error: unsupported encoding",
267 PdfError::EncodingError("unsupported encoding".to_string()),
268 ),
269 (
270 "Font error: missing font",
271 PdfError::FontError("missing font".to_string()),
272 ),
273 (
274 "Compression error: deflate failed",
275 PdfError::CompressionError("deflate failed".to_string()),
276 ),
277 (
278 "Invalid image: corrupt JPEG",
279 PdfError::InvalidImage("corrupt JPEG".to_string()),
280 ),
281 ];
282
283 for (expected, error) in errors {
284 assert_eq!(error.to_string(), expected);
285 }
286 }
287
288 #[test]
289 fn test_oxidize_pdf_error_chain_display() {
290 let errors = [
292 (
293 "Parse error: unexpected token",
294 OxidizePdfError::ParseError("unexpected token".to_string()),
295 ),
296 (
297 "Invalid PDF structure: missing xref",
298 OxidizePdfError::InvalidStructure("missing xref".to_string()),
299 ),
300 (
301 "Encoding error: invalid UTF-8",
302 OxidizePdfError::EncodingError("invalid UTF-8".to_string()),
303 ),
304 (
305 "Other error: unknown issue",
306 OxidizePdfError::Other("unknown issue".to_string()),
307 ),
308 ];
309
310 for (expected, error) in errors {
311 assert_eq!(error.to_string(), expected);
312 }
313 }
314
315 #[test]
316 fn test_error_send_sync() {
317 fn assert_send_sync<T: Send + Sync>() {}
319 assert_send_sync::<PdfError>();
320 assert_send_sync::<OxidizePdfError>();
321 }
322
323 #[test]
324 fn test_error_struct_creation() {
325 let errors = vec![
327 PdfError::InvalidStructure("test".to_string()),
328 PdfError::InvalidObjectReference(1, 0),
329 PdfError::EncodingError("encoding".to_string()),
330 PdfError::FontError("font".to_string()),
331 PdfError::CompressionError("compression".to_string()),
332 PdfError::InvalidImage("image".to_string()),
333 PdfError::ParseError("parse".to_string()),
334 PdfError::InvalidPageNumber(1),
335 PdfError::InvalidFormat("format".to_string()),
336 PdfError::InvalidHeader,
337 PdfError::ContentStreamTooLarge(1024),
338 PdfError::OperationCancelled,
339 ];
340
341 for error in errors {
343 let msg = error.to_string();
344 assert!(!msg.is_empty(), "Error message should not be empty");
345
346 match &error {
348 PdfError::OperationCancelled => assert!(msg.contains("cancelled")),
349 PdfError::ContentStreamTooLarge(_) => assert!(msg.contains("too large")),
350 _ => assert!(msg.contains("error") || msg.contains("Invalid")),
351 }
352 }
353 }
354
355 #[test]
356 fn test_oxidize_pdf_error_struct_creation() {
357 let errors = vec![
359 OxidizePdfError::ParseError("test".to_string()),
360 OxidizePdfError::InvalidStructure("structure".to_string()),
361 OxidizePdfError::EncodingError("encoding".to_string()),
362 OxidizePdfError::Other("other".to_string()),
363 ];
364
365 for error in errors {
367 let msg = error.to_string();
368 assert!(msg.contains("error") || msg.contains("Invalid"));
369 }
370 }
371
372 #[test]
373 fn test_error_equality() {
374 let error1 = PdfError::InvalidStructure("test".to_string());
375 let error2 = PdfError::InvalidStructure("test".to_string());
376 let error3 = PdfError::InvalidStructure("different".to_string());
377
378 assert_eq!(error1.to_string(), error2.to_string());
380 assert_ne!(error1.to_string(), error3.to_string());
381 }
382
383 #[test]
384 fn test_io_error_preservation() {
385 let original_io_error = IoError::new(ErrorKind::UnexpectedEof, "sudden EOF");
387 let pdf_error = PdfError::from(original_io_error);
388
389 if let PdfError::Io(io_err) = pdf_error {
390 assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof);
391 assert_eq!(io_err.to_string(), "sudden EOF");
392 } else {
393 panic!("IO error should be preserved as PdfError::Io");
394 }
395 }
396
397 #[test]
398 fn test_oxidize_pdf_error_io_error_preservation() {
399 let original_io_error = IoError::new(ErrorKind::InvalidData, "corrupted data");
401 let oxidize_error = OxidizePdfError::from(original_io_error);
402
403 if let OxidizePdfError::Io(io_err) = oxidize_error {
404 assert_eq!(io_err.kind(), ErrorKind::InvalidData);
405 assert_eq!(io_err.to_string(), "corrupted data");
406 } else {
407 panic!("IO error should be preserved as OxidizePdfError::Io");
408 }
409 }
410
411 #[test]
412 fn test_operation_cancelled_error() {
413 let error = PdfError::OperationCancelled;
415 assert_eq!(error.to_string(), "Operation cancelled");
416
417 let result: Result<()> = Err(PdfError::OperationCancelled);
419 assert!(result.is_err());
420 if let Err(PdfError::OperationCancelled) = result {
421 } else {
423 panic!("Expected OperationCancelled variant");
424 }
425 }
426
427 #[test]
428 fn test_encryption_error() {
429 let error = PdfError::EncryptionError("AES decryption failed".to_string());
431 assert_eq!(error.to_string(), "Encryption error: AES decryption failed");
432
433 let debug_str = format!("{:?}", error);
435 assert!(debug_str.contains("EncryptionError"));
436 assert!(debug_str.contains("AES decryption failed"));
437 }
438
439 #[test]
440 fn test_permission_denied_error() {
441 let error = PdfError::PermissionDenied("Cannot modify protected document".to_string());
443 assert_eq!(
444 error.to_string(),
445 "Permission denied: Cannot modify protected document"
446 );
447
448 let other_error = PdfError::InvalidOperation("Cannot modify".to_string());
450 assert_ne!(error.to_string(), other_error.to_string());
451 }
452
453 #[test]
454 fn test_invalid_operation_error() {
455 let error =
457 PdfError::InvalidOperation("Cannot perform operation on encrypted PDF".to_string());
458 assert_eq!(
459 error.to_string(),
460 "Invalid operation: Cannot perform operation on encrypted PDF"
461 );
462
463 match error {
465 PdfError::InvalidOperation(msg) => {
466 assert!(msg.contains("encrypted"));
467 }
468 _ => panic!("Expected InvalidOperation variant"),
469 }
470 }
471
472 #[test]
473 fn test_duplicate_field_error() {
474 let field_name = "email_address";
476 let error = PdfError::DuplicateField(field_name.to_string());
477 assert_eq!(error.to_string(), "Duplicate field: email_address");
478
479 let empty_error = PdfError::DuplicateField(String::new());
481 assert_eq!(empty_error.to_string(), "Duplicate field: ");
482 }
483
484 #[test]
485 fn test_field_not_found_error() {
486 let field_name = "signature_field";
488 let error = PdfError::FieldNotFound(field_name.to_string());
489 assert_eq!(error.to_string(), "Field not found: signature_field");
490
491 let special_field = "field[0].subfield";
493 let special_error = PdfError::FieldNotFound(special_field.to_string());
494 assert_eq!(
495 special_error.to_string(),
496 "Field not found: field[0].subfield"
497 );
498 }
499
500 #[test]
501 fn test_aes_error_conversion() {
502 use crate::encryption::AesError;
505
506 let aes_error = AesError::InvalidKeyLength {
507 expected: 32,
508 actual: 16,
509 };
510 let pdf_error: PdfError = aes_error.into();
511
512 match pdf_error {
513 PdfError::EncryptionError(msg) => {
514 assert!(msg.contains("Invalid key length") || msg.contains("InvalidKeyLength"));
515 }
516 _ => panic!("Expected EncryptionError from AesError conversion"),
517 }
518 }
519
520 #[test]
521 fn test_parse_error_conversion() {
522 use crate::parser::ParseError;
524
525 let parse_error = ParseError::InvalidXRef;
526 let pdf_error: PdfError = parse_error.into();
527
528 match pdf_error {
529 PdfError::ParseError(msg) => {
530 assert!(msg.contains("XRef") || msg.contains("Invalid"));
531 }
532 _ => panic!("Expected ParseError from ParseError conversion"),
533 }
534 }
535}