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 crate::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}