Skip to main content

max / makenotwork

3.3 KB · 86 lines History Blame Raw
1 //! Branded 404/500 responses for in-handler error paths.
2 //!
3 //! The router `.fallback` renders a branded, session-aware error page, but
4 //! errors raised *inside* a handler (missing thread/community, a bad UUID in the
5 //! path, a DB failure) used to short-circuit with bare `(StatusCode, &str)`
6 //! plaintext, unbranded and jarring. These helpers render the same
7 //! `Error404Template`/`Error500Template` instead.
8 //!
9 //! They render a **session-less** shell (logged-out header), because the deep
10 //! shared helpers that raise these errors don't carry the session. The only
11 //! per-process value the templates need is `mnw_base_url` (all the chrome nav
12 //! links), which is immutable config; it's stashed in a `OnceLock` at startup
13 //! rather than threaded through ~80 call sites. The fallback handler still
14 //! renders the fully session-aware page for unmatched routes.
15
16 use std::sync::{Arc, OnceLock};
17
18 use axum::http::StatusCode;
19 use axum::response::{IntoResponse, Response};
20
21 use crate::templates::{Error404Template, Error500Template};
22
23 static MNW_BASE_URL: OnceLock<Arc<str>> = OnceLock::new();
24
25 /// Record the MNW base URL for branded error pages. Called once at startup
26 /// (and by the test harness); later calls are ignored.
27 pub fn init(mnw_base_url: Arc<str>) {
28 let _ = MNW_BASE_URL.set(mnw_base_url);
29 }
30
31 fn base_url() -> Arc<str> {
32 MNW_BASE_URL.get().cloned().unwrap_or_else(|| Arc::from(""))
33 }
34
35 /// Branded 404 page (session-less). Use from shared handler helpers in place of
36 /// a bare `(StatusCode::NOT_FOUND, "Not found")`.
37 pub fn not_found() -> Response {
38 (
39 StatusCode::NOT_FOUND,
40 Error404Template {
41 csrf_token: None,
42 session_user: None,
43 mnw_base_url: base_url(),
44 },
45 )
46 .into_response()
47 }
48
49 /// Branded 500 page (session-less). Use from shared handler helpers in place of
50 /// a bare `(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")`.
51 pub fn internal_error() -> Response {
52 (
53 StatusCode::INTERNAL_SERVER_ERROR,
54 Error500Template {
55 csrf_token: None,
56 session_user: None,
57 mnw_base_url: base_url(),
58 },
59 )
60 .into_response()
61 }
62
63 /// A fully static, template-free branded 500. Used only when the Askama render
64 /// itself fails; falling back to [`internal_error`] there would re-enter
65 /// the template engine that's already broken. No interpolation, so it can never
66 /// fail to produce output.
67 pub fn internal_error_static() -> Response {
68 const BODY: &str = "<!DOCTYPE html>\n\
69 <html lang=\"en\"><head><meta charset=\"utf-8\">\
70 <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
71 <title>Something went wrong</title>\
72 <style>body{font-family:system-ui,-apple-system,sans-serif;background:#111;color:#eee;\
73 margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center}\
74 main{max-width:32rem;padding:2rem;text-align:center}.mark{font-size:2rem;opacity:.55}\
75 h1{font-weight:600;font-size:1.25rem;margin:.75rem 0}a{color:#9ad}</style></head>\
76 <body><main><div class=\"mark\">&#9670;</div>\
77 <h1>Something went wrong</h1>\
78 <p>This page couldn't be rendered. The error has been logged. Try again, or return to the <a href=\"/\">forum home</a>.</p>\
79 </main></body></html>";
80 (
81 StatusCode::INTERNAL_SERVER_ERROR,
82 axum::response::Html(BODY),
83 )
84 .into_response()
85 }
86