use std::path::PathBuf;
use crate::capability::Capability;
use crate::compliance::{PdfAProfile, Violation};
use crate::tier::Tier;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Io {
source: std::io::Error,
path: Option<PathBuf>,
},
FileNotFound {
path: PathBuf,
},
InvalidPdf {
byte_offset: Option<u64>,
reason: String,
},
UnsupportedPdfVersion {
found: String,
supported_up_to: String,
},
PdfaValidationFailed {
profile: PdfAProfile,
violations: Vec<Violation>,
},
DecryptionFailed {
reason: DecryptionFailureReason,
},
InvalidSignature {
field: String,
reason: String,
},
FeatureNotInTier {
capability: Capability,
current_tier: Tier,
required_tier: Tier,
},
CapabilityNotCompiled {
capability: Capability,
feature_flag: &'static str,
},
InvalidLicense {
reason: String,
},
UnsupportedOnWasm {
operation: &'static str,
},
MissingDependency {
dep: &'static str,
install_hint: &'static str,
},
MemoryBudgetExceeded {
requested: usize,
limit: usize,
},
ResourceLimitExceeded {
kind: ResourceLimitKind,
observed: u64,
limit: u64,
},
Internal {
message: String,
crate_version: &'static str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecryptionFailureReason {
WrongPassword,
UnsupportedAlgorithm,
MalformedDictionary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ResourceLimitKind {
FileTooLarge,
StreamTooLarge,
ImageTooLarge,
ObjectDepthExceeded,
TooManyOperators,
XfaNestingTooDeep,
FormCalcRecursionTooDeep,
}
impl std::fmt::Display for ResourceLimitKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FileTooLarge => f.write_str("file too large"),
Self::StreamTooLarge => f.write_str("decompressed stream too large"),
Self::ImageTooLarge => f.write_str("image too large (pixel count)"),
Self::ObjectDepthExceeded => f.write_str("object reference depth exceeded"),
Self::TooManyOperators => f.write_str("content stream operator count exceeded"),
Self::XfaNestingTooDeep => f.write_str("XFA template nesting too deep"),
Self::FormCalcRecursionTooDeep => f.write_str("FormCalc recursion too deep"),
}
}
}
impl From<pdf_engine::LimitError> for Error {
fn from(e: pdf_engine::LimitError) -> Self {
use pdf_engine::LimitError as LE;
let (kind, observed, limit) = match e {
LE::FileTooLarge {
actual_bytes,
limit_bytes,
} => (ResourceLimitKind::FileTooLarge, actual_bytes, limit_bytes),
LE::StreamTooLarge {
actual_bytes,
limit_bytes,
} => (ResourceLimitKind::StreamTooLarge, actual_bytes, limit_bytes),
LE::ImageTooLarge {
pixels,
limit_pixels,
..
} => (ResourceLimitKind::ImageTooLarge, pixels, limit_pixels),
LE::ObjectDepthExceeded { depth, limit } => (
ResourceLimitKind::ObjectDepthExceeded,
depth as u64,
limit as u64,
),
LE::TooManyOperators { count, limit } => {
(ResourceLimitKind::TooManyOperators, count, limit)
}
LE::XfaNestingTooDeep { depth, limit } => (
ResourceLimitKind::XfaNestingTooDeep,
depth as u64,
limit as u64,
),
LE::FormCalcRecursionTooDeep { depth, limit } => (
ResourceLimitKind::FormCalcRecursionTooDeep,
depth as u64,
limit as u64,
),
};
Error::ResourceLimitExceeded {
kind,
observed,
limit,
}
}
}
impl std::fmt::Display for DecryptionFailureReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::WrongPassword => f.write_str("wrong password"),
Self::UnsupportedAlgorithm => f.write_str("unsupported encryption algorithm"),
Self::MalformedDictionary => f.write_str("malformed encryption dictionary"),
}
}
}
impl Error {
pub const fn code(&self) -> &'static str {
match self {
Error::Io { .. } => "E-IO-GENERIC",
Error::FileNotFound { .. } => "E-IO-FILE-NOT-FOUND",
Error::InvalidPdf { .. } => "E-PARSE-INVALID-PDF",
Error::UnsupportedPdfVersion { .. } => "E-PARSE-UNSUPPORTED-VERSION",
Error::PdfaValidationFailed { .. } => "E-COMPLIANCE-PDFA-INVALID",
Error::DecryptionFailed { .. } => "E-SECURITY-DECRYPTION-FAILED",
Error::InvalidSignature { .. } => "E-SECURITY-INVALID-SIGNATURE",
Error::FeatureNotInTier { .. } => "E-LICENSE-FEATURE-NOT-IN-TIER",
Error::CapabilityNotCompiled { .. } => "E-LICENSE-CAPABILITY-NOT-COMPILED",
Error::InvalidLicense { .. } => "E-LICENSE-INVALID",
Error::UnsupportedOnWasm { .. } => "E-ENV-UNSUPPORTED-ON-WASM",
Error::MissingDependency { .. } => "E-ENV-MISSING-DEPENDENCY",
Error::MemoryBudgetExceeded { .. } => "E-BUDGET-MEMORY-EXCEEDED",
Error::ResourceLimitExceeded { .. } => "E-BUDGET-RESOURCE-LIMIT",
Error::Internal { .. } => "E-INTERNAL",
}
}
pub const fn docs_url(&self) -> &'static str {
match self {
Error::Io { .. } => "https://pdfluent.com/errors/E-IO-GENERIC",
Error::FileNotFound { .. } => "https://pdfluent.com/errors/E-IO-FILE-NOT-FOUND",
Error::InvalidPdf { .. } => "https://pdfluent.com/errors/E-PARSE-INVALID-PDF",
Error::UnsupportedPdfVersion { .. } => {
"https://pdfluent.com/errors/E-PARSE-UNSUPPORTED-VERSION"
}
Error::PdfaValidationFailed { .. } => {
"https://pdfluent.com/errors/E-COMPLIANCE-PDFA-INVALID"
}
Error::DecryptionFailed { .. } => {
"https://pdfluent.com/errors/E-SECURITY-DECRYPTION-FAILED"
}
Error::InvalidSignature { .. } => {
"https://pdfluent.com/errors/E-SECURITY-INVALID-SIGNATURE"
}
Error::FeatureNotInTier { .. } => {
"https://pdfluent.com/errors/E-LICENSE-FEATURE-NOT-IN-TIER"
}
Error::CapabilityNotCompiled { .. } => {
"https://pdfluent.com/errors/E-LICENSE-CAPABILITY-NOT-COMPILED"
}
Error::InvalidLicense { .. } => "https://pdfluent.com/errors/E-LICENSE-INVALID",
Error::UnsupportedOnWasm { .. } => {
"https://pdfluent.com/errors/E-ENV-UNSUPPORTED-ON-WASM"
}
Error::MissingDependency { .. } => {
"https://pdfluent.com/errors/E-ENV-MISSING-DEPENDENCY"
}
Error::MemoryBudgetExceeded { .. } => {
"https://pdfluent.com/errors/E-BUDGET-MEMORY-EXCEEDED"
}
Error::ResourceLimitExceeded { .. } => {
"https://pdfluent.com/errors/E-BUDGET-RESOURCE-LIMIT"
}
Error::Internal { .. } => "https://pdfluent.com/errors/E-INTERNAL",
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Io { source, path } => match path {
Some(p) => write!(f, "I/O error on {}: {source}", p.display()),
None => write!(f, "I/O error: {source}"),
},
Error::FileNotFound { path } => write!(f, "File not found: {}", path.display()),
Error::InvalidPdf { byte_offset, reason } => match byte_offset {
Some(o) => write!(f, "Invalid PDF at byte {o}: {reason}"),
None => write!(f, "Invalid PDF: {reason}"),
},
Error::UnsupportedPdfVersion { found, supported_up_to } => write!(
f,
"Unsupported PDF version {found} (this build supports up to {supported_up_to})"
),
Error::PdfaValidationFailed { profile, violations } => write!(
f,
"PDF/A validation failed for profile {profile:?} with {} violation(s)",
violations.len()
),
Error::DecryptionFailed { reason } => write!(f, "Decryption failed: {reason}"),
Error::InvalidSignature { field, reason } => {
write!(f, "Signature '{field}' is invalid: {reason}")
}
Error::FeatureNotInTier {
capability,
current_tier,
required_tier,
} => write!(
f,
"Capability {capability:?} requires tier {required_tier:?}; current tier is {current_tier:?}.\n Upgrade: https://pdfluent.com/pricing\n Docs: {}",
self.docs_url()
),
Error::CapabilityNotCompiled {
capability,
feature_flag,
} => write!(
f,
"Capability {capability:?} requires the `{feature_flag}` Cargo feature, which is not enabled in this build.\n Docs: {}",
self.docs_url()
),
Error::InvalidLicense { reason } => {
write!(f, "Invalid license: {reason}\n Docs: {}", self.docs_url())
}
Error::UnsupportedOnWasm { operation } => write!(
f,
"Operation `{operation}` is not supported on wasm32 targets.\n Docs: {}",
self.docs_url()
),
Error::MissingDependency {
dep,
install_hint,
} => write!(
f,
"Missing dependency: {dep}.\n Install: {install_hint}\n Docs: {}",
self.docs_url()
),
Error::MemoryBudgetExceeded { requested, limit } => write!(
f,
"Memory budget exceeded: requested {requested} bytes, limit is {limit}"
),
Error::ResourceLimitExceeded {
kind,
observed,
limit,
} => write!(
f,
"Resource limit exceeded: {kind} (observed {observed}, limit {limit}).\n Docs: {}",
self.docs_url()
),
Error::Internal { message, crate_version } => write!(
f,
"Internal error (please report): {message} [pdfluent {crate_version}]"
),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io { source, .. } => Some(source),
_ => None,
}
}
}
pub(crate) fn internal_error(message: impl Into<String>) -> Error {
Error::Internal {
message: message.into(),
crate_version: env!("CARGO_PKG_VERSION"),
}
}
impl From<pdf_engine::EngineError> for Error {
fn from(e: pdf_engine::EngineError) -> Self {
use pdf_engine::EngineError as E;
match e {
E::Encrypted(_reason) => Error::DecryptionFailed {
reason: DecryptionFailureReason::WrongPassword,
},
E::InvalidPdf(reason) => Error::InvalidPdf {
byte_offset: None,
reason,
},
E::LimitExceeded(le) => Error::from(le),
other => Error::InvalidPdf {
byte_offset: None,
reason: format!("{other:?}"),
},
}
}
}
impl From<lopdf::Error> for Error {
fn from(e: lopdf::Error) -> Self {
Error::InvalidPdf {
byte_offset: None,
reason: e.to_string(),
}
}
}
impl From<pdf_manip::ManipError> for Error {
fn from(e: pdf_manip::ManipError) -> Self {
use pdf_manip::ManipError as M;
match e {
M::DecryptionFailed => Error::DecryptionFailed {
reason: DecryptionFailureReason::WrongPassword,
},
other => Error::InvalidPdf {
byte_offset: None,
reason: other.to_string(),
},
}
}
}
impl From<pdf_sign::SignError> for Error {
fn from(e: pdf_sign::SignError) -> Self {
use pdf_sign::SignError as S;
match e {
S::Pkcs12Load(reason)
| S::UnsupportedKeyType(reason)
| S::CmsBuild(reason)
| S::SigningFailed(reason) => Error::InvalidSignature {
field: "<signing>".into(),
reason,
},
S::NoPrivateKey => Error::InvalidSignature {
field: "<signing>".into(),
reason: "PKCS#12 identity contained no private key".into(),
},
S::NoCertificate => Error::InvalidSignature {
field: "<signing>".into(),
reason: "PKCS#12 identity contained no certificate".into(),
},
}
}
}
impl From<pdf_redact::RedactError> for Error {
fn from(e: pdf_redact::RedactError) -> Self {
Error::InvalidPdf {
byte_offset: None,
reason: e.to_string(),
}
}
}