ctoolbox/io/webui/
error.rs

1//! Anyhow error handling support for Axum.
2//! Based on:
3//! <https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs>
4/* MIT License
5
6Copyright (c) 2019–2025 axum Contributors
7
8Permission is hereby granted, free of charge, to any
9person obtaining a copy of this software and associated
10documentation files (the "Software"), to deal in the
11Software without restriction, including without
12limitation the rights to use, copy, modify, merge,
13publish, distribute, sublicense, and/or sell copies of
14the Software, and to permit persons to whom the Software
15is furnished to do so, subject to the following
16conditions:
17
18The above copyright notice and this permission notice
19shall be included in all copies or substantial portions
20of the Software.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
23ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
24TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
25PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
26SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
29IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30DEALINGS IN THE SOFTWARE.*/
31
32use crate::io::webui::AppState;
33use crate::io::webui::RequestState;
34use crate::io::webui::error_500;
35use axum::response::{IntoResponse, Response};
36
37/// A custom result type for web handlers.
38pub type WebResult<T> = std::result::Result<T, WebError>;
39
40// Make our own error that wraps `anyhow::Error` and includes state.
41pub struct WebError {
42    error: anyhow::Error,
43    state: AppState,
44    req: RequestState,
45}
46
47impl WebError {
48    /// Create a new `WebError` with the given error, state, and request.
49    pub fn new<E>(error: E, state: AppState, req: RequestState) -> Self
50    where
51        E: Into<anyhow::Error>,
52    {
53        Self {
54            error: error.into(),
55            state,
56            req,
57        }
58    }
59}
60
61// Tell axum how to convert `WebError` into a response.
62impl IntoResponse for WebError {
63    fn into_response(self) -> Response {
64        error_500(&self.state, &self.req, self.error)
65    }
66}
67
68/// A trait to extend `Result` and `Option` with error handling that injects `AppState` and `RequestState`.
69pub trait WebErr<T> {
70    fn web_err(self, state: &AppState, req: &RequestState) -> WebResult<T>;
71}
72
73impl<T, E> WebErr<T> for Result<T, E>
74where
75    E: Into<anyhow::Error>,
76{
77    fn web_err(self, state: &AppState, req: &RequestState) -> WebResult<T> {
78        self.map_err(|e| WebError::new(e, state.clone(), req.clone()))
79    }
80}
81
82impl<T> WebErr<T> for Option<T> {
83    fn web_err(self, state: &AppState, req: &RequestState) -> WebResult<T> {
84        self.ok_or_else(|| {
85            WebError::new(
86                anyhow::anyhow!("Unexpected None"),
87                state.clone(),
88                req.clone(),
89            )
90        })
91    }
92}