diff --git a/web/src/main.rs b/web/src/main.rs index 16f67bd..0f1a5ab 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; +use std::fmt::{self, Display}; -use actix_web::error::ErrorBadRequest; -use actix_web::http::header::LOCATION; -use actix_web::middleware::Logger; -use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, Result}; +use actix_web::dev::HttpResponseBuilder; +use actix_web::http::{header, StatusCode}; +use actix_web::middleware::normalize::TrailingSlash; +use actix_web::middleware::{self, Logger}; +use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, ResponseError, Result}; use askama::Template; use pronouns_today::user_preferences::Preference; use pronouns_today::{InstanceSettings, Pronoun}; @@ -11,14 +13,14 @@ use pronouns_today::{InstanceSettings, Pronoun}; #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate<'a> { - name: Option, + name: Option, short: String, pronouns: Vec<(usize, &'a Pronoun)>, } fn render_page(pronoun: &Pronoun, settings: &InstanceSettings, name: Option) -> String { IndexTemplate { - name, + name, short: pronoun.render_threeform(), pronouns: settings.pronoun_list.iter().enumerate().collect(), } @@ -26,45 +28,45 @@ fn render_page(pronoun: &Pronoun, settings: &InstanceSettings, name: Option) -> Result { - eprintln!("default"); - let pronoun = settings - .select_pronouns(None, None) - .map_err(ErrorBadRequest)?; - Ok(HttpResponse::Ok() - .content_type("text/html") - .body(render_page(&pronoun, &settings, None))) -} - #[post("/")] async fn create_link( settings: web::Data, form: web::Form>, ) -> Result { eprintln!("create {:?}", form); - let mut weights = vec![0; settings.pronoun_list.len()]; - for (k, v) in form.iter() { - if let Ok(i) = k.parse::() { - let w = v.parse::().map_err(ErrorBadRequest)?; - if i < weights.len() - 1 { - weights[i] = w; - } - } - } + let mut weights = vec![0; settings.pronoun_list.len()]; + for (k, v) in form.iter() { + if let Ok(i) = k.parse::() { + let w = v.parse::().map_err(|_| Error::InvlaidPrefString)?; + if i < weights.len() - 1 { + weights[i] = w; + } + } + } let prefs = InstanceSettings::create_preferences(&weights); - eprintln!("prefs: {:?}", prefs); - let pref_string = prefs.as_prefstring(); - eprintln!("prefstring: {}", pref_string); - let url = match form.get("name") { - Some(name) if !name.is_empty() => format!("/{}/{}", name, pref_string), - _ => format!("/{}", pref_string), - }; + eprintln!("prefs: {:?}", prefs); + let pref_string = prefs.as_prefstring(); + eprintln!("prefstring: {}", pref_string); + let url = match form.get("name") { + Some(name) if !name.is_empty() => format!("/{}/{}", name, pref_string), + _ => format!("/{}", pref_string), + }; Ok(HttpResponse::SeeOther() - .header(LOCATION, url) + .header(header::LOCATION, url) .finish()) } +#[get("/")] +async fn default(settings: web::Data) -> Result { + eprintln!("default"); + let pronoun = settings + .select_pronouns(None, None) + .map_err(|_| Error::InvlaidPrefString)?; + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(render_page(&pronoun, &settings, None))) +} + #[get("/{prefs}")] async fn only_prefs( web::Path(prefs): web::Path, @@ -73,9 +75,9 @@ async fn only_prefs( eprintln!("prefs {}", prefs); let pronoun = settings .select_pronouns(None, Some(&prefs)) - .map_err(ErrorBadRequest)?; + .map_err(|_| Error::InvlaidPrefString)?; Ok(HttpResponse::Ok() - .content_type("text/html") + .content_type("text/html; charset=utf-8") .body(render_page(&pronoun, &settings, None))) } @@ -87,12 +89,18 @@ async fn prefs_and_name( eprintln!("prefs+name"); let pronoun = settings .select_pronouns(Some(&name), Some(&prefs)) - .map_err(ErrorBadRequest)?; + .map_err(|_| Error::InvlaidPrefString)?; Ok(HttpResponse::Ok() - .content_type("text/html") + .content_type("text/html; charset=utf-8") .body(render_page(&pronoun, &settings, Some(name)))) } +async fn not_found() -> impl Responder { + HttpResponse::NotFound() + .content_type("text/html; charset=utf-8") + .body(include_str!("../templates/404.html")) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); @@ -101,12 +109,54 @@ async fn main() -> std::io::Result<()> { App::new() .data(InstanceSettings::default()) .wrap(logger) + .wrap(middleware::NormalizePath::new(TrailingSlash::Trim)) .service(prefs_and_name) .service(only_prefs) .service(default) .service(create_link) + .default_service(web::to(not_found)) }) .bind("127.0.0.1:8080")? .run() .await } + +#[derive(Template)] +#[template(path = "error.html")] +struct ErrorPage { + msg: String, +} + +#[derive(Debug)] +enum Error { + InvlaidPrefString, +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + &Error::InvlaidPrefString => "This URL contains an invalid pronoun preference string", + }; + write!(f, "{}", msg) + } +} + +impl ResponseError for Error { + fn status_code(&self) -> actix_web::http::StatusCode { + match self { + &Error::InvlaidPrefString => StatusCode::BAD_REQUEST, + } + } + + fn error_response(&self) -> HttpResponse { + HttpResponseBuilder::new(self.status_code()) + .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8") + .body( + ErrorPage { + msg: self.to_string(), + } + .render() + .unwrap(), + ) + } +} diff --git a/web/templates/404.html b/web/templates/404.html new file mode 100644 index 0000000..eb22ee3 --- /dev/null +++ b/web/templates/404.html @@ -0,0 +1,9 @@ + + + Page Not Found + + +

Page Not Found

+

The page you requested does not exist.

+ + diff --git a/web/templates/error.html b/web/templates/error.html new file mode 100644 index 0000000..6c7421a --- /dev/null +++ b/web/templates/error.html @@ -0,0 +1,9 @@ + + + An Error Occurred + + +

An Error Occurred

+

{{ msg }}

+ +