icap_rs/server/handler.rs
1//! Error type returned from user-defined route handlers.
2//!
3//! The server converts a [`HandlerError`] into an ICAP response — by default
4//! `500 Internal Server Error` — and logs the underlying source at `WARN`
5//! level. The connection stays open and is ready to serve the next request.
6//!
7//! `HandlerError` is intentionally not a `std::error::Error`: that lets it
8//! accept *any* `std::error::Error + Send + Sync + 'static` through a blanket
9//! `From` impl, so handlers can use `?` to propagate `crate::Error`,
10//! `std::io::Error`, or any user-defined error without manual mapping.
11//!
12//! # Example
13//!
14//! ```
15//! use icap_rs::{HandlerError, HandlerResult, IncomingRequest, Response, StatusCode};
16//!
17//! async fn handler(_req: IncomingRequest) -> HandlerResult<Response> {
18//! // Propagate a typed error from any layer — turns into 500 automatically.
19//! let resp = Response::no_content_with_istag("svc-1.0")?;
20//! Ok(resp)
21//! }
22//!
23//! async fn handler_bad_request(_req: IncomingRequest) -> HandlerResult<Response> {
24//! Err(HandlerError::new(StatusCode::BAD_REQUEST).with_message("missing URL"))
25//! }
26//! ```
27
28use std::fmt;
29
30use crate::{Response, StatusCode};
31
32/// Boxed `std::error::Error` carried as the source of a [`HandlerError`].
33pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
34
35/// Error returned from a route handler.
36///
37/// See the [module-level docs][self] for the full design rationale.
38#[must_use]
39pub struct HandlerError {
40 status: StatusCode,
41 message: Option<String>,
42 source: Option<BoxError>,
43}
44
45impl HandlerError {
46 /// Create a `HandlerError` with the given status and no source/message.
47 pub fn new(status: StatusCode) -> Self {
48 Self {
49 status,
50 message: None,
51 source: None,
52 }
53 }
54
55 /// Create a 500 Internal Server Error with a human-readable message.
56 pub fn internal(message: impl Into<String>) -> Self {
57 Self {
58 status: StatusCode::INTERNAL_SERVER_ERROR,
59 message: Some(message.into()),
60 source: None,
61 }
62 }
63
64 /// Override the ICAP status emitted to the client.
65 pub const fn with_status(mut self, status: StatusCode) -> Self {
66 self.status = status;
67 self
68 }
69
70 /// Attach a human-readable message used as the response reason phrase.
71 pub fn with_message(mut self, message: impl Into<String>) -> Self {
72 self.message = Some(message.into());
73 self
74 }
75
76 /// Attach an underlying error as the source (preserved in the error chain
77 /// for logging).
78 pub fn with_source(mut self, source: impl Into<BoxError>) -> Self {
79 self.source = Some(source.into());
80 self
81 }
82
83 /// The status code the server will send.
84 pub const fn status(&self) -> StatusCode {
85 self.status
86 }
87
88 /// The configured message, if any.
89 pub fn message(&self) -> Option<&str> {
90 self.message.as_deref()
91 }
92
93 /// The underlying source error, if any.
94 pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
95 self.source
96 .as_deref()
97 .map(|e| e as &(dyn std::error::Error + 'static))
98 }
99
100 /// Render the error as an ICAP `Response`.
101 ///
102 /// Uses [`Self::message`] as the reason phrase when set, otherwise the
103 /// canonical reason for the status code, otherwise `"Handler error"`.
104 pub(crate) fn into_response(self) -> Response {
105 let reason = self
106 .message
107 .as_deref()
108 .or_else(|| self.status.canonical_reason())
109 .unwrap_or("Handler error");
110 Response::new(self.status, reason)
111 }
112}
113
114impl fmt::Debug for HandlerError {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.debug_struct("HandlerError")
117 .field("status", &self.status.as_u16())
118 .field("message", &self.message)
119 .field("source", &self.source)
120 .finish()
121 }
122}
123
124impl fmt::Display for HandlerError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "handler error ({})", self.status.as_u16())?;
127 if let Some(msg) = &self.message {
128 write!(f, ": {msg}")?;
129 }
130 if let Some(src) = &self.source {
131 write!(f, ": {src}")?;
132 }
133 Ok(())
134 }
135}
136
137// Anyhow-style blanket conversion. Works because `HandlerError` itself is
138// intentionally NOT a `std::error::Error`, so no reflexive collision arises.
139impl<E> From<E> for HandlerError
140where
141 E: std::error::Error + Send + Sync + 'static,
142{
143 fn from(err: E) -> Self {
144 Self {
145 status: StatusCode::INTERNAL_SERVER_ERROR,
146 message: None,
147 source: Some(Box::new(err)),
148 }
149 }
150}
151
152/// Convenient alias for results returned from route handlers.
153pub type HandlerResult<T> = Result<T, HandlerError>;