use std::io; use actix_web::{ body::BoxBody, dev::ServiceResponse, get, http::{header::ContentType, StatusCode}, middleware::{ErrorHandlerResponse, ErrorHandlers}, web, App, HttpResponse, HttpServer, Responder, Result, }; use actix_web_lab::{extract::Path, respond::Html}; use fluent_templates::{static_loader, FluentLoader, Loader as _}; use handlebars::Handlebars; use serde_json::json; mod lang_choice; use self::lang_choice::LangChoice; static_loader! { static LOCALES = { locales: "./locales", fallback_language: "en", // removes unicode isolating marks around arguments // you typically should only set to false when testing. customise: |bundle| bundle.set_use_isolating(false), }; } #[get("/")] async fn index(hb: web::Data>, lang: LangChoice) -> impl Responder { let data = json!({ "lang": lang }); let body = hb.render("index", &data).unwrap(); Html(body) } #[get("/{user}/{data}")] async fn user( hb: web::Data>, Path(info): Path<(String, String)>, lang: LangChoice, ) -> impl Responder { let data = json!({ "lang": lang, "user": info.0, "data": info.1 }); let body = hb.render("user", &data).unwrap(); Html(body) } #[actix_web::main] async fn main() -> io::Result<()> { // Handlebars uses a repository for the compiled templates. This object must be shared between // the application threads, and is therefore passed to the App in an Arc. let mut handlebars = Handlebars::new(); // register template dir with Handlebars registry handlebars .register_templates_directory(".html", "./templates") .unwrap(); // register Fluent helper with Handlebars registry handlebars.register_helper("fluent", Box::new(FluentLoader::new(&*LOCALES))); let handlebars = web::Data::new(handlebars); HttpServer::new(move || { App::new() .wrap(error_handlers()) .app_data(web::Data::clone(&handlebars)) .service(index) .service(user) }) .bind(("127.0.0.1", 8080))? .run() .await } // Custom error handlers, to return HTML responses when an error occurs. fn error_handlers() -> ErrorHandlers { ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found) } // Error handler for a 404 Page not found error. fn not_found(res: ServiceResponse) -> Result> { let lang = LangChoice::from_req(res.request()).lang_id(); let error = LOCALES.lookup(&lang, "error-not-found").unwrap(); let response = get_error_response(&res, &error); Ok(ErrorHandlerResponse::Response(ServiceResponse::new( res.into_parts().0, response.map_into_left_body(), ))) } // Generic error handler. fn get_error_response(res: &ServiceResponse, error: &str) -> HttpResponse { let req = res.request(); let lang = LangChoice::from_req(req); // 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::>() .expect("correctly set up handlebars in app data"); let data = json!({ "lang": lang, "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()), } }