feat: implement custom error handling with AppError enum and update dependencies #4

Merged
GW_MC merged 1 commits from feature/error-handling into master 2026-02-16 13:26:13 +08:00
5 changed files with 88 additions and 2 deletions

2
Cargo.lock generated
View File

@@ -1276,7 +1276,7 @@ dependencies = [
"tauri",
"tauri-build",
"tauri-plugin-opener",
"thiserror 1.0.69",
"thiserror 2.0.18",
"uuid",
]

View File

@@ -25,7 +25,7 @@ serde_json = { workspace = true }
sea-orm = { workspace = true }
chrono = { workspace = true }
migration = { path = "../crates/migration" }
thiserror = "1"
thiserror = "2"
rust_decimal = "1"
uuid = { version = "1", features = ["v4"] }

View File

@@ -0,0 +1,84 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sea_orm::DbErr),
#[error("Validation error: {0}")]
Validation(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Invalid amount: {0}")]
InvalidAmount(String),
#[error("Currency mismatch: expected {expected}, got {actual}")]
CurrencyMismatch { expected: String, actual: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Internal error: {0}")]
Internal(String),
#[error("{0}")]
Other(String),
}
#[derive(serde::Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
enum ErrorKind {
Database(String),
Validation(String),
NotFound(String),
InvalidAmount(String),
CurrencyMismatch { expected: String, actual: String },
Io(String),
Serialization(String),
Internal(String),
Other(String),
}
impl serde::Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let error_message = self.to_string();
let error_kind: ErrorKind = match self {
Self::Io(_) => ErrorKind::Io(error_message),
Self::Database(_) => ErrorKind::Database(error_message),
Self::Validation(_) => ErrorKind::Validation(error_message),
Self::NotFound(_) => ErrorKind::NotFound(error_message),
Self::InvalidAmount(_) => ErrorKind::InvalidAmount(error_message),
Self::CurrencyMismatch { expected, actual } => ErrorKind::CurrencyMismatch {
expected: expected.clone(),
actual: actual.clone(),
},
Self::Serialization(_) => ErrorKind::Serialization(error_message),
Self::Internal(_) => ErrorKind::Internal(error_message),
Self::Other(_) => ErrorKind::Other(error_message),
};
error_kind.serialize(serializer)
}
}
impl From<AppError> for String {
fn from(error: AppError) -> Self {
error.to_string()
}
}
impl From<rust_decimal::Error> for AppError {
fn from(error: rust_decimal::Error) -> Self {
AppError::InvalidAmount(error.to_string())
}
}
pub type CommandResult<T> = std::result::Result<T, AppError>;

View File

@@ -0,0 +1 @@
pub mod app_error;

View File

@@ -1,4 +1,5 @@
mod db;
mod errors;
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]