allowthem_server/
browser_error.rs1use axum::http::StatusCode;
2use axum::response::{Html, IntoResponse, Response};
3
4const FALLBACK_ERROR_HTML: &str = r#"<!DOCTYPE html>
7<html lang="en">
8<head>
9 <meta charset="utf-8">
10 <meta name="viewport" content="width=device-width, initial-scale=1">
11 <title>{{TITLE}} — allowthem</title>
12 <link rel="stylesheet" href="/static/wavefunk/css/wavefunk.css">
13</head>
14<body class="wf-auth" style="display:flex;align-items:center;justify-content:center;min-height:100vh">
15 <main class="wf-auth-form" style="max-width:460px;width:100%">
16 <div class="wf-auth-wrap">
17 <h1>{{TITLE}}</h1>
18 <p class="wf-auth-sub">{{MESSAGE}}</p>
19 <p class="wf-caption wf-mt-5"><a href="/">Return home</a></p>
20 </div>
21 </main>
22</body>
23</html>"#;
24
25fn html_escape(s: &str) -> String {
28 let mut out = String::with_capacity(s.len());
29 for ch in s.chars() {
30 match ch {
31 '&' => out.push_str("&"),
32 '<' => out.push_str("<"),
33 '>' => out.push_str(">"),
34 '"' => out.push_str("""),
35 '\'' => out.push_str("'"),
36 _ => out.push(ch),
37 }
38 }
39 out
40}
41
42pub fn render_error_page(title: &str, message: &str) -> String {
48 FALLBACK_ERROR_HTML
49 .replace("{{TITLE}}", &html_escape(title))
50 .replace("{{MESSAGE}}", &html_escape(message))
51}
52
53#[derive(Debug)]
54pub enum BrowserError {
55 Ui(wavefunk_ui::askama::Error),
56 Auth(allowthem_core::AuthError),
57}
58
59impl From<wavefunk_ui::askama::Error> for BrowserError {
60 fn from(err: wavefunk_ui::askama::Error) -> Self {
61 BrowserError::Ui(err)
62 }
63}
64
65impl From<allowthem_core::AuthError> for BrowserError {
66 fn from(err: allowthem_core::AuthError) -> Self {
67 BrowserError::Auth(err)
68 }
69}
70
71impl IntoResponse for BrowserError {
72 fn into_response(self) -> Response {
73 match self {
74 BrowserError::Ui(e) => {
75 tracing::error!(error = %e, "UI render failed");
76 let html = render_error_page(
77 "Internal error",
78 "Something went wrong while rendering this page.",
79 );
80 (StatusCode::INTERNAL_SERVER_ERROR, Html(html)).into_response()
81 }
82 BrowserError::Auth(allowthem_core::AuthError::NotFound) => {
83 let html = render_error_page(
84 "Not found",
85 "The page you are looking for could not be found.",
86 );
87 (StatusCode::NOT_FOUND, Html(html)).into_response()
88 }
89 BrowserError::Auth(allowthem_core::AuthError::Validation(msg)) => {
90 tracing::warn!(error = %msg, "validation error");
91 let html = render_error_page("Validation error", &msg);
92 (StatusCode::UNPROCESSABLE_ENTITY, Html(html)).into_response()
93 }
94 BrowserError::Auth(e) => {
95 tracing::error!(error = %e, "auth error");
96 let html = render_error_page(
97 "Internal error",
98 "Something went wrong. Please try again later.",
99 );
100 (StatusCode::INTERNAL_SERVER_ERROR, Html(html)).into_response()
101 }
102 }
103 }
104}