diff --git a/src/db/error.rs b/src/db/error.rs new file mode 100644 index 0000000..f8ee401 --- /dev/null +++ b/src/db/error.rs @@ -0,0 +1,152 @@ +use std::fmt; + +use sqlx::error::DatabaseError as _; + +pub enum DatabaseError { + /// Errors we can receive **from** the database that are caused by connection + /// problems or schema problems (e.g. we get a return value that does not fit our enum, + /// or a wrongly formatted date) + Sql { + description: String, + }, + Uuid { + description: String, + }, + Enum { + description: String, + }, + TimeParse { + description: String, + }, +} + +impl fmt::Display for DatabaseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Sql { description } => { + write!(f, "SQL error: {description}") + } + Self::Uuid { description } => { + write!(f, "UUID error: {description}") + } + Self::Enum { description } => { + write!(f, "Enum error: {description}") + } + Self::TimeParse { description } => { + write!(f, "Date parse error: {description}") + } + } + } +} + +pub enum QueryError { + /// Errors that are caused by wrong input data, e.g. ids that cannot be found, or + /// inserts that violate unique constraints + Duplicate { + description: String, + }, + NotFound { + description: String, + }, + ReferenceNotFound { + description: String, + }, +} + +impl fmt::Display for QueryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Duplicate { description } => { + write!(f, "Duplicate data entry: {description}") + } + Self::NotFound { description } => { + write!(f, "not found: {description}") + } + Self::ReferenceNotFound { description } => { + write!(f, "SQL foreign key reference was not found: {description}") + } + } + } +} + +pub enum Error { + Database(DatabaseError), + Query(QueryError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Database(error) => write!(f, "{error}"), + Self::Query(error) => write!(f, "{error}"), + } + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // defer to Display + write!(f, "SQL error: {self}") + } +} + +impl From for Error { + fn from(value: uuid::Error) -> Self { + Error::Database(DatabaseError::Uuid { + description: value.to_string(), + }) + } +} + +impl From for Error { + fn from(value: time::error::Format) -> Self { + Error::Database(DatabaseError::TimeParse { + description: value.to_string(), + }) + } +} + +impl From for Error { + fn from(value: sqlx::Error) -> Self { + match value { + sqlx::Error::RowNotFound => Error::Query(QueryError::NotFound { + description: value.to_string(), + }), + sqlx::Error::Database(ref error) => { + let sqlite_error = error.downcast_ref::(); + if let Some(code) = sqlite_error.code() { + match &*code { + // SQLITE_CONSTRAINT_FOREIGNKEY + "787" => Error::Query(QueryError::ReferenceNotFound { + description: "foreign key reference not found".to_string(), + }), + // SQLITE_CONSTRAINT_UNIQUE + "2067" => Error::Query(QueryError::Duplicate { + description: "item with unique constraint already exists".to_string(), + }), + _ => Error::Database(DatabaseError::Sql { + description: format!("got error with unknown code: {sqlite_error}"), + }), + } + } else { + Error::Database(DatabaseError::Sql { + description: format!("got error without code: {sqlite_error}"), + }) + } + } + _ => Error::Database(DatabaseError::Sql { + description: format!("got unknown error: {value}"), + }), + } + } +} + +impl From for Error { + fn from(value: time::error::Parse) -> Self { + Error::Database(DatabaseError::TimeParse { + description: value.to_string(), + }) + } +} + +impl std::error::Error for Error {} diff --git a/src/db/mod.rs b/src/db/mod.rs index a9cbee4..5db733f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -2,6 +2,7 @@ use base64::Engine as _; use sha2::{Digest, Sha256}; use std::fmt; +pub mod error; pub mod postgres; pub mod sqlite; diff --git a/src/models/error.rs b/src/models/error.rs index f8ee401..7f311fb 100644 --- a/src/models/error.rs +++ b/src/models/error.rs @@ -1,152 +1 @@ -use std::fmt; - -use sqlx::error::DatabaseError as _; - -pub enum DatabaseError { - /// Errors we can receive **from** the database that are caused by connection - /// problems or schema problems (e.g. we get a return value that does not fit our enum, - /// or a wrongly formatted date) - Sql { - description: String, - }, - Uuid { - description: String, - }, - Enum { - description: String, - }, - TimeParse { - description: String, - }, -} - -impl fmt::Display for DatabaseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Sql { description } => { - write!(f, "SQL error: {description}") - } - Self::Uuid { description } => { - write!(f, "UUID error: {description}") - } - Self::Enum { description } => { - write!(f, "Enum error: {description}") - } - Self::TimeParse { description } => { - write!(f, "Date parse error: {description}") - } - } - } -} - -pub enum QueryError { - /// Errors that are caused by wrong input data, e.g. ids that cannot be found, or - /// inserts that violate unique constraints - Duplicate { - description: String, - }, - NotFound { - description: String, - }, - ReferenceNotFound { - description: String, - }, -} - -impl fmt::Display for QueryError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Duplicate { description } => { - write!(f, "Duplicate data entry: {description}") - } - Self::NotFound { description } => { - write!(f, "not found: {description}") - } - Self::ReferenceNotFound { description } => { - write!(f, "SQL foreign key reference was not found: {description}") - } - } - } -} - -pub enum Error { - Database(DatabaseError), - Query(QueryError), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Database(error) => write!(f, "{error}"), - Self::Query(error) => write!(f, "{error}"), - } - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // defer to Display - write!(f, "SQL error: {self}") - } -} - -impl From for Error { - fn from(value: uuid::Error) -> Self { - Error::Database(DatabaseError::Uuid { - description: value.to_string(), - }) - } -} - -impl From for Error { - fn from(value: time::error::Format) -> Self { - Error::Database(DatabaseError::TimeParse { - description: value.to_string(), - }) - } -} - -impl From for Error { - fn from(value: sqlx::Error) -> Self { - match value { - sqlx::Error::RowNotFound => Error::Query(QueryError::NotFound { - description: value.to_string(), - }), - sqlx::Error::Database(ref error) => { - let sqlite_error = error.downcast_ref::(); - if let Some(code) = sqlite_error.code() { - match &*code { - // SQLITE_CONSTRAINT_FOREIGNKEY - "787" => Error::Query(QueryError::ReferenceNotFound { - description: "foreign key reference not found".to_string(), - }), - // SQLITE_CONSTRAINT_UNIQUE - "2067" => Error::Query(QueryError::Duplicate { - description: "item with unique constraint already exists".to_string(), - }), - _ => Error::Database(DatabaseError::Sql { - description: format!("got error with unknown code: {sqlite_error}"), - }), - } - } else { - Error::Database(DatabaseError::Sql { - description: format!("got error without code: {sqlite_error}"), - }) - } - } - _ => Error::Database(DatabaseError::Sql { - description: format!("got unknown error: {value}"), - }), - } - } -} - -impl From for Error { - fn from(value: time::error::Parse) -> Self { - Error::Database(DatabaseError::TimeParse { - description: value.to_string(), - }) - } -} - -impl std::error::Error for Error {} +pub use crate::db::error::{Error, QueryError, DatabaseError}; diff --git a/src/models/trips/mod.rs b/src/models/trips/mod.rs index 39e519b..2729d04 100644 --- a/src/models/trips/mod.rs +++ b/src/models/trips/mod.rs @@ -1018,11 +1018,7 @@ impl Trip { } #[tracing::instrument] - pub async fn load_todos( - &mut self, - ctx: &Context, - pool: &db::Pool, - ) -> Result<(), Error> { + pub async fn load_todos(&mut self, ctx: &Context, pool: &db::Pool) -> Result<(), Error> { self.todos = Some( crate::components::trips::todos::Todo::findall( ctx, @@ -1035,11 +1031,7 @@ impl Trip { } #[tracing::instrument] - pub async fn load_trips_types( - &mut self, - ctx: &Context, - pool: &db::Pool, - ) -> Result<(), Error> { + pub async fn load_trips_types(&mut self, ctx: &Context, pool: &db::Pool) -> Result<(), Error> { let user_id = ctx.user.id.to_string(); let id = self.id.to_string(); let types = crate::query_all!( @@ -1176,11 +1168,7 @@ impl Trip { } #[tracing::instrument] - pub async fn load_categories( - &mut self, - ctx: &Context, - pool: &db::Pool, - ) -> Result<(), Error> { + pub async fn load_categories(&mut self, ctx: &Context, pool: &db::Pool) -> Result<(), Error> { let mut categories: Vec = vec![]; // we can ignore the return type as we collect into `categories` // in the `map_ok()` closure