1pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Debug, thiserror::Error)]
14pub enum Error {
15 #[error("parse: {0}")]
17 Core(#[from] pdfboss_core::Error),
18 #[error("io: {0}")]
20 Io(std::io::Error),
21 #[cfg(feature = "http")]
27 #[error("http{}: {msg}", status.map(|code| format!(" {code}")).unwrap_or_default())]
28 Http { status: Option<u16>, msg: String },
29 #[error("truncated read at offset {offset}: wanted {wanted} bytes, got {got}")]
32 TruncatedRead {
33 offset: u64,
34 wanted: usize,
35 got: usize,
36 },
37}
38
39impl From<std::io::Error> for Error {
40 fn from(inner: std::io::Error) -> Error {
41 #[cfg(feature = "http")]
42 if let Some(marker) = inner
43 .get_ref()
44 .and_then(|source| source.downcast_ref::<TransportMarker>())
45 {
46 return Error::Http {
47 status: marker.status,
48 msg: marker.msg.clone(),
49 };
50 }
51 Error::Io(inner)
52 }
53}
54
55impl From<Error> for pdfboss_core::Error {
62 fn from(e: Error) -> pdfboss_core::Error {
63 match e {
64 Error::Core(inner) => inner,
65 transport => pdfboss_core::Error::Transport(transport.to_string()),
66 }
67 }
68}
69
70#[cfg(feature = "http")]
74#[derive(Debug)]
75pub(crate) struct TransportMarker {
76 pub(crate) status: Option<u16>,
77 pub(crate) msg: String,
78}
79
80#[cfg(feature = "http")]
81impl std::fmt::Display for TransportMarker {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 write!(f, "http {:?}: {}", self.status, self.msg)
84 }
85}
86
87#[cfg(feature = "http")]
88impl std::error::Error for TransportMarker {}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn wraps_core_and_io_errors_with_layer_prefixes() {
96 let core = Error::from(pdfboss_core::Error::InvalidXref);
97 assert!(matches!(
98 core,
99 Error::Core(pdfboss_core::Error::InvalidXref)
100 ));
101 assert_eq!(
102 core.to_string(),
103 "parse: invalid or unrecoverable cross-reference data"
104 );
105 let io = Error::from(std::io::Error::other("boom"));
106 assert!(matches!(io, Error::Io(_)));
107 assert_eq!(io.to_string(), "io: boom");
108 }
109
110 #[test]
111 fn transport_variants_render_their_context() {
112 let err = Error::TruncatedRead {
113 offset: 512,
114 wanted: 100,
115 got: 3,
116 };
117 assert_eq!(
118 err.to_string(),
119 "truncated read at offset 512: wanted 100 bytes, got 3"
120 );
121 }
122
123 #[cfg(feature = "http")]
124 #[test]
125 fn http_error_renders_status_when_known_and_stays_prefixed_without_it() {
126 let with_status = Error::Http {
127 status: Some(404),
128 msg: "not found".to_string(),
129 };
130 assert_eq!(with_status.to_string(), "http 404: not found");
131 let without_status = Error::Http {
132 status: None,
133 msg: "connection refused".to_string(),
134 };
135 assert_eq!(without_status.to_string(), "http: connection refused");
136 }
137}