pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("parse: {0}")]
Core(#[from] pdfboss_core::Error),
#[error("io: {0}")]
Io(std::io::Error),
#[cfg(feature = "http")]
#[error("http{}: {msg}", status.map(|code| format!(" {code}")).unwrap_or_default())]
Http { status: Option<u16>, msg: String },
#[error("truncated read at offset {offset}: wanted {wanted} bytes, got {got}")]
TruncatedRead {
offset: u64,
wanted: usize,
got: usize,
},
}
impl From<std::io::Error> for Error {
fn from(inner: std::io::Error) -> Error {
#[cfg(feature = "http")]
if let Some(marker) = inner
.get_ref()
.and_then(|source| source.downcast_ref::<TransportMarker>())
{
return Error::Http {
status: marker.status,
msg: marker.msg.clone(),
};
}
Error::Io(inner)
}
}
impl From<Error> for pdfboss_core::Error {
fn from(e: Error) -> pdfboss_core::Error {
match e {
Error::Core(inner) => inner,
transport => pdfboss_core::Error::Transport(transport.to_string()),
}
}
}
#[cfg(feature = "http")]
#[derive(Debug)]
pub(crate) struct TransportMarker {
pub(crate) status: Option<u16>,
pub(crate) msg: String,
}
#[cfg(feature = "http")]
impl std::fmt::Display for TransportMarker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "http {:?}: {}", self.status, self.msg)
}
}
#[cfg(feature = "http")]
impl std::error::Error for TransportMarker {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wraps_core_and_io_errors_with_layer_prefixes() {
let core = Error::from(pdfboss_core::Error::InvalidXref);
assert!(matches!(
core,
Error::Core(pdfboss_core::Error::InvalidXref)
));
assert_eq!(
core.to_string(),
"parse: invalid or unrecoverable cross-reference data"
);
let io = Error::from(std::io::Error::other("boom"));
assert!(matches!(io, Error::Io(_)));
assert_eq!(io.to_string(), "io: boom");
}
#[test]
fn transport_variants_render_their_context() {
let err = Error::TruncatedRead {
offset: 512,
wanted: 100,
got: 3,
};
assert_eq!(
err.to_string(),
"truncated read at offset 512: wanted 100 bytes, got 3"
);
}
#[cfg(feature = "http")]
#[test]
fn http_error_renders_status_when_known_and_stays_prefixed_without_it() {
let with_status = Error::Http {
status: Some(404),
msg: "not found".to_string(),
};
assert_eq!(with_status.to_string(), "http 404: not found");
let without_status = Error::Http {
status: None,
msg: "connection refused".to_string(),
};
assert_eq!(without_status.to_string(), "http: connection refused");
}
}