From 8dab4b9a50ba4b8a65a91d46bcbd6cb88a622621 Mon Sep 17 00:00:00 2001
From: Tobias Reisinger <tobias@msrg.cc>
Date: Mon, 27 Nov 2023 13:33:04 +0100
Subject: [PATCH] Improve database errors (NotFound -> Option)

---
 emgauwa-controller/src/main.rs           | 11 ++------
 emgauwa-lib/src/db/controllers.rs        | 22 +++++++++------
 emgauwa-lib/src/db/errors.rs             | 22 +++++++++------
 emgauwa-lib/src/db/mod.rs                | 35 +++++++++++-------------
 emgauwa-lib/src/db/relays.rs             | 29 ++++++++++++--------
 emgauwa-lib/src/db/schedules.rs          | 22 +++++++++------
 emgauwa-lib/src/db/tag.rs                | 18 ++++++------
 emgauwa-lib/src/handlers/v1/schedules.rs | 12 ++++++--
 emgauwa-lib/src/models/mod.rs            |  3 +-
 9 files changed, 96 insertions(+), 78 deletions(-)

diff --git a/emgauwa-controller/src/main.rs b/emgauwa-controller/src/main.rs
index 7a6e468..20bd992 100644
--- a/emgauwa-controller/src/main.rs
+++ b/emgauwa-controller/src/main.rs
@@ -2,7 +2,6 @@ use std::str;
 
 use crate::relay_loop::run_relay_loop;
 use crate::settings::Settings;
-use emgauwa_lib::db::errors::DatabaseError;
 use emgauwa_lib::db::{DbController, DbRelay};
 use emgauwa_lib::types::ControllerUid;
 use emgauwa_lib::{db, models};
@@ -75,14 +74,10 @@ async fn main() {
 					relay.number.unwrap(),
 				)
 				.await
+				.expect("Failed to get relay from database")
 				{
-					Ok(relay) => relay,
-					Err(err) => match err {
-						DatabaseError::NotFound => {
-							create_this_relay(&mut conn, &db_controller, relay).await
-						}
-						_ => panic!("Failed to get relay from database"),
-					},
+					None => create_this_relay(&mut conn, &db_controller, relay).await,
+					Some(relay) => relay,
 				}
 			})
 		})
diff --git a/emgauwa-lib/src/db/controllers.rs b/emgauwa-lib/src/db/controllers.rs
index b930517..20a4364 100644
--- a/emgauwa-lib/src/db/controllers.rs
+++ b/emgauwa-lib/src/db/controllers.rs
@@ -21,25 +21,26 @@ impl DbController {
 	pub async fn get_all(
 		conn: &mut PoolConnection<Sqlite>,
 	) -> Result<Vec<DbController>, DatabaseError> {
-		Ok(sqlx::query_as!(DbController, "SELECT * FROM controllers")
+		sqlx::query_as!(DbController, "SELECT * FROM controllers")
 			.fetch_all(conn.deref_mut())
-			.await?)
+			.await
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn get(
 		conn: &mut PoolConnection<Sqlite>,
 		id: i64,
-	) -> Result<DbController, DatabaseError> {
+	) -> Result<Option<DbController>, DatabaseError> {
 		sqlx::query_as!(DbController, "SELECT * FROM controllers WHERE id = ?", id)
 			.fetch_optional(conn.deref_mut())
 			.await
-			.map(|s| s.ok_or(DatabaseError::NotFound))?
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn get_by_uid(
 		conn: &mut PoolConnection<Sqlite>,
 		filter_uid: &ControllerUid,
-	) -> Result<DbController, DatabaseError> {
+	) -> Result<Option<DbController>, DatabaseError> {
 		sqlx::query_as!(
 			DbController,
 			"SELECT * FROM controllers WHERE uid = ?",
@@ -47,16 +48,17 @@ impl DbController {
 		)
 		.fetch_optional(conn.deref_mut())
 		.await
-		.map(|s| s.ok_or(DatabaseError::NotFound))?
+		.map_err(DatabaseError::from)
 	}
 
 	pub async fn get_by_tag(
 		conn: &mut PoolConnection<Sqlite>,
 		tag: &DbTag,
 	) -> Result<Vec<DbController>, DatabaseError> {
-		Ok(sqlx::query_as!(DbController, "SELECT schedule.* FROM controllers AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id)
+		sqlx::query_as!(DbController, "SELECT schedule.* FROM controllers AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id)
 			.fetch_all(conn.deref_mut())
-			.await?)
+			.await
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn delete_by_uid(
@@ -109,6 +111,8 @@ impl DbController {
 		.execute(conn.deref_mut())
 		.await?;
 
-		Self::get(conn, self.id).await
+		Self::get(conn, self.id)
+			.await?
+			.ok_or(DatabaseError::UpdateGetError)
 	}
 }
diff --git a/emgauwa-lib/src/db/errors.rs b/emgauwa-lib/src/db/errors.rs
index f71d109..d486c36 100644
--- a/emgauwa-lib/src/db/errors.rs
+++ b/emgauwa-lib/src/db/errors.rs
@@ -12,6 +12,7 @@ pub enum DatabaseError {
 	NotFound,
 	Protected,
 	UpdateError,
+	UpdateGetError,
 	Unknown,
 }
 
@@ -40,17 +41,20 @@ impl Serialize for DatabaseError {
 
 impl From<&DatabaseError> for String {
 	fn from(err: &DatabaseError) -> Self {
-		match err {
-			DatabaseError::InsertError => String::from("error on inserting into database"),
+		String::from(match err {
+			DatabaseError::InsertError => "error on inserting into database",
 			DatabaseError::InsertGetError => {
-				String::from("error on retrieving new entry from database (your entry was saved)")
+				"error on retrieving new entry from database (your entry was saved)"
 			}
-			DatabaseError::NotFound => String::from("model was not found in database"),
-			DatabaseError::DeleteError => String::from("error on deleting from database"),
-			DatabaseError::Protected => String::from("model is protected"),
-			DatabaseError::UpdateError => String::from("error on updating the model"),
-			DatabaseError::Unknown => String::from("unknown error"),
-		}
+			DatabaseError::NotFound => "model was not found in database",
+			DatabaseError::DeleteError => "error on deleting from database",
+			DatabaseError::Protected => "model is protected",
+			DatabaseError::UpdateError => "error on updating the model",
+			DatabaseError::UpdateGetError => {
+				"error on retrieving updated model from database (your entry was saved)"
+			}
+			DatabaseError::Unknown => "unknown error",
+		})
 	}
 }
 
diff --git a/emgauwa-lib/src/db/mod.rs b/emgauwa-lib/src/db/mod.rs
index 7e7df11..031449b 100644
--- a/emgauwa-lib/src/db/mod.rs
+++ b/emgauwa-lib/src/db/mod.rs
@@ -34,25 +34,22 @@ async fn init_schedule(
 	periods: DbPeriods,
 ) -> Result<(), DatabaseError> {
 	trace!("Initializing schedule {:?}", name);
-	match DbSchedule::get_by_uid(&mut pool.acquire().await.unwrap(), uid).await {
-		Ok(_) => Ok(()),
-		Err(err) => match err {
-			DatabaseError::NotFound => {
-				trace!("Schedule {:?} not found, inserting", name);
-				sqlx::query_as!(
-					DbSchedule,
-					"INSERT INTO schedules (uid, name, periods) VALUES (?, ?, ?) RETURNING *",
-					uid,
-					name,
-					periods,
-				)
-				.fetch_optional(pool)
-				.await?
-				.ok_or(DatabaseError::InsertGetError)
-				.map(|_| ())
-			}
-			_ => Err(err),
-		},
+	match DbSchedule::get_by_uid(&mut pool.acquire().await.unwrap(), uid).await? {
+		Some(_) => Ok(()),
+		None => {
+			trace!("Schedule {:?} not found, inserting", name);
+			sqlx::query_as!(
+				DbSchedule,
+				"INSERT INTO schedules (uid, name, periods) VALUES (?, ?, ?) RETURNING *",
+				uid,
+				name,
+				periods,
+			)
+			.fetch_optional(pool)
+			.await?
+			.ok_or(DatabaseError::InsertGetError)
+			.map(|_| ())
+		}
 	}
 }
 
diff --git a/emgauwa-lib/src/db/relays.rs b/emgauwa-lib/src/db/relays.rs
index 70dcb1f..80f9b2b 100644
--- a/emgauwa-lib/src/db/relays.rs
+++ b/emgauwa-lib/src/db/relays.rs
@@ -25,27 +25,30 @@ impl DbRelay {
 			.await?)
 	}
 
-	pub async fn get(conn: &mut PoolConnection<Sqlite>, id: i64) -> Result<DbRelay, DatabaseError> {
-		sqlx::query_as!(DbRelay, "SELECT * FROM relays WHERE id = ?", id)
-			.fetch_optional(conn.deref_mut())
-			.await
-			.map(|s| s.ok_or(DatabaseError::NotFound))?
+	pub async fn get(
+		conn: &mut PoolConnection<Sqlite>,
+		id: i64,
+	) -> Result<Option<DbRelay>, DatabaseError> {
+		Ok(
+			sqlx::query_as!(DbRelay, "SELECT * FROM relays WHERE id = ?", id)
+				.fetch_optional(conn.deref_mut())
+				.await?,
+		)
 	}
 
 	pub async fn get_by_controller_and_num(
 		conn: &mut PoolConnection<Sqlite>,
 		controller: &DbController,
 		number: i64,
-	) -> Result<DbRelay, DatabaseError> {
-		sqlx::query_as!(
+	) -> Result<Option<DbRelay>, DatabaseError> {
+		Ok(sqlx::query_as!(
 			DbRelay,
 			"SELECT * FROM relays WHERE controller_id = ? AND number = ?",
 			controller.id,
 			number
 		)
 		.fetch_optional(conn.deref_mut())
-		.await
-		.map(|s| s.ok_or(DatabaseError::NotFound))?
+		.await?)
 	}
 
 	pub async fn get_by_tag(
@@ -102,14 +105,18 @@ impl DbRelay {
 		.execute(conn.deref_mut())
 		.await?;
 
-		DbRelay::get(conn, self.id).await
+		DbRelay::get(conn, self.id)
+			.await?
+			.ok_or(DatabaseError::UpdateGetError)
 	}
 
 	pub async fn get_controller(
 		&self,
 		conn: &mut PoolConnection<Sqlite>,
 	) -> Result<DbController, DatabaseError> {
-		DbController::get(conn, self.controller_id).await
+		DbController::get(conn, self.controller_id)
+			.await?
+			.ok_or(DatabaseError::NotFound)
 	}
 
 	pub async fn get_tags(
diff --git a/emgauwa-lib/src/db/schedules.rs b/emgauwa-lib/src/db/schedules.rs
index bf5232f..bfe1f27 100644
--- a/emgauwa-lib/src/db/schedules.rs
+++ b/emgauwa-lib/src/db/schedules.rs
@@ -27,25 +27,26 @@ impl DbSchedule {
 	pub async fn get_all(
 		conn: &mut PoolConnection<Sqlite>,
 	) -> Result<Vec<DbSchedule>, DatabaseError> {
-		Ok(sqlx::query_as!(DbSchedule, "SELECT * FROM schedules")
+		sqlx::query_as!(DbSchedule, "SELECT * FROM schedules")
 			.fetch_all(conn.deref_mut())
-			.await?)
+			.await
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn get(
 		conn: &mut PoolConnection<Sqlite>,
 		id: i64,
-	) -> Result<DbSchedule, DatabaseError> {
+	) -> Result<Option<DbSchedule>, DatabaseError> {
 		sqlx::query_as!(DbSchedule, "SELECT * FROM schedules WHERE id = ?", id)
 			.fetch_optional(conn.deref_mut())
 			.await
-			.map(|s| s.ok_or(DatabaseError::NotFound))?
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn get_by_uid(
 		conn: &mut PoolConnection<Sqlite>,
 		filter_uid: &ScheduleUid,
-	) -> Result<DbSchedule, DatabaseError> {
+	) -> Result<Option<DbSchedule>, DatabaseError> {
 		sqlx::query_as!(
 			DbSchedule,
 			"SELECT * FROM schedules WHERE uid = ?",
@@ -53,16 +54,17 @@ impl DbSchedule {
 		)
 		.fetch_optional(conn.deref_mut())
 		.await
-		.map(|s| s.ok_or(DatabaseError::NotFound))?
+		.map_err(DatabaseError::from)
 	}
 
 	pub async fn get_by_tag(
 		conn: &mut PoolConnection<Sqlite>,
 		tag: &DbTag,
 	) -> Result<Vec<DbSchedule>, DatabaseError> {
-		Ok(sqlx::query_as!(DbSchedule, "SELECT schedule.* FROM schedules AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id)
+		sqlx::query_as!(DbSchedule, "SELECT schedule.* FROM schedules AS schedule INNER JOIN junction_tag ON junction_tag.schedule_id = schedule.id WHERE junction_tag.tag_id = ?", tag.id)
 			.fetch_all(conn.deref_mut())
-			.await?)
+			.await
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn delete_by_uid(
@@ -123,7 +125,9 @@ impl DbSchedule {
 		.execute(conn.deref_mut())
 		.await?;
 
-		DbSchedule::get(conn, self.id).await
+		DbSchedule::get(conn, self.id)
+			.await?
+			.ok_or(DatabaseError::UpdateGetError)
 	}
 
 	pub async fn get_tags(
diff --git a/emgauwa-lib/src/db/tag.rs b/emgauwa-lib/src/db/tag.rs
index f788f5b..4f89432 100644
--- a/emgauwa-lib/src/db/tag.rs
+++ b/emgauwa-lib/src/db/tag.rs
@@ -35,32 +35,34 @@ impl DbTag {
 		.ok_or(DatabaseError::InsertGetError)
 	}
 
-	pub async fn get(conn: &mut PoolConnection<Sqlite>, id: i64) -> Result<DbTag, DatabaseError> {
+	pub async fn get(
+		conn: &mut PoolConnection<Sqlite>,
+		id: i64,
+	) -> Result<Option<DbTag>, DatabaseError> {
 		sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE id = ?", id)
 			.fetch_optional(conn.deref_mut())
 			.await
-			.map(|t| t.ok_or(DatabaseError::NotFound))?
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn get_by_tag_or_create(
 		conn: &mut PoolConnection<Sqlite>,
 		target_tag: &str,
 	) -> Result<DbTag, DatabaseError> {
-		match DbTag::get_by_tag(conn, target_tag).await {
-			Ok(tag) => Ok(tag),
-			Err(DatabaseError::NotFound) => DbTag::create(conn, target_tag).await,
-			Err(e) => Err(e),
+		match DbTag::get_by_tag(conn, target_tag).await? {
+			Some(tag) => Ok(tag),
+			None => DbTag::create(conn, target_tag).await,
 		}
 	}
 
 	pub async fn get_by_tag(
 		conn: &mut PoolConnection<Sqlite>,
 		target_tag: &str,
-	) -> Result<DbTag, DatabaseError> {
+	) -> Result<Option<DbTag>, DatabaseError> {
 		sqlx::query_as!(DbTag, "SELECT * FROM tags WHERE tag = ?", target_tag)
 			.fetch_optional(conn.deref_mut())
 			.await
-			.map(|t| t.ok_or(DatabaseError::NotFound))?
+			.map_err(DatabaseError::from)
 	}
 
 	pub async fn link_relay(
diff --git a/emgauwa-lib/src/handlers/v1/schedules.rs b/emgauwa-lib/src/handlers/v1/schedules.rs
index 489bac6..14d4949 100644
--- a/emgauwa-lib/src/handlers/v1/schedules.rs
+++ b/emgauwa-lib/src/handlers/v1/schedules.rs
@@ -39,7 +39,9 @@ pub async fn tagged(
 	let mut pool_conn = pool.acquire().await?;
 
 	let (tag,) = path.into_inner();
-	let tag_db = DbTag::get_by_tag(&mut pool_conn, &tag).await?;
+	let tag_db = DbTag::get_by_tag(&mut pool_conn, &tag)
+		.await?
+		.ok_or(DatabaseError::NotFound)?;
 
 	let schedules = DbSchedule::get_by_tag(&mut pool_conn, &tag_db).await?;
 
@@ -61,7 +63,9 @@ pub async fn show(
 	let (schedule_uid,) = path.into_inner();
 	let uid = ScheduleUid::try_from(schedule_uid.as_str()).or(Err(ApiError::BadUid))?;
 
-	let schedule = DbSchedule::get_by_uid(&mut pool_conn, &uid).await?;
+	let schedule = DbSchedule::get_by_uid(&mut pool_conn, &uid)
+		.await?
+		.ok_or(DatabaseError::NotFound)?;
 
 	let return_schedule = Schedule::from_schedule(schedule, &mut pool_conn);
 	Ok(HttpResponse::Ok().json(return_schedule))
@@ -136,7 +140,9 @@ pub async fn update(
 	let (schedule_uid,) = path.into_inner();
 	let uid = ScheduleUid::try_from(schedule_uid.as_str()).or(Err(ApiError::BadUid))?;
 
-	let schedule = DbSchedule::get_by_uid(&mut pool_conn, &uid).await?;
+	let schedule = DbSchedule::get_by_uid(&mut pool_conn, &uid)
+		.await?
+		.ok_or(DatabaseError::NotFound)?;
 
 	let schedule = schedule
 		.update(&mut pool_conn, data.name.as_str(), &data.periods)
diff --git a/emgauwa-lib/src/models/mod.rs b/emgauwa-lib/src/models/mod.rs
index bfd7a70..fd23583 100644
--- a/emgauwa-lib/src/models/mod.rs
+++ b/emgauwa-lib/src/models/mod.rs
@@ -38,8 +38,7 @@ impl Schedule {
 impl Relay {
 	pub fn from_db_relay(relay: db::DbRelay, conn: &mut PoolConnection<Sqlite>) -> Self {
 		let relay = relay.clone();
-		let controller =
-			executor::block_on(db::DbController::get(conn, relay.controller_id)).unwrap();
+		let controller = executor::block_on(relay.get_controller(conn)).unwrap();
 		let tags = executor::block_on(relay.get_tags(conn)).unwrap();
 
 		Relay {