Skip to main content

velvet_web/
errors.rs

1use std::{env::VarError, io};
2
3use askama::Template;
4use axum::{
5    body::Body,
6    http::Response,
7    response::{Html, IntoResponse, Redirect},
8};
9use reqwest::StatusCode;
10use tracing::error;
11
12pub type AppResult<T> = Result<T, AppError>;
13
14pub struct TemplateResponse<T: Template>(pub T);
15
16impl<T: Template> IntoResponse for TemplateResponse<T> {
17    fn into_response(self) -> Response<Body> {
18        match self.0.render() {
19            Ok(resp) => (StatusCode::OK, Html(resp)).into_response(),
20            Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("error, {e}")).into_response(),
21        }
22    }
23}
24
25/// This is a general Application error.
26/// This error is already converted from typical usage of the dependencies of the stack.
27#[derive(Debug)]
28pub struct AppError {
29    status: StatusCode,
30    error: anyhow::Error,
31    redirect: Option<Redirect>,
32}
33
34impl std::error::Error for AppError {}
35
36impl std::fmt::Display for AppError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.write_str(self.error.to_string().as_str())
39    }
40}
41
42impl From<&str> for AppError {
43    fn from(value: &str) -> Self {
44        Self {
45            status: StatusCode::INTERNAL_SERVER_ERROR,
46            error: anyhow::anyhow!(value.to_string()),
47            redirect: None,
48        }
49    }
50}
51
52impl From<Redirect> for AppError {
53    fn from(redirect: Redirect) -> Self {
54        Self {
55            status: StatusCode::PERMANENT_REDIRECT,
56            error: anyhow::anyhow!("None"),
57            redirect: Some(redirect),
58        }
59    }
60}
61
62impl From<io::Error> for AppError {
63    fn from(value: io::Error) -> Self {
64        Self {
65            status: StatusCode::INTERNAL_SERVER_ERROR,
66            error: value.into(),
67            redirect: None,
68        }
69    }
70}
71
72#[cfg(any(feature = "postgres", feature = "mysql", feature = "sqlite"))]
73impl From<sqlx::Error> for AppError {
74    fn from(value: sqlx::Error) -> Self {
75        Self {
76            status: StatusCode::INTERNAL_SERVER_ERROR,
77            error: value.into(),
78            redirect: None,
79        }
80    }
81}
82
83#[cfg(any(feature = "postgres", feature = "mysql", feature = "sqlite"))]
84impl From<sqlx::migrate::MigrateError> for AppError {
85    fn from(value: sqlx::migrate::MigrateError) -> Self {
86        Self {
87            status: StatusCode::INTERNAL_SERVER_ERROR,
88            error: value.into(),
89            redirect: None,
90        }
91    }
92}
93
94impl From<reqwest::Error> for AppError {
95    fn from(value: reqwest::Error) -> Self {
96        Self {
97            status: StatusCode::INTERNAL_SERVER_ERROR,
98            error: value.into(),
99            redirect: None,
100        }
101    }
102}
103
104impl From<anyhow::Error> for AppError {
105    fn from(value: anyhow::Error) -> Self {
106        Self {
107            status: StatusCode::INTERNAL_SERVER_ERROR,
108            error: value,
109            redirect: None,
110        }
111    }
112}
113
114impl From<VarError> for AppError {
115    fn from(value: VarError) -> Self {
116        Self {
117            status: StatusCode::INTERNAL_SERVER_ERROR,
118            error: value.into(),
119            redirect: None,
120        }
121    }
122}
123
124impl From<StatusCode> for AppError {
125    fn from(status: StatusCode) -> Self {
126        Self {
127            status,
128            error: anyhow::Error::msg(status.canonical_reason().unwrap_or("")),
129            redirect: None,
130        }
131    }
132}
133
134#[cfg(feature = "login")]
135impl From<argon2::password_hash::Error> for AppError {
136    fn from(error: argon2::password_hash::Error) -> Self {
137        Self {
138            status: StatusCode::INTERNAL_SERVER_ERROR,
139            error: error.into(),
140            redirect: None,
141        }
142    }
143}
144
145impl From<lettre::error::Error> for AppError {
146    fn from(value: lettre::error::Error) -> Self {
147        Self {
148            status: StatusCode::INTERNAL_SERVER_ERROR,
149            error: value.into(),
150            redirect: None,
151        }
152    }
153}
154
155impl From<lettre::transport::smtp::Error> for AppError {
156    fn from(value: lettre::transport::smtp::Error) -> Self {
157        Self {
158            status: StatusCode::INTERNAL_SERVER_ERROR,
159            error: value.into(),
160            redirect: None,
161        }
162    }
163}
164
165impl From<lettre::address::AddressError> for AppError {
166    fn from(value: lettre::address::AddressError) -> Self {
167        Self {
168            status: StatusCode::INTERNAL_SERVER_ERROR,
169            error: value.into(),
170            redirect: None,
171        }
172    }
173}
174
175impl From<askama::Error> for AppError {
176    fn from(value: askama::Error) -> Self {
177        Self {
178            status: StatusCode::INTERNAL_SERVER_ERROR,
179            error: value.into(),
180            redirect: None,
181        }
182    }
183}
184
185impl IntoResponse for AppError {
186    fn into_response(self) -> Response<Body> {
187        if let Some(r) = self.redirect {
188            return r.into_response();
189        }
190        error!("Status: {}, Error: {}", self.status, self.error);
191        (self.status, "Error").into_response()
192    }
193}