use std::io;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PdfError {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("PDF parse error: {0}")]
Parse(String),
#[error("Invalid page index: {0}")]
InvalidPage(usize),
#[error("Unsupported feature: {0}")]
UnsupportedFeature(String),
#[error("Encryption error: {0}")]
Encryption(String),
#[error("Resource limit exceeded for {resource}: actual {actual}, limit {limit}")]
ResourceLimitExceeded {
resource: &'static str,
limit: u64,
actual: u64,
},
#[error("security violation: {0}")]
SecurityViolation(String),
#[error("signature error: {0}")]
Signature(String),
#[error("{0}")]
Other(String),
}
impl PdfError {
#[must_use]
pub const fn code(&self) -> PdfErrorCode {
match self {
Self::Io(_) => PdfErrorCode::Io,
Self::Parse(_) => PdfErrorCode::Parse,
Self::InvalidPage(_) => PdfErrorCode::InvalidPage,
Self::UnsupportedFeature(_) => PdfErrorCode::UnsupportedFeature,
Self::Encryption(_) => PdfErrorCode::Encryption,
Self::ResourceLimitExceeded { .. } => PdfErrorCode::ResourceLimitExceeded,
Self::SecurityViolation(_) => PdfErrorCode::SecurityViolation,
Self::Signature(_) => PdfErrorCode::Signature,
Self::Other(_) => PdfErrorCode::Other,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PdfErrorCode {
Io,
Parse,
InvalidPage,
UnsupportedFeature,
Encryption,
ResourceLimitExceeded,
SecurityViolation,
Signature,
Other,
}
pub type Result<T, E = PdfError> = std::result::Result<T, E>;
#[cfg(test)]
#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn io_error_display() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file missing");
let err = PdfError::Io(io_err);
assert!(format!("{}", err).contains("I/O error"));
assert!(format!("{}", err).contains("file missing"));
}
#[test]
fn io_error_from_conversion() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
let err: PdfError = io_err.into();
assert!(matches!(err, PdfError::Io(_)));
}
#[test]
fn parse_error_display() {
let err = PdfError::Parse("bad header".to_string());
assert_eq!(format!("{}", err), "PDF parse error: bad header");
}
#[test]
fn invalid_page_display() {
let err = PdfError::InvalidPage(42);
assert_eq!(format!("{}", err), "Invalid page index: 42");
}
#[test]
fn unsupported_feature_display() {
let err = PdfError::UnsupportedFeature("encryption".to_string());
assert_eq!(format!("{}", err), "Unsupported feature: encryption");
}
#[test]
fn encryption_error_display() {
let err = PdfError::Encryption("wrong password".to_string());
assert_eq!(format!("{}", err), "Encryption error: wrong password");
}
#[test]
fn resource_limit_exceeded_display() {
let err = PdfError::ResourceLimitExceeded {
resource: "input_bytes",
limit: 1024,
actual: 2048,
};
let msg = format!("{}", err);
assert!(msg.contains("input_bytes"));
assert!(msg.contains("1024"));
assert!(msg.contains("2048"));
}
#[test]
fn security_violation_display() {
let err = PdfError::SecurityViolation("SSRF blocked".to_string());
assert_eq!(format!("{}", err), "security violation: SSRF blocked");
}
#[test]
fn signature_error_display() {
let err = PdfError::Signature("invalid cert".to_string());
assert_eq!(format!("{}", err), "signature error: invalid cert");
}
#[test]
fn other_error_display() {
let err = PdfError::Other("something".to_string());
assert_eq!(format!("{}", err), "something");
}
#[test]
fn code_io() {
let err = PdfError::Io(io::Error::other("x"));
assert_eq!(err.code(), PdfErrorCode::Io);
}
#[test]
fn code_parse() {
let err = PdfError::Parse("x".to_string());
assert_eq!(err.code(), PdfErrorCode::Parse);
}
#[test]
fn code_invalid_page() {
let err = PdfError::InvalidPage(0);
assert_eq!(err.code(), PdfErrorCode::InvalidPage);
}
#[test]
fn code_unsupported_feature() {
let err = PdfError::UnsupportedFeature("x".to_string());
assert_eq!(err.code(), PdfErrorCode::UnsupportedFeature);
}
#[test]
fn code_encryption() {
let err = PdfError::Encryption("x".to_string());
assert_eq!(err.code(), PdfErrorCode::Encryption);
}
#[test]
fn code_resource_limit_exceeded() {
let err = PdfError::ResourceLimitExceeded {
resource: "pages",
limit: 100,
actual: 200,
};
assert_eq!(err.code(), PdfErrorCode::ResourceLimitExceeded);
}
#[test]
fn code_security_violation() {
let err = PdfError::SecurityViolation("x".to_string());
assert_eq!(err.code(), PdfErrorCode::SecurityViolation);
}
#[test]
fn code_signature() {
let err = PdfError::Signature("x".to_string());
assert_eq!(err.code(), PdfErrorCode::Signature);
}
#[test]
fn code_other() {
let err = PdfError::Other("x".to_string());
assert_eq!(err.code(), PdfErrorCode::Other);
}
#[test]
fn error_code_clone() {
let code = PdfErrorCode::Io;
let cloned = code;
assert_eq!(code, cloned);
}
#[test]
fn error_code_debug() {
assert_eq!(format!("{:?}", PdfErrorCode::Parse), "Parse");
assert_eq!(format!("{:?}", PdfErrorCode::Io), "Io");
}
#[test]
fn error_code_eq() {
assert_eq!(PdfErrorCode::Io, PdfErrorCode::Io);
assert_ne!(PdfErrorCode::Io, PdfErrorCode::Parse);
}
#[test]
fn error_code_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(PdfErrorCode::Io);
set.insert(PdfErrorCode::Io);
set.insert(PdfErrorCode::Parse);
assert_eq!(set.len(), 2);
}
#[test]
fn debug_format_all_variants() {
let errors = vec![
PdfError::Parse("p".to_string()),
PdfError::InvalidPage(0),
PdfError::UnsupportedFeature("u".to_string()),
PdfError::Encryption("e".to_string()),
PdfError::SecurityViolation("s".to_string()),
PdfError::Signature("s".to_string()),
PdfError::Other("o".to_string()),
];
for err in errors {
let _ = format!("{:?}", err);
}
}
}