add simple error response rendering

we don't really need anything pretty here since 99% of the time, the
error's that would be generated during a request will be something to
do with tera template rendering and thus, are probably only going to be
experienced while fiddling with the site. so we just want to display
the error details and move on.

the vast majority (if not all) of other errors will occuring during
startup and content reloading, and thus we only really need to worry
about them being logged to the console (which they are already)
This commit is contained in:
Gered 2023-07-09 13:33:38 -04:00
parent c7665fdc79
commit 3427b954ed
2 changed files with 33 additions and 19 deletions

View file

@ -41,12 +41,15 @@ async fn rss_feed(data: web::Data<site::SiteService>) -> impl Responder {
data.serve_rss_feed() data.serve_rss_feed()
} }
async fn site_content(req: HttpRequest, data: web::Data<site::SiteService>) -> Either<HttpResponse, Redirect> { async fn site_content(
req: HttpRequest,
data: web::Data<site::SiteService>,
) -> Result<Either<HttpResponse, Redirect>, site::SiteError> {
log::debug!("GET {} -> fallback to site_content()", req.path()); log::debug!("GET {} -> fallback to site_content()", req.path());
if let Some(response) = data.serve_content_by_url(&req) { if let Some(response) = data.serve_content_by_url(&req)? {
response Ok(response)
} else { } else {
Either::Left(not_found()) Ok(Either::Left(not_found()))
} }
} }

View file

@ -3,6 +3,8 @@ use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::RwLock; use std::sync::RwLock;
use actix_web::body::BoxBody;
use actix_web::http::header::ContentType;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::web::Redirect; use actix_web::web::Redirect;
use actix_web::{Either, HttpRequest, HttpResponse}; use actix_web::{Either, HttpRequest, HttpResponse};
@ -63,6 +65,15 @@ pub enum SiteError {
TeraError(#[from] tera::Error), TeraError(#[from] tera::Error),
} }
impl actix_web::error::ResponseError for SiteError {
fn error_response(&self) -> HttpResponse<BoxBody> {
let status_code = self.status_code();
HttpResponse::build(status_code) //
.insert_header(ContentType::plaintext())
.body(format!("{status_code}\n\n{:#?}", self))
}
}
pub struct AlternateUrlMappings { pub struct AlternateUrlMappings {
mapping: HashMap<UriPath, UriPath>, mapping: HashMap<UriPath, UriPath>,
} }
@ -354,34 +365,34 @@ impl SiteService {
Ok(()) Ok(())
} }
pub fn serve_latest_post(&self) -> HttpResponse { pub fn serve_latest_post(&self) -> Result<HttpResponse, SiteError> {
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
let post = content.get_latest_post(); let post = content.get_latest_post();
let mut context = tera::Context::new(); let mut context = tera::Context::new();
if let Some(post) = post { if let Some(post) = post {
context.insert("post", post); context.insert("post", post);
} }
HttpResponse::Ok().body(content.template_renderer.render("latest_post.html", &context).unwrap()) Ok(HttpResponse::Ok().body(content.template_renderer.render("latest_post.html", &context)?))
} }
pub fn serve_posts_by_tag(&self, tag: &Tag) -> HttpResponse { pub fn serve_posts_by_tag(&self, tag: &Tag) -> Result<HttpResponse, SiteError> {
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
let posts = content.get_posts_with_tag_ordered_by_date(tag); let posts = content.get_posts_with_tag_ordered_by_date(tag);
let mut context = tera::Context::new(); let mut context = tera::Context::new();
context.insert("tag", tag); context.insert("tag", tag);
context.insert("posts", &posts); context.insert("posts", &posts);
HttpResponse::Ok().body(content.template_renderer.render("tag.html", &context).unwrap()) Ok(HttpResponse::Ok().body(content.template_renderer.render("tag.html", &context)?))
} }
pub fn serve_posts_archive(&self) -> HttpResponse { pub fn serve_posts_archive(&self) -> Result<HttpResponse, SiteError> {
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
let posts = content.get_posts_ordered_by_date(); let posts = content.get_posts_ordered_by_date();
let mut context = tera::Context::new(); let mut context = tera::Context::new();
context.insert("posts", &posts); context.insert("posts", &posts);
HttpResponse::Ok().body(content.template_renderer.render("archive.html", &context).unwrap()) Ok(HttpResponse::Ok().body(content.template_renderer.render("archive.html", &context)?))
} }
pub fn serve_rss_feed(&self) -> HttpResponse { pub fn serve_rss_feed(&self) -> Result<HttpResponse, SiteError> {
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
let base_url = url::Url::parse(&content.rss.url).unwrap(); let base_url = url::Url::parse(&content.rss.url).unwrap();
let posts = content.get_posts_ordered_by_date(); let posts = content.get_posts_ordered_by_date();
@ -404,10 +415,10 @@ impl SiteService {
}) })
.collect::<Vec<rss::Item>>(), .collect::<Vec<rss::Item>>(),
); );
HttpResponse::Ok().content_type("application/rss+xml").body(channel.to_string()) Ok(HttpResponse::Ok().content_type("application/rss+xml").body(channel.to_string()))
} }
pub fn serve_content_by_url(&self, req: &HttpRequest) -> Option<Either<HttpResponse, Redirect>> { pub fn serve_content_by_url(&self, req: &HttpRequest) -> Result<Option<Either<HttpResponse, Redirect>>, SiteError> {
let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling let content = self.content.read().expect("SiteContent read lock failed"); // TODO: better error handling
let url = String::from(req.path()); let url = String::from(req.path());
match content.get_content_at(&url) { match content.get_content_at(&url) {
@ -415,23 +426,23 @@ impl SiteService {
log::debug!("Found page content at {}", req.path()); log::debug!("Found page content at {}", req.path());
let mut context = tera::Context::new(); let mut context = tera::Context::new();
context.insert("page", page); context.insert("page", page);
let rendered = content.template_renderer.render("page.html", &context).unwrap(); let rendered = content.template_renderer.render("page.html", &context)?;
Some(Either::Left(HttpResponse::Ok().body(rendered))) Ok(Some(Either::Left(HttpResponse::Ok().body(rendered))))
} }
Some(Content::Post(post)) => { Some(Content::Post(post)) => {
log::debug!("Found post content at {}", req.path()); log::debug!("Found post content at {}", req.path());
let mut context = tera::Context::new(); let mut context = tera::Context::new();
context.insert("post", post); context.insert("post", post);
let rendered = content.template_renderer.render("post.html", &context).unwrap(); let rendered = content.template_renderer.render("post.html", &context)?;
Some(Either::Left(HttpResponse::Ok().body(rendered))) Ok(Some(Either::Left(HttpResponse::Ok().body(rendered))))
} }
Some(Content::Redirect(url)) => { Some(Content::Redirect(url)) => {
log::debug!("Found redirect at {}", req.path()); log::debug!("Found redirect at {}", req.path());
Some(Either::Right(Redirect::to(url).using_status_code(StatusCode::MOVED_PERMANENTLY))) Ok(Some(Either::Right(Redirect::to(url).using_status_code(StatusCode::MOVED_PERMANENTLY))))
} }
None => { None => {
log::debug!("No matching content at {}", req.path()); log::debug!("No matching content at {}", req.path());
None Ok(None)
} }
} }
} }