better error handling using anyhow

This commit is contained in:
Khaïs COLIN 2025-10-27 10:46:22 +01:00
parent 425ec50a5f
commit ad3a235982
Signed by: logistic-bot
SSH key fingerprint: SHA256:RlpiqKeXpcPFZZ4y9Ou4xi2M8OhRJovIwDlbCaMsuAo
4 changed files with 66 additions and 51 deletions

7
Cargo.lock generated
View file

@ -20,6 +20,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "askama" name = "askama"
version = "0.14.0" version = "0.14.0"
@ -812,6 +818,7 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
name = "task_counter" name = "task_counter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"askama", "askama",
"axum", "axum",
"chrono", "chrono",

View file

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.100"
askama = "0.14.0" askama = "0.14.0"
axum = "0.8.6" axum = "0.8.6"
chrono = "0.4.42" chrono = "0.4.42"

BIN
foods.db

Binary file not shown.

View file

@ -5,7 +5,7 @@ use axum::{
Router, Router,
extract::{MatchedPath, Path, State}, extract::{MatchedPath, Path, State},
http::{Request, StatusCode}, http::{Request, StatusCode},
response::Html, response::{Html, IntoResponse, Response},
routing::{get, post}, routing::{get, post},
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
@ -80,7 +80,7 @@ impl Food {
struct PreparedStatements {} struct PreparedStatements {}
impl<'conn> PreparedStatements { impl<'conn> PreparedStatements {
fn check(conn: &Connection) -> rusqlite::Result<Self> { fn check(conn: &Connection) -> anyhow::Result<Self> {
conn.prepare_cached(include_str!("increase.sql"))?; conn.prepare_cached(include_str!("increase.sql"))?;
conn.prepare_cached(include_str!("decrease.sql"))?; conn.prepare_cached(include_str!("decrease.sql"))?;
conn.prepare_cached(include_str!("get_food.sql"))?; conn.prepare_cached(include_str!("get_food.sql"))?;
@ -121,10 +121,35 @@ impl<'conn> PreparedStatements {
} }
} }
// Make our own error that wraps `anyhow::Error`.
struct AppError(anyhow::Error);
// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
// `Result<_, AppError>`. That way you don't need to do that manually.
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}
type ConnState = Arc<Mutex<Connection>>; type ConnState = Arc<Mutex<Connection>>;
fn get_version(tx: &Transaction) -> rusqlite::Result<usize> { fn get_version(tx: &Transaction) -> anyhow::Result<usize> {
tx.query_one(include_str!("get_version.sql"), (), |row| row.get(0)) Ok(tx.query_one(include_str!("get_version.sql"), (), |row| row.get(0))?)
} }
fn get_migrations() -> [&'static str; 4] { fn get_migrations() -> [&'static str; 4] {
@ -136,7 +161,7 @@ fn get_migrations() -> [&'static str; 4] {
] ]
} }
fn do_migrations(conn: &mut Connection) -> rusqlite::Result<()> { fn do_migrations(conn: &mut Connection) -> anyhow::Result<()> {
let migrations = get_migrations(); let migrations = get_migrations();
let num_migrations = migrations.len(); let num_migrations = migrations.len();
let tx = conn.transaction()?; let tx = conn.transaction()?;
@ -198,7 +223,7 @@ fn app(conn: Connection) -> axum::Router {
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), std::io::Error> { async fn main() -> anyhow::Result<()> {
tracing_subscriber::registry() tracing_subscriber::registry()
.with( .with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
@ -239,10 +264,11 @@ async fn main() -> Result<(), std::io::Error> {
.local_addr() .local_addr()
.expect("failed to get local listening address") .expect("failed to get local listening address")
); );
axum::serve(listener, app).await axum::serve(listener, app).await?;
Ok(())
} }
fn get_foods(conn: &ConnState) -> rusqlite::Result<Vec<Food>> { fn get_foods(conn: &ConnState) -> anyhow::Result<Vec<Food>> {
let conn = conn.lock(); let conn = conn.lock();
let mut stmt = PreparedStatements::get_foods(&conn); let mut stmt = PreparedStatements::get_foods(&conn);
let foods: Vec<_> = stmt let foods: Vec<_> = stmt
@ -252,7 +278,7 @@ fn get_foods(conn: &ConnState) -> rusqlite::Result<Vec<Food>> {
Ok(foods) Ok(foods)
} }
fn get_sum(conn: &Arc<Mutex<Connection>>) -> rusqlite::Result<Sum> { fn get_sum(conn: &Arc<Mutex<Connection>>) -> anyhow::Result<Sum> {
let conn = conn.lock(); let conn = conn.lock();
let mut stmt = PreparedStatements::get_sum(&conn); let mut stmt = PreparedStatements::get_sum(&conn);
let sum = stmt.query_one((), |row| { let sum = stmt.query_one((), |row| {
@ -272,22 +298,15 @@ fn get_date() -> String {
date date
} }
async fn root(State(conn): State<ConnState>) -> Result<Html<String>, StatusCode> { async fn root(State(conn): State<ConnState>) -> Result<Html<String>, AppError> {
let foods = get_foods(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let foods = get_foods(&conn)?;
let sum = get_sum(&conn).map_err(|e| { let sum = get_sum(&conn)?;
error!(?e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
let date = get_date(); let date = get_date();
let index = IndexTemplate { foods, sum, date }; let index = IndexTemplate { foods, sum, date };
Ok(Html( Ok(Html(index.render()?))
index
.render()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
))
} }
fn do_increase(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<()> { fn do_increase(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<()> {
let conn = conn.lock(); let conn = conn.lock();
let mut stmt = PreparedStatements::increase(&conn); let mut stmt = PreparedStatements::increase(&conn);
let new: i32 = stmt.query_one((id,), |row| row.get(0))?; let new: i32 = stmt.query_one((id,), |row| row.get(0))?;
@ -295,7 +314,7 @@ fn do_increase(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<()> {
Ok(()) Ok(())
} }
fn get_food(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<Food> { fn get_food(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<Food> {
let conn = conn.lock(); let conn = conn.lock();
let mut stmt = PreparedStatements::get_food(&conn); let mut stmt = PreparedStatements::get_food(&conn);
let food = stmt.query_one((id,), Food::from_row)?; let food = stmt.query_one((id,), Food::from_row)?;
@ -306,20 +325,16 @@ fn get_food(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<Food> {
async fn increase( async fn increase(
State(conn): State<Arc<Mutex<Connection>>>, State(conn): State<Arc<Mutex<Connection>>>,
Path(id): Path<i32>, Path(id): Path<i32>,
) -> Result<Html<String>, StatusCode> { ) -> Result<Html<String>, AppError> {
do_increase(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; do_increase(&conn, id)?;
let food = get_food(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let food = get_food(&conn, id)?;
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let sum = get_sum(&conn)?;
let date = get_date(); let date = get_date();
let update = FoodUpdateTemplate { food, sum, date }; let update = FoodUpdateTemplate { food, sum, date };
Ok(Html( Ok(Html(update.render()?))
update
.render()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
))
} }
fn do_decrease(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<()> { fn do_decrease(conn: &Arc<Mutex<Connection>>, id: i32) -> anyhow::Result<()> {
let conn = conn.lock(); let conn = conn.lock();
let mut stmt = PreparedStatements::decrease(&conn); let mut stmt = PreparedStatements::decrease(&conn);
let new: i32 = stmt.query_one((id,), |row| row.get(0))?; let new: i32 = stmt.query_one((id,), |row| row.get(0))?;
@ -330,20 +345,16 @@ fn do_decrease(conn: &Arc<Mutex<Connection>>, id: i32) -> rusqlite::Result<()> {
async fn decrease( async fn decrease(
State(conn): State<Arc<Mutex<Connection>>>, State(conn): State<Arc<Mutex<Connection>>>,
Path(id): Path<i32>, Path(id): Path<i32>,
) -> Result<Html<String>, StatusCode> { ) -> Result<Html<String>, AppError> {
do_decrease(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; do_decrease(&conn, id)?;
let food = get_food(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let food = get_food(&conn, id)?;
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let sum = get_sum(&conn)?;
let date = get_date(); let date = get_date();
let update = FoodUpdateTemplate { food, sum, date }; let update = FoodUpdateTemplate { food, sum, date };
Ok(Html( Ok(Html(update.render()?))
update
.render()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
))
} }
fn do_set(conn: &Arc<Mutex<Connection>>, id: i32, amount: i32) -> rusqlite::Result<()> { fn do_set(conn: &Arc<Mutex<Connection>>, id: i32, amount: i32) -> anyhow::Result<()> {
let conn = conn.lock(); let conn = conn.lock();
let mut stmt = PreparedStatements::set(&conn); let mut stmt = PreparedStatements::set(&conn);
let new: i32 = stmt.query_one((id, amount), |row| row.get(0))?; let new: i32 = stmt.query_one((id, amount), |row| row.get(0))?;
@ -354,15 +365,11 @@ fn do_set(conn: &Arc<Mutex<Connection>>, id: i32, amount: i32) -> rusqlite::Resu
async fn set( async fn set(
State(conn): State<Arc<Mutex<Connection>>>, State(conn): State<Arc<Mutex<Connection>>>,
Path((id, amount)): Path<(i32, i32)>, Path((id, amount)): Path<(i32, i32)>,
) -> Result<Html<String>, StatusCode> { ) -> Result<Html<String>, AppError> {
do_set(&conn, id, amount).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; do_set(&conn, id, amount)?;
let food = get_food(&conn, id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let food = get_food(&conn, id)?;
let sum = get_sum(&conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let sum = get_sum(&conn)?;
let date = get_date(); let date = get_date();
let update = FoodUpdateTemplate { food, sum, date }; let update = FoodUpdateTemplate { food, sum, date };
Ok(Html( Ok(Html(update.render()?))
update
.render()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
))
} }