Skip to main content

firecrawl_pdfium/
error.rs

1//! Error types for the crate.
2
3use std::fmt;
4use std::path::PathBuf;
5
6/// Convenience alias used throughout the crate.
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Failure to locate, open, or validate the PDFium shared library.
10#[derive(Debug)]
11#[non_exhaustive]
12pub enum LoadError {
13    /// No candidate library was found. Contains every location that was
14    /// tried, in discovery order.
15    LibraryNotFound {
16        /// Every location tried, in discovery order.
17        searched: Vec<String>,
18    },
19    /// A concrete library file was found (or explicitly given) but the
20    /// dynamic loader failed to open it.
21    OpenFailed {
22        /// The library file that failed to open.
23        path: PathBuf,
24        /// The dynamic loader's error.
25        source: libloading::Error,
26    },
27    /// The library opened, but a required PDFium symbol is missing — the
28    /// build is older than the oldest version this crate supports, or the
29    /// file is not PDFium at all.
30    MissingSymbol(crate::sys::MissingSymbolError),
31}
32
33impl fmt::Display for LoadError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            LoadError::LibraryNotFound { searched } => {
37                write!(
38                    f,
39                    "PDFium shared library not found; searched: {}. \
40                     Run `cargo xtask fetch-pdfium`, set PDFIUM_LIB_PATH, or use \
41                     Pdfium::load_from_path()",
42                    searched.join(", ")
43                )
44            }
45            LoadError::OpenFailed { path, source } => {
46                write!(
47                    f,
48                    "failed to open PDFium library at {}: {source}",
49                    path.display()
50                )
51            }
52            LoadError::MissingSymbol(e) => write!(f, "{e}"),
53        }
54    }
55}
56
57impl std::error::Error for LoadError {
58    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59        match self {
60            LoadError::LibraryNotFound { .. } => None,
61            LoadError::OpenFailed { source, .. } => Some(source),
62            LoadError::MissingSymbol(e) => Some(e),
63        }
64    }
65}
66
67/// All errors returned by the safe API.
68#[derive(Debug)]
69#[non_exhaustive]
70pub enum Error {
71    /// The PDFium library could not be loaded.
72    Load(LoadError),
73    /// PDFium is already loaded from a different library path; the process
74    /// keeps the first successfully loaded library for its lifetime.
75    AlreadyLoaded {
76        /// Where the active instance was loaded from (`None` when it was
77        /// resolved by bare name through the system loader).
78        loaded_from: Option<PathBuf>,
79        /// The conflicting path passed to this call.
80        requested: PathBuf,
81    },
82    /// I/O failure while reading a PDF file from disk.
83    Io(std::io::Error),
84    /// The document is encrypted and requires a password, but none was
85    /// supplied.
86    PasswordRequired,
87    /// A password was supplied but does not unlock the document.
88    IncorrectPassword,
89    /// The document uses a security/encryption scheme PDFium does not
90    /// support.
91    UnsupportedSecurity,
92    /// The data is not a PDF, or is too corrupt to open.
93    InvalidPdf,
94    /// PDFium reported an error code this crate does not recognize.
95    Pdfium {
96        /// Raw `FPDF_GetLastError` value.
97        code: u64,
98    },
99    /// Requested page index does not exist.
100    PageIndexOutOfBounds {
101        /// The requested 0-based page index.
102        index: usize,
103        /// The document's page count.
104        count: usize,
105    },
106    /// PDFium failed to load a page that should exist (severely corrupt
107    /// page tree or content).
108    PageLoadFailed {
109        /// The 0-based page index that failed to load.
110        index: usize,
111    },
112    /// PDFium failed to prepare the page for text extraction.
113    TextLoadFailed {
114        /// The 0-based page index whose text failed to load.
115        index: usize,
116    },
117    /// The page reports more text characters than the configured
118    /// extraction limit (see [`PdfPage::text_with_limit`]); nothing was
119    /// allocated.
120    ///
121    /// [`PdfPage::text_with_limit`]: crate::PdfPage::text_with_limit
122    TextTooLarge {
123        /// Characters PDFium reports on the page.
124        chars: usize,
125        /// The configured ceiling.
126        limit: usize,
127    },
128    /// PDFium failed to initialize the form-fill environment.
129    FormInitFailed,
130    /// The rendered output would exceed [`RenderConfig::max_output_bytes`]
131    /// (or the hard `i32` pixel-dimension limits of PDFium's bitmap API).
132    ///
133    /// [`RenderConfig::max_output_bytes`]: crate::RenderConfig::max_output_bytes
134    RenderTooLarge {
135        /// Bytes the requested output would need.
136        required_bytes: u64,
137        /// The configured ceiling.
138        limit: u64,
139    },
140    /// Rendering failed inside PDFium (bitmap creation or coordinate
141    /// transform rejected).
142    RenderFailed {
143        /// Which PDFium operation rejected the render.
144        reason: &'static str,
145    },
146    /// An argument or configuration value is invalid (zero/non-finite
147    /// scale, zero target dimensions, interior NUL byte in a password, ...).
148    InvalidConfig(String),
149}
150
151impl fmt::Display for Error {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match self {
154            Error::Load(e) => write!(f, "{e}"),
155            Error::AlreadyLoaded {
156                loaded_from,
157                requested,
158            } => match loaded_from {
159                Some(p) => write!(
160                    f,
161                    "PDFium is already loaded from {} (requested {}); a process keeps \
162                     its first PDFium library for its lifetime",
163                    p.display(),
164                    requested.display()
165                ),
166                None => write!(
167                    f,
168                    "PDFium is already loaded via the system loader (requested {}); a \
169                     process keeps its first PDFium library for its lifetime",
170                    requested.display()
171                ),
172            },
173            Error::Io(e) => write!(f, "I/O error reading PDF: {e}"),
174            Error::PasswordRequired => {
175                write!(f, "document is encrypted and requires a password")
176            }
177            Error::IncorrectPassword => write!(f, "incorrect password for encrypted document"),
178            Error::UnsupportedSecurity => {
179                write!(f, "document uses an unsupported security scheme")
180            }
181            Error::InvalidPdf => write!(f, "data is not a valid PDF document"),
182            Error::Pdfium { code } => write!(f, "PDFium error code {code}"),
183            Error::PageIndexOutOfBounds { index, count } => {
184                write!(
185                    f,
186                    "page index {index} out of bounds (document has {count} pages)"
187                )
188            }
189            Error::PageLoadFailed { index } => write!(f, "PDFium failed to load page {index}"),
190            Error::TextLoadFailed { index } => {
191                write!(f, "PDFium failed to load text for page {index}")
192            }
193            Error::TextTooLarge { chars, limit } => write!(
194                f,
195                "page reports {chars} text characters, exceeding the extraction \
196                 limit of {limit}"
197            ),
198            Error::FormInitFailed => {
199                write!(f, "PDFium failed to initialize the form-fill environment")
200            }
201            Error::RenderTooLarge {
202                required_bytes,
203                limit,
204            } => write!(
205                f,
206                "rendered output would require {required_bytes} bytes, exceeding the \
207                 configured limit of {limit} bytes"
208            ),
209            Error::RenderFailed { reason } => write!(f, "rendering failed: {reason}"),
210            Error::InvalidConfig(msg) => write!(f, "invalid configuration: {msg}"),
211        }
212    }
213}
214
215impl std::error::Error for Error {
216    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
217        match self {
218            Error::Load(e) => Some(e),
219            Error::Io(e) => Some(e),
220            _ => None,
221        }
222    }
223}
224
225impl From<LoadError> for Error {
226    fn from(e: LoadError) -> Self {
227        Error::Load(e)
228    }
229}
230
231impl Error {
232    /// True when the failure is specifically about encryption/passwords:
233    /// [`Error::PasswordRequired`], [`Error::IncorrectPassword`], or
234    /// [`Error::UnsupportedSecurity`].
235    pub fn is_encryption_error(&self) -> bool {
236        matches!(
237            self,
238            Error::PasswordRequired | Error::IncorrectPassword | Error::UnsupportedSecurity
239        )
240    }
241}