Skip to main content

pdf_engine/
error.rs

1//! Error types for the rendering engine.
2
3use crate::api_error::PdfError;
4
5/// Errors that can occur in the rendering engine.
6#[derive(Debug, thiserror::Error)]
7pub enum EngineError {
8    /// The PDF data is invalid or could not be parsed.
9    #[error("invalid PDF: {0}")]
10    InvalidPdf(String),
11
12    /// A page index is out of range.
13    #[error("page {index} out of range (document has {count} pages)")]
14    PageOutOfRange {
15        /// The requested page index.
16        index: usize,
17        /// Total number of pages.
18        count: usize,
19    },
20
21    /// A rendering error occurred.
22    #[error("render error: {0}")]
23    RenderError(String),
24
25    /// An I/O error occurred.
26    #[error(transparent)]
27    Io(#[from] std::io::Error),
28
29    /// The PDF is encrypted and requires a password to open.
30    #[error("PDF is encrypted: {0}")]
31    Encrypted(String),
32
33    /// The page has invalid or degenerate dimensions.
34    #[error("invalid page geometry (w={width:.2}pt, h={height:.2}pt): {reason}")]
35    InvalidPageGeometry {
36        /// Page width in points.
37        width: f32,
38        /// Page height in points.
39        height: f32,
40        /// Human-readable reason for the failure.
41        reason: String,
42    },
43
44    /// XFA flattening failed.
45    #[error("XFA flatten failed: {0}")]
46    XfaFlattenFailed(String),
47
48    /// A configured resource limit was exceeded during rendering or
49    /// text extraction.  Carries the [`LimitError`] so callers can
50    /// branch on the specific cap that fired.
51    ///
52    /// [`LimitError`]: crate::limits::LimitError
53    #[error("resource limit exceeded: {0}")]
54    LimitExceeded(crate::limits::LimitError),
55}
56
57impl PdfError for EngineError {
58    fn code(&self) -> &str {
59        match self {
60            EngineError::InvalidPdf(_) => "INVALID_PDF",
61            EngineError::PageOutOfRange { .. } => "PAGE_OUT_OF_RANGE",
62            EngineError::RenderError(_) => "RENDER_ERROR",
63            EngineError::Io(_) => "IO_ERROR",
64            EngineError::Encrypted(_) => "ENCRYPTED",
65            EngineError::InvalidPageGeometry { .. } => "INVALID_PAGE_GEOMETRY",
66            EngineError::XfaFlattenFailed(_) => "XFA_FLATTEN_FAILED",
67            EngineError::LimitExceeded(_) => "LIMIT_EXCEEDED",
68        }
69    }
70
71    fn help(&self) -> Option<String> {
72        match self {
73            EngineError::InvalidPdf(_) => {
74                Some("The PDF file is corrupt or invalid. Try using a PDF repair tool or re-save the document.".to_string())
75            }
76            EngineError::PageOutOfRange { index, count } => {
77                Some(format!("Page {} does not exist. This document has {} pages (0-{}).", index, count, count - 1))
78            }
79            EngineError::RenderError(_) => {
80                Some("Rendering failed. Check that the page exists and the document is not corrupted.".to_string())
81            }
82            EngineError::Io(_) => {
83                Some("An I/O error occurred. Check file permissions and disk space.".to_string())
84            }
85            EngineError::Encrypted(_) => {
86                Some("The PDF is encrypted. Provide the correct password to open it.".to_string())
87            }
88            EngineError::InvalidPageGeometry { .. } => {
89                Some("The page has invalid or degenerate dimensions and cannot be rendered.".to_string())
90            }
91            EngineError::XfaFlattenFailed(_) => {
92                Some("XFA flattening produced an invalid PDF. The document may use unsupported XFA features.".to_string())
93            }
94            EngineError::LimitExceeded(_) => {
95                Some("A processing resource limit was exceeded. Raise the relevant cap in ProcessingLimits or check that the input is not adversarial.".to_string())
96            }
97        }
98    }
99}
100
101/// Convenience type alias.
102pub type Result<T> = std::result::Result<T, EngineError>;
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn encrypted_error_code() {
110        let e = EngineError::Encrypted("bad password".into());
111        assert_eq!(e.code(), "ENCRYPTED");
112    }
113
114    #[test]
115    fn invalid_page_geometry_error_code() {
116        let e = EngineError::InvalidPageGeometry {
117            width: 0.5,
118            height: 0.5,
119            reason: "test".into(),
120        };
121        assert_eq!(e.code(), "INVALID_PAGE_GEOMETRY");
122    }
123
124    #[test]
125    fn xfa_flatten_failed_error_code() {
126        let e = EngineError::XfaFlattenFailed("corrupt output".into());
127        assert_eq!(e.code(), "XFA_FLATTEN_FAILED");
128    }
129}