Skip to main content

doido_controller/
rescue.rs

1//! `rescue_from`: map specific error types to responses instead of a blanket
2//! `500` (Rails `rescue_from RecordNotFound, with: :not_found`).
3//!
4//! Actions return `Result<Response, anyhow::Error>`; on an `Err`, the registered
5//! handlers are tried in order by downcasting the error to each handler's type.
6//! The first that matches produces the response; if none match, the caller falls
7//! back to the default `500`.
8
9use axum::response::Response;
10use doido_core::anyhow;
11
12type Handler = Box<dyn Fn(&anyhow::Error) -> Option<Response> + Send + Sync>;
13
14/// An ordered set of typed error handlers.
15#[derive(Default)]
16pub struct RescueHandlers {
17    handlers: Vec<Handler>,
18}
19
20impl RescueHandlers {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Register a handler for error type `E`. When a rescued error downcasts to
26    /// `E`, `handler` renders the response.
27    pub fn on<E>(mut self, handler: impl Fn(&E) -> Response + Send + Sync + 'static) -> Self
28    where
29        E: std::error::Error + Send + Sync + 'static,
30    {
31        self.handlers
32            .push(Box::new(move |err| err.downcast_ref::<E>().map(&handler)));
33        self
34    }
35
36    /// Try each handler in registration order; return the first match, if any.
37    pub fn rescue(&self, err: &anyhow::Error) -> Option<Response> {
38        self.handlers.iter().find_map(|h| h(err))
39    }
40}