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#[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}