more agnostic
This commit is contained in:
152
src/db/error.rs
Normal file
152
src/db/error.rs
Normal file
@@ -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<uuid::Error> for Error {
|
||||||
|
fn from(value: uuid::Error) -> Self {
|
||||||
|
Error::Database(DatabaseError::Uuid {
|
||||||
|
description: value.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<time::error::Format> for Error {
|
||||||
|
fn from(value: time::error::Format) -> Self {
|
||||||
|
Error::Database(DatabaseError::TimeParse {
|
||||||
|
description: value.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sqlx::Error> 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::<sqlx::sqlite::SqliteError>();
|
||||||
|
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<time::error::Parse> for Error {
|
||||||
|
fn from(value: time::error::Parse) -> Self {
|
||||||
|
Error::Database(DatabaseError::TimeParse {
|
||||||
|
description: value.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
@@ -2,6 +2,7 @@ use base64::Engine as _;
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
pub mod postgres;
|
pub mod postgres;
|
||||||
pub mod sqlite;
|
pub mod sqlite;
|
||||||
|
|
||||||
|
|||||||
@@ -1,152 +1 @@
|
|||||||
use std::fmt;
|
pub use crate::db::error::{Error, QueryError, DatabaseError};
|
||||||
|
|
||||||
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<uuid::Error> for Error {
|
|
||||||
fn from(value: uuid::Error) -> Self {
|
|
||||||
Error::Database(DatabaseError::Uuid {
|
|
||||||
description: value.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<time::error::Format> for Error {
|
|
||||||
fn from(value: time::error::Format) -> Self {
|
|
||||||
Error::Database(DatabaseError::TimeParse {
|
|
||||||
description: value.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<sqlx::Error> 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::<sqlx::sqlite::SqliteError>();
|
|
||||||
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<time::error::Parse> for Error {
|
|
||||||
fn from(value: time::error::Parse) -> Self {
|
|
||||||
Error::Database(DatabaseError::TimeParse {
|
|
||||||
description: value.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|||||||
@@ -1018,11 +1018,7 @@ impl Trip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn load_todos(
|
pub async fn load_todos(&mut self, ctx: &Context, pool: &db::Pool) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
ctx: &Context,
|
|
||||||
pool: &db::Pool,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.todos = Some(
|
self.todos = Some(
|
||||||
crate::components::trips::todos::Todo::findall(
|
crate::components::trips::todos::Todo::findall(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -1035,11 +1031,7 @@ impl Trip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn load_trips_types(
|
pub async fn load_trips_types(&mut self, ctx: &Context, pool: &db::Pool) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
ctx: &Context,
|
|
||||||
pool: &db::Pool,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let user_id = ctx.user.id.to_string();
|
let user_id = ctx.user.id.to_string();
|
||||||
let id = self.id.to_string();
|
let id = self.id.to_string();
|
||||||
let types = crate::query_all!(
|
let types = crate::query_all!(
|
||||||
@@ -1176,11 +1168,7 @@ impl Trip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn load_categories(
|
pub async fn load_categories(&mut self, ctx: &Context, pool: &db::Pool) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
ctx: &Context,
|
|
||||||
pool: &db::Pool,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut categories: Vec<TripCategory> = vec![];
|
let mut categories: Vec<TripCategory> = vec![];
|
||||||
// we can ignore the return type as we collect into `categories`
|
// we can ignore the return type as we collect into `categories`
|
||||||
// in the `map_ok()` closure
|
// in the `map_ok()` closure
|
||||||
|
|||||||
Reference in New Issue
Block a user