|
2 | 2 |
|
3 | 3 | use std::path::PathBuf; |
4 | 4 |
|
| 5 | +use crate::render::get_backtrace_as_strings; |
5 | 6 | use crate::webserver::ErrorWithStatus; |
6 | 7 | use crate::AppState; |
7 | 8 | use actix_web::error::UrlencodedError; |
8 | 9 | use actix_web::http::{header, StatusCode}; |
9 | 10 | use actix_web::{HttpRequest, HttpResponse}; |
10 | 11 | use actix_web::{HttpResponseBuilder, ResponseError}; |
| 12 | +use handlebars::{Renderable, StringOutput}; |
| 13 | +use serde_json::json; |
| 14 | + |
| 15 | +fn error_to_html_string(app_state: &AppState, err: &anyhow::Error) -> anyhow::Result<String> { |
| 16 | + let mut out = StringOutput::new(); |
| 17 | + let shell_template = app_state.all_templates.get_static_template("shell")?; |
| 18 | + let error_template = app_state.all_templates.get_static_template("error")?; |
| 19 | + let registry = &app_state.all_templates.handlebars; |
| 20 | + let shell_ctx = handlebars::Context::null(); |
| 21 | + let data = if app_state.config.environment.is_prod() { |
| 22 | + json!(null) |
| 23 | + } else { |
| 24 | + json!({ |
| 25 | + "description": err.to_string(), |
| 26 | + "backtrace": get_backtrace_as_strings(err), |
| 27 | + "note": "You can hide error messages like this one from your users by setting the 'environment' configuration option to 'production'.", |
| 28 | + }) |
| 29 | + }; |
| 30 | + let err_ctx = handlebars::Context::wraps(data)?; |
| 31 | + let rc = &mut handlebars::RenderContext::new(None); |
| 32 | + |
| 33 | + // Open the shell component |
| 34 | + shell_template |
| 35 | + .before_list |
| 36 | + .render(registry, &shell_ctx, rc, &mut out)?; |
| 37 | + |
| 38 | + // Open the error component |
| 39 | + error_template |
| 40 | + .before_list |
| 41 | + .render(registry, &err_ctx, rc, &mut out)?; |
| 42 | + // Close the error component |
| 43 | + error_template |
| 44 | + .after_list |
| 45 | + .render(registry, &err_ctx, rc, &mut out)?; |
| 46 | + |
| 47 | + // Close the shell component |
| 48 | + shell_template |
| 49 | + .after_list |
| 50 | + .render(registry, &shell_ctx, rc, &mut out)?; |
| 51 | + |
| 52 | + Ok(out.into_string()?) |
| 53 | +} |
11 | 54 |
|
12 | 55 | fn anyhow_err_to_actix_resp(e: &anyhow::Error, state: &AppState) -> HttpResponse { |
13 | 56 | let mut resp = HttpResponseBuilder::new(StatusCode::INTERNAL_SERVER_ERROR); |
14 | | - let mut body = "Sorry, but we were not able to process your request.\n\n".to_owned(); |
15 | | - let env = state.config.environment; |
16 | | - if env.is_prod() { |
17 | | - body.push_str("Contact the administrator for more information. A detailed error message has been logged."); |
18 | | - log::error!("{e:#}"); |
19 | | - } else { |
20 | | - use std::fmt::Write; |
21 | | - write!( |
22 | | - body, |
23 | | - "Below are detailed debugging information which may contain sensitive data. \n\ |
24 | | - Set environment to \"production\" in the configuration file to hide this information. \n\n\ |
25 | | - {e:?}" |
26 | | - ) |
27 | | - .unwrap(); |
28 | | - } |
29 | 57 | resp.insert_header(( |
30 | 58 | header::CONTENT_TYPE, |
31 | 59 | header::HeaderValue::from_static("text/plain; charset=utf-8"), |
32 | 60 | )); |
33 | 61 |
|
34 | 62 | if let Some(status_err @ &ErrorWithStatus { .. }) = e.downcast_ref() { |
35 | | - status_err |
36 | | - .error_response() |
37 | | - .set_body(actix_web::body::BoxBody::new(body)) |
| 63 | + status_err.error_response() |
38 | 64 | } else if let Some(sqlx::Error::PoolTimedOut) = e.downcast_ref() { |
39 | 65 | use rand::Rng; |
40 | 66 | resp.status(StatusCode::TOO_MANY_REQUESTS) |
41 | 67 | .insert_header(( |
42 | 68 | header::RETRY_AFTER, |
43 | 69 | header::HeaderValue::from(rand::rng().random_range(1..=15)), |
44 | 70 | )) |
45 | | - .body("The database is currently too busy to handle your request. Please try again later.\n\n".to_owned() + &body) |
| 71 | + .body("The database is currently too busy to handle your request. Please try again later.".to_owned()) |
46 | 72 | } else { |
47 | | - resp.body(body) |
| 73 | + match error_to_html_string(state, e) { |
| 74 | + Ok(body) => { |
| 75 | + resp.insert_header(( |
| 76 | + header::CONTENT_TYPE, |
| 77 | + header::HeaderValue::from_static("text/html; charset=utf-8"), |
| 78 | + )); |
| 79 | + resp.body(body) |
| 80 | + } |
| 81 | + Err(second_err) => { |
| 82 | + log::error!("Unable to render error: {e:#}"); |
| 83 | + resp.body(format!( |
| 84 | + "A second error occurred while rendering the error page: \n\n\ |
| 85 | + Initial error: \n\ |
| 86 | + {e:#}\n\n\ |
| 87 | + Second error: \n\ |
| 88 | + {second_err:#}" |
| 89 | + )) |
| 90 | + } |
| 91 | + } |
48 | 92 | } |
49 | 93 | } |
50 | 94 |
|
|
0 commit comments