1use http::header::{InvalidHeaderName, InvalidHeaderValue};
24use http::{Error as HttpError, header::ToStrError};
25use std::error::Error as StdError;
26use std::fmt;
27use std::io;
28use std::str::Utf8Error;
29use std::string::FromUtf8Error;
30use std::time::Duration;
31use thiserror::Error;
32
33pub type BoxError = Box<dyn StdError + Send + Sync + 'static>;
35
36#[derive(Debug, Error)]
38#[non_exhaustive]
39pub enum Error {
40 #[error("I/O error: {0}")]
42 Io(#[source] io::Error),
43
44 #[error(transparent)]
46 Timeout(#[from] TimeoutError),
47
48 #[error(transparent)]
50 Protocol(#[from] ProtocolError),
51
52 #[error(transparent)]
55 Config(#[from] ConfigError),
56
57 #[cfg(feature = "tls-rustls")]
59 #[error(transparent)]
60 Tls(#[from] crate::tls::TlsError),
61
62 #[error("HTTP builder error: {0}")]
64 Http(#[from] HttpError),
65
66 #[error("external error: {0}")]
69 External(#[source] BoxError),
70}
71
72impl Error {
73 pub const fn is_io(&self) -> bool {
75 matches!(self, Self::Io(_))
76 }
77
78 pub const fn is_timeout(&self) -> bool {
80 matches!(self, Self::Timeout(_))
81 }
82
83 pub const fn is_protocol(&self) -> bool {
85 matches!(self, Self::Protocol(_))
86 }
87
88 pub const fn is_body_too_large(&self) -> bool {
90 matches!(self, Self::Protocol(ProtocolError::BodyTooLarge { .. }))
91 }
92
93 pub const fn is_config(&self) -> bool {
95 matches!(self, Self::Config(_))
96 }
97
98 pub const fn is_early_close(&self) -> bool {
101 matches!(self, Self::Protocol(ProtocolError::EarlyClose))
102 }
103
104 pub const fn is_retryable(&self) -> bool {
111 if self.is_early_close() {
112 return true;
113 }
114 if let Self::Timeout(t) = self {
115 return matches!(t.kind, TimeoutKind::ClientConnect | TimeoutKind::ServerIdle);
116 }
117 false
118 }
119
120 pub fn parse(message: impl Into<String>) -> Self {
122 Self::Protocol(ProtocolError::Parse(message.into()))
123 }
124
125 pub fn http_parse(message: impl Into<String>) -> Self {
127 Self::Protocol(ProtocolError::HttpParse(message.into()))
128 }
129
130 pub fn header(message: impl Into<String>) -> Self {
132 Self::Protocol(ProtocolError::Header(message.into()))
133 }
134
135 pub fn body(message: impl Into<String>) -> Self {
137 Self::Protocol(ProtocolError::Body(message.into()))
138 }
139
140 pub const fn body_too_large(size: usize, max: usize) -> Self {
142 Self::Protocol(ProtocolError::BodyTooLarge { size, max })
143 }
144
145 pub fn serialization(message: impl Into<String>) -> Self {
147 Self::Protocol(ProtocolError::Serialization(message.into()))
148 }
149
150 pub fn service(message: impl Into<String>) -> Self {
152 Self::Config(ConfigError::Other(message.into()))
153 }
154
155 pub const fn missing_header(name: &'static str) -> Self {
157 Self::Protocol(ProtocolError::MissingHeader(name))
158 }
159
160 pub fn invalid_istag(value: impl Into<String>) -> Self {
162 Self::Protocol(ProtocolError::InvalidISTag(value.into()))
163 }
164
165 pub fn invalid_status_code(value: impl Into<String>) -> Self {
167 Self::Protocol(ProtocolError::invalid(ProtocolField::StatusCode, value))
168 }
169
170 pub fn invalid_method(value: impl Into<String>) -> Self {
172 Self::Protocol(ProtocolError::invalid(ProtocolField::Method, value))
173 }
174
175 pub fn invalid_uri(value: impl Into<String>) -> Self {
177 Self::Protocol(ProtocolError::invalid(ProtocolField::Uri, value))
178 }
179
180 pub fn invalid_version(value: impl Into<String>) -> Self {
182 Self::Protocol(ProtocolError::invalid(ProtocolField::Version, value))
183 }
184
185 pub fn external<E>(err: E) -> Self
187 where
188 E: StdError + Send + Sync + 'static,
189 {
190 Self::External(Box::new(err))
191 }
192
193 pub fn unexpected(message: impl Into<String>) -> Self {
195 Self::External(Box::<MessageError>::new(MessageError(message.into())))
196 }
197
198 pub const fn timeout(kind: TimeoutKind, duration: Duration) -> Self {
200 Self::Timeout(TimeoutError { kind, duration })
201 }
202
203 pub const fn client_total_timeout(d: Duration) -> Self {
204 Self::timeout(TimeoutKind::ClientTotal, d)
205 }
206 pub const fn client_connect_timeout(d: Duration) -> Self {
207 Self::timeout(TimeoutKind::ClientConnect, d)
208 }
209 pub const fn client_write_timeout(d: Duration) -> Self {
210 Self::timeout(TimeoutKind::ClientWrite, d)
211 }
212 pub const fn client_continue_timeout(d: Duration) -> Self {
213 Self::timeout(TimeoutKind::ClientContinue, d)
214 }
215 pub const fn server_header_read_timeout(d: Duration) -> Self {
216 Self::timeout(TimeoutKind::ServerHeaderRead, d)
217 }
218 pub const fn server_body_read_timeout(d: Duration) -> Self {
219 Self::timeout(TimeoutKind::ServerBodyRead, d)
220 }
221 pub const fn server_write_timeout(d: Duration) -> Self {
222 Self::timeout(TimeoutKind::ServerWrite, d)
223 }
224 pub const fn server_idle_timeout(d: Duration) -> Self {
225 Self::timeout(TimeoutKind::ServerIdle, d)
226 }
227}
228
229impl From<io::Error> for Error {
230 fn from(err: io::Error) -> Self {
231 Self::Io(err)
232 }
233}
234
235impl From<Utf8Error> for Error {
236 fn from(e: Utf8Error) -> Self {
237 Self::Protocol(ProtocolError::Utf8(e))
238 }
239}
240
241impl From<FromUtf8Error> for Error {
242 fn from(e: FromUtf8Error) -> Self {
243 Self::Protocol(ProtocolError::FromUtf8(e))
244 }
245}
246
247impl From<InvalidHeaderName> for Error {
248 fn from(e: InvalidHeaderName) -> Self {
249 Self::Protocol(ProtocolError::HeaderName(e))
250 }
251}
252
253impl From<InvalidHeaderValue> for Error {
254 fn from(e: InvalidHeaderValue) -> Self {
255 Self::Protocol(ProtocolError::HeaderValue(e))
256 }
257}
258
259impl From<ToStrError> for Error {
260 fn from(e: ToStrError) -> Self {
261 Self::Protocol(ProtocolError::HeaderToStr(e))
262 }
263}
264
265#[derive(Debug, Error)]
268#[error("{kind} timed out after {duration:?}")]
269pub struct TimeoutError {
270 pub kind: TimeoutKind,
271 pub duration: Duration,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276#[non_exhaustive]
277pub enum TimeoutKind {
278 ClientTotal,
280 ClientConnect,
282 ClientWrite,
284 ClientContinue,
286 ServerHeaderRead,
288 ServerBodyRead,
290 ServerWrite,
292 ServerIdle,
294}
295
296impl fmt::Display for TimeoutKind {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 let s = match self {
299 Self::ClientTotal => "client request",
300 Self::ClientConnect => "client TCP connect",
301 Self::ClientWrite => "client write",
302 Self::ClientContinue => "client 100-continue wait",
303 Self::ServerHeaderRead => "server header read",
304 Self::ServerBodyRead => "server body read",
305 Self::ServerWrite => "server write",
306 Self::ServerIdle => "server idle keep-alive",
307 };
308 f.write_str(s)
309 }
310}
311
312#[derive(Debug, Error)]
314#[non_exhaustive]
315pub enum ProtocolError {
316 #[error("peer closed before ICAP headers")]
318 EarlyClose,
319
320 #[error("ICAP parse error: {0}")]
322 Parse(String),
323
324 #[error("HTTP parse error: {0}")]
326 HttpParse(String),
327
328 #[error("missing required header: {0}")]
330 MissingHeader(&'static str),
331
332 #[error("header error: {0}")]
335 Header(String),
336
337 #[error("body error: {0}")]
339 Body(String),
340
341 #[error("body too large: {size} bytes (max {max})")]
343 BodyTooLarge { size: usize, max: usize },
344
345 #[error("invalid {field}: {value}")]
347 InvalidField { field: ProtocolField, value: String },
348
349 #[error("invalid ISTag: {0}")]
351 InvalidISTag(String),
352
353 #[error("serialization error: {0}")]
355 Serialization(String),
356
357 #[error("UTF-8 error: {0}")]
359 Utf8(#[from] Utf8Error),
360
361 #[error("UTF-8 conversion error: {0}")]
363 FromUtf8(#[from] FromUtf8Error),
364
365 #[error("invalid HTTP header name: {0}")]
367 HeaderName(#[from] InvalidHeaderName),
368
369 #[error("invalid HTTP header value: {0}")]
371 HeaderValue(#[from] InvalidHeaderValue),
372
373 #[error("invalid HTTP header text: {0}")]
375 HeaderToStr(#[from] ToStrError),
376}
377
378impl ProtocolError {
379 pub fn invalid(field: ProtocolField, value: impl Into<String>) -> Self {
381 Self::InvalidField {
382 field,
383 value: value.into(),
384 }
385 }
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391#[non_exhaustive]
392pub enum ProtocolField {
393 StatusCode,
394 Method,
395 Uri,
396 Version,
397}
398
399impl fmt::Display for ProtocolField {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 let s = match self {
402 Self::StatusCode => "status code",
403 Self::Method => "method",
404 Self::Uri => "URI",
405 Self::Version => "protocol version",
406 };
407 f.write_str(s)
408 }
409}
410
411#[derive(Debug, Error)]
414#[non_exhaustive]
415pub enum ConfigError {
416 #[error("service '{service}' has no handlers")]
418 ServiceWithoutHandlers { service: String },
419
420 #[error("service '{service}' must configure ServiceOptions with an explicit ISTag")]
422 MissingServiceOptions { service: String },
423
424 #[error("invalid options for service '{service}': {reason}")]
426 InvalidServiceOptions { service: String, reason: String },
427
428 #[error("default service '{name}' resolves to unknown service '{resolved}'")]
430 UnknownDefaultService { name: String, resolved: String },
431
432 #[error("alias '{from}' resolves to unknown service '{resolved}'")]
434 UnknownAlias { from: String, resolved: String },
435
436 #[error("{0}")]
438 Other(String),
439}
440
441#[derive(Debug)]
442struct MessageError(String);
443
444impl fmt::Display for MessageError {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 f.write_str(&self.0)
447 }
448}
449
450impl StdError for MessageError {}
451
452pub type IcapResult<T> = Result<T, Error>;
454
455pub trait ToIcapResult<T> {
459 fn to_icap_result(self) -> IcapResult<T>;
460}
461
462impl<T, E> ToIcapResult<T> for Result<T, E>
463where
464 E: StdError + Send + Sync + 'static,
465{
466 fn to_icap_result(self) -> IcapResult<T> {
467 self.map_err(Error::external)
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn header_value_conversion_routes_through_protocol() {
477 let err: Error = http::HeaderValue::from_str("bad\r\nvalue")
478 .expect_err("header value must be rejected")
479 .into();
480 assert!(matches!(
481 err,
482 Error::Protocol(ProtocolError::HeaderValue(_))
483 ));
484 assert!(StdError::source(&err).is_some());
485 }
486
487 #[test]
488 fn http_builder_conversion_preserves_source_error() {
489 let err: Error = http::Request::builder()
490 .method("bad method")
491 .body(())
492 .expect_err("invalid method must be rejected")
493 .into();
494 assert!(matches!(err, Error::Http(_)));
495 assert!(StdError::source(&err).is_some());
496 }
497
498 #[test]
499 fn to_icap_result_wraps_in_external() {
500 let result: Result<(), io::Error> = Err(io::Error::other("external"));
501 let err = result.to_icap_result().expect_err("external error");
502 assert!(matches!(err, Error::External(_)));
503 assert!(StdError::source(&err).is_some());
504 }
505
506 #[test]
507 fn timeout_helpers_set_kind() {
508 let err = Error::server_write_timeout(Duration::from_millis(5));
509 match err {
510 Error::Timeout(t) => assert_eq!(t.kind, TimeoutKind::ServerWrite),
511 _ => panic!("expected Timeout variant"),
512 }
513 }
514
515 #[test]
516 fn classifiers_agree_with_variant() {
517 let t = Error::client_connect_timeout(Duration::from_millis(1));
518 assert!(t.is_timeout());
519 assert!(t.is_retryable());
520
521 let p = Error::parse("bad");
522 assert!(p.is_protocol());
523 assert!(!p.is_retryable());
524
525 let e = Error::Protocol(ProtocolError::EarlyClose);
526 assert!(e.is_early_close());
527 assert!(e.is_retryable());
528 }
529}