feat: implement server error handling
parent
d7c5c85460
commit
3add93bdb2
|
@ -10,17 +10,12 @@ pub fn app(ctx: crate::Context) -> axum::Router {
|
|||
async fn post_comment(
|
||||
State(ctx): State<crate::Context>,
|
||||
Form(comment): Form<NewComment>,
|
||||
) -> Html<String> {
|
||||
) -> super::Result<Html<String>> {
|
||||
let comment = tokio::task::spawn_blocking(move || comment.insert(&ctx.pool))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
.unwrap()?;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("comment", &comment);
|
||||
Html(
|
||||
ctx.tera
|
||||
.render("partials/comment_li.html", &context)
|
||||
.unwrap(),
|
||||
)
|
||||
Ok(Html(ctx.tera.render("partials/comment_li.html", &context)?))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
use std::fmt::{self, Write};
|
||||
|
||||
use axum::{http::StatusCode, response::IntoResponse};
|
||||
|
||||
use crate::db;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppError {
|
||||
Db(db::DbError),
|
||||
Tera(tera::Error),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl fmt::Display for AppError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Db(_) => write!(f, "database error"),
|
||||
Self::Tera(_) => write!(f, "error rendering template"),
|
||||
Self::NotFound => write!(f, "not found"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for AppError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Db(err) => Some(err),
|
||||
Self::Tera(err) => Some(err),
|
||||
Self::NotFound => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ErrorExt: std::error::Error {
|
||||
/// Return the full chain of error messages
|
||||
fn stack(&self) -> String {
|
||||
let mut msg = format!("{}", self);
|
||||
let mut err = self.source();
|
||||
|
||||
while let Some(src) = err {
|
||||
write!(msg, " - {}", src).unwrap();
|
||||
|
||||
err = src.source();
|
||||
}
|
||||
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: std::error::Error> ErrorExt for E {}
|
||||
|
||||
impl From<db::DbError> for AppError {
|
||||
fn from(value: db::DbError) -> Self {
|
||||
Self::Db(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tera::Error> for AppError {
|
||||
fn from(value: tera::Error) -> Self {
|
||||
Self::Tera(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
Self::NotFound => StatusCode::NOT_FOUND.into_response(),
|
||||
_ => {
|
||||
tracing::error!("{}", self.stack());
|
||||
|
||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod comments;
|
||||
mod error;
|
||||
mod plants;
|
||||
|
||||
use axum::{
|
||||
|
@ -13,6 +14,8 @@ use tower_http::set_header::SetResponseHeaderLayer;
|
|||
|
||||
use crate::db::Plant;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, error::AppError>;
|
||||
|
||||
const HX_REQUEST_HEADER: &str = "HX-Request";
|
||||
const HX_HISTORY_RESTORE_HEADER: &str = "HX-History-Restore-Request";
|
||||
|
||||
|
@ -47,13 +50,12 @@ pub fn app(ctx: crate::Context) -> axum::Router {
|
|||
))
|
||||
}
|
||||
|
||||
async fn get_index(State(ctx): State<crate::Context>) -> Html<String> {
|
||||
async fn get_index(State(ctx): State<crate::Context>) -> Result<Html<String>> {
|
||||
let plants = tokio::task::spawn_blocking(move || Plant::all(&ctx.pool))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
.unwrap()?;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("plants", &plants);
|
||||
Html(ctx.tera.render("index.html", &context).unwrap())
|
||||
Ok(Html(ctx.tera.render("index.html", &context)?))
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use tera::Context;
|
|||
|
||||
use crate::db::{self, DbError, Plant};
|
||||
|
||||
use super::render_partial;
|
||||
use super::{error::AppError, render_partial};
|
||||
|
||||
pub fn app(ctx: crate::Context) -> axum::Router {
|
||||
Router::new()
|
||||
|
@ -22,40 +22,48 @@ async fn get_plant_page(
|
|||
State(ctx): State<crate::Context>,
|
||||
headers: HeaderMap,
|
||||
Path(plant_id): Path<i32>,
|
||||
) -> Html<String> {
|
||||
let (plant, comments) = tokio::task::spawn_blocking(move || {
|
||||
let plant = Plant::by_id(&ctx.pool, plant_id)?.unwrap();
|
||||
let comments = plant.comments(&ctx.pool)?;
|
||||
) -> super::Result<Html<String>> {
|
||||
let res = tokio::task::spawn_blocking(move || {
|
||||
let plant = Plant::by_id(&ctx.pool, plant_id)?;
|
||||
|
||||
Ok::<_, DbError>((plant, comments))
|
||||
if let Some(plant) = plant {
|
||||
let comments = plant.comments(&ctx.pool)?;
|
||||
|
||||
Ok::<_, DbError>(Some((plant, comments)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
.unwrap()?;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("plant", &plant);
|
||||
context.insert("comments", &comments);
|
||||
match res {
|
||||
Some((plant, comments)) => {
|
||||
let mut context = Context::new();
|
||||
context.insert("plant", &plant);
|
||||
context.insert("comments", &comments);
|
||||
|
||||
let tmpl = if render_partial(&headers) {
|
||||
"partials/plant_info.html"
|
||||
} else {
|
||||
"plant_page.html"
|
||||
};
|
||||
let tmpl = if render_partial(&headers) {
|
||||
"partials/plant_info.html"
|
||||
} else {
|
||||
"plant_page.html"
|
||||
};
|
||||
|
||||
Html(ctx.tera.render(tmpl, &context).unwrap())
|
||||
Ok(Html(ctx.tera.render(tmpl, &context)?))
|
||||
}
|
||||
None => Err(AppError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
async fn post_plant(
|
||||
State(ctx): State<crate::Context>,
|
||||
Form(plant): Form<db::NewPlant>,
|
||||
) -> Html<String> {
|
||||
) -> super::Result<Html<String>> {
|
||||
let plant = tokio::task::spawn_blocking(move || plant.insert(&ctx.pool))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
.unwrap()?;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("plant", &plant);
|
||||
Html(ctx.tera.render("partials/plant_li.html", &context).unwrap())
|
||||
Ok(Html(ctx.tera.render("partials/plant_li.html", &context)?))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue