Bladeren bron

Fixes #12: Add error handling.

Alexandre Leblanc 3 jaren geleden
bovenliggende
commit
0f2ae768db
1 gewijzigde bestanden met toevoegingen van 101 en 50 verwijderingen
  1. 101 50
      src/main.rs

+ 101 - 50
src/main.rs

@@ -4,12 +4,17 @@ mod config;
 mod repository;
 mod theme;
 
-use actix_web::middleware::Logger;
+use actix_web::body::BoxBody;
+use actix_web::dev::ServiceResponse;
+use actix_web::error::ParseError::Status;
+use actix_web::http::{header::ContentType, StatusCode};
+use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers, Logger};
 use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
 use fern;
 use fluent_templates::{static_loader, FluentLoader, Loader as _};
 use handlebars::Handlebars;
 use serde_json;
+use serde_json::json;
 use std::{env, io};
 
 use crate::config::Configuration;
@@ -30,55 +35,6 @@ static_loader! {
     };
 }
 
-#[get("")]
-async fn show_articles_list(hb: web::Data<Handlebars<'_>>) -> impl Responder {
-    let repo = GitRepository {};
-
-    let exe_dir = env::current_dir()
-        .and_then(|dir| Ok(dir.to_str().unwrap_or("").to_string()))
-        .unwrap();
-    log::trace!("Serving article from repository: {exe_dir}/{ARTICLE_BASE_PATH}");
-
-    let paths = repo.get_directory_listing(ARTICLE_BASE_PATH);
-
-    let data = serde_json::json!({
-        "page-title": "Article",
-        "entries": &paths
-    });
-    let body = hb.render("articles_list", &data).unwrap();
-
-    HttpResponse::Ok().body(body)
-}
-
-#[get("/{path:.+}.html")]
-async fn show_article(hb: web::Data<Handlebars<'_>>, path: web::Path<(String,)>) -> impl Responder {
-    let p = format!("{ARTICLE_BASE_PATH}/{}", &path.0);
-    let exe_dir = env::current_dir()
-        .and_then(|dir| Ok(dir.to_str().unwrap_or("").to_string()))
-        .unwrap();
-    log::trace!("Estimated content repository: {p}");
-
-    let theme: Theme = Theme::new(format!("{}/{}", exe_dir, ARTICLE_BASE_PATH));
-
-    let rendered = theme.markdown_as_html(&p);
-
-    match rendered {
-        Ok(content) => {
-            let data = serde_json::json!({
-                "page-title": "Article",
-                "article": &content
-            });
-            let body = hb.render("article", &data).unwrap();
-
-            HttpResponse::Ok().body(body)
-        }
-        Err(e) => {
-            log::error!("Couldn't find article located at '{}'. Error: {}", p, e);
-            HttpResponse::NotFound().finish()
-        }
-    }
-}
-
 #[actix_web::main]
 async fn main() -> io::Result<()> {
     if !env::vars().any(|(k, _)| k == "RUST_LOG") {
@@ -113,6 +69,7 @@ async fn main() -> io::Result<()> {
 
     HttpServer::new(move || {
         App::new()
+            .wrap(error_handlers())
             .wrap(Logger::default())
             .app_data(web::Data::clone(&handlebars))
             .service(actix_files::Files::new("/css", "./www/themes/default/css"))
@@ -135,3 +92,97 @@ async fn main() -> io::Result<()> {
     .run()
     .await
 }
+
+#[get("")]
+async fn show_articles_list(hb: web::Data<Handlebars<'_>>) -> impl Responder {
+    let repo = GitRepository {};
+
+    let exe_dir = env::current_dir()
+        .and_then(|dir| Ok(dir.to_str().unwrap_or("").to_string()))
+        .unwrap();
+    log::trace!("Serving article from repository: {exe_dir}/{ARTICLE_BASE_PATH}");
+
+    let paths = repo.get_directory_listing(ARTICLE_BASE_PATH);
+
+    let data = serde_json::json!({
+        "page-title": "Article",
+        "entries": &paths
+    });
+    let body = hb.render("articles_list", &data).unwrap();
+
+    HttpResponse::Ok().body(body)
+}
+
+#[get("/{path:.+}.html")]
+async fn show_article(hb: web::Data<Handlebars<'_>>, path: web::Path<(String,)>) -> impl Responder {
+    let p = format!("{ARTICLE_BASE_PATH}/{}", &path.0);
+    let exe_dir = env::current_dir()
+        .and_then(|dir| Ok(dir.to_str().unwrap_or("").to_string()))
+        .unwrap();
+    log::trace!("Estimated content repository: {p}");
+
+    let theme: Theme = Theme::new(format!("{}/{}", exe_dir, ARTICLE_BASE_PATH));
+
+    let rendered = theme.markdown_as_html(&p);
+
+    match rendered {
+        Ok(content) => {
+            let data = serde_json::json!({
+                "page-title": "Article",
+                "article": &content
+            });
+            let body = hb.render("article", &data).unwrap();
+
+            HttpResponse::Ok().body(body)
+        }
+        Err(e) => {
+            log::error!("Couldn't find article located at '{}'. Error: {}", p, e);
+            HttpResponse::NotFound().finish()
+        }
+    }
+}
+
+// Custom error handlers, to return HTML responses when an error occurs.
+fn error_handlers() -> ErrorHandlers<BoxBody> {
+    ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
+}
+
+fn not_found<B>(
+    res: ServiceResponse<B>,
+) -> Result<ErrorHandlerResponse<BoxBody>, actix_web::Error> {
+    let response = get_error_response(&res, "La page web demandée est indisponible.");
+
+    Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
+        res.into_parts().0,
+        response.map_into_left_body(),
+    )))
+}
+
+// Generic error handler.
+fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
+    let req = res.request();
+
+    // Provide a fallback to a simple plain text response in case an error occurs during the
+    // rendering of the error page.
+
+    let hb = req
+        .app_data::<web::Data<Handlebars>>()
+        .expect("correctly set up handlebars in app data");
+
+    let data = json!({
+        "error": error,
+        "status_code": res.status().as_str()
+    });
+
+    let body = hb.render("error", &data);
+
+    match body {
+        Ok(body) => HttpResponse::build(res.status())
+            .content_type(ContentType::html())
+            .body(body),
+
+        Err(_) => HttpResponse::build(res.status())
+            .content_type(ContentType::plaintext())
+            .body(error.to_string()),
+    }
+}