From f5fc85cb00bde742a6e969d99a6e56a44c8e0565 Mon Sep 17 00:00:00 2001 From: Dmitri Date: Fri, 24 Apr 2026 22:04:50 +0200 Subject: [PATCH] errors --- Cargo.lock | 1 + Cargo.toml | 1 + src/config.rs | 29 +++++++++++++++++------------ src/database.rs | 33 +++++++++++++++------------------ src/errors.rs | 26 ++++++++++++++++++++++++++ src/main.rs | 23 +++++++++++------------ 6 files changed, 71 insertions(+), 42 deletions(-) create mode 100644 src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 7bc0fac..d7e86f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,6 +1081,7 @@ dependencies = [ "axum", "dotenvy", "sqlx", + "thiserror", "tokio", "tracing", "tracing-appender", diff --git a/Cargo.toml b/Cargo.toml index 1e85b82..07d461b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ tracing-tree = "0.4.1" tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros", "signal"] } sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "time", "uuid" ] } axum = "0.8.9" +thiserror = "2" diff --git a/src/config.rs b/src/config.rs index 834650a..010741d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,8 @@ use std::env; use dotenvy::dotenv; +use crate::errors::AppError; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppEnv { Development, @@ -9,12 +11,15 @@ pub enum AppEnv { } impl AppEnv { - pub fn from_env() -> Self { - match std::env::var("APP_ENV").as_deref() { - Ok("prod") => AppEnv::Production, - Ok("dev") => AppEnv::Development, - Ok(other) => panic!("Invalid APP_ENV: {}", other), - Err(_) => panic!("APP_ENV must be set"), + pub fn from_env() -> Result { + match env::var("APP_ENV").as_deref() { + Ok("prod") => Ok(AppEnv::Production), + Ok("dev") => Ok(AppEnv::Development), + Ok(other) => Err(AppError::InvalidConfig(format!( + "Invalid APP_ENV: {}", + other + ))), + Err(_) => Err(AppError::InvalidConfig("APP_ENV must be set".to_string())), } } } @@ -27,12 +32,12 @@ pub struct Config { } impl Config { - pub fn load() -> Self { + pub fn load() -> Result { dotenv().ok(); - Self { - db_url: env::var("DB_URL").expect("DB_URL is not configured"), - socket_address: env::var("SOCKET_ADDRESS").expect("SOCKET_ADDRESS is not configured"), - app_env: AppEnv::from_env(), - } + Ok(Self { + db_url: env::var("DB_URL")?, + socket_address: env::var("SOCKET_ADDRESS")?, + app_env: AppEnv::from_env()?, + }) } } diff --git a/src/database.rs b/src/database.rs index dc79422..95faff3 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,22 +1,19 @@ -use std::process::exit; +use sqlx::{Pool, Postgres, postgres::PgPoolOptions, migrate::MigrateError}; -use sqlx::{Pool, Postgres, postgres::PgPoolOptions}; +use crate::errors::AppError; -pub async fn init(db_url: &str) -> Pool { - let db = match PgPoolOptions::new().connect(db_url).await { - Ok(p) => p, - Err(_) => { - tracing::error!("Failed to connect to the database"); - exit(1); - } - }; - match sqlx::migrate!().run(&db).await { - Ok(_) => tracing::info!("Migration completed succesfully"), - Err(_) => { - tracing::error!("Failed to apply migrations"); - exit(1) - } - } +pub async fn init(db_url: &str) -> Result, AppError> { + let db = PgPoolOptions::new() + .connect(db_url) + .await + .map_err(AppError::DbConnect)?; - db + sqlx::migrate!() + .run(&db) + .await + .map_err(|e: MigrateError| AppError::InvalidConfig(format!("Migration failed: {}", e)))?; + + tracing::info!("Migration completed successfully"); + + Ok(db) } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..1c3d795 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,26 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("Failed to load configuration: {0}")] + Config(#[from] std::env::VarError), + + #[error("Invalid configuration value: {0}")] + InvalidConfig(String), + + #[error("Failed to connect to database")] + DbConnect(#[from] sqlx::Error), + + #[error("Failed to bind to address")] + Bind(#[from] std::io::Error), +} + +#[derive(Debug, Error)] +#[error("Application error: {0}")] +pub struct MainError(pub AppError); + +impl From for MainError { + fn from(err: AppError) -> Self { + Self(err) + } +} diff --git a/src/main.rs b/src/main.rs index b76a9a2..321b4d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,27 +2,26 @@ use axum::{Router, routing::get}; mod config; mod database; +mod errors; mod logging; +use errors::{AppError, MainError}; + #[tokio::main] -async fn main() { - let cfg = config::Config::load(); +async fn main() -> Result<(), MainError> { + let cfg = config::Config::load()?; let _logging_guard = logging::LoggerConfig::init(cfg.app_env); - let _db = database::init(&cfg.db_url).await; - let app = Router::new().route( - "/", - get(|| async { - tracing::info!("ciao"); - return "ciao"; - }), - ); + let _db = database::init(&cfg.db_url).await?; + let app = Router::new().route("/", get(|| async { "ciao" })); let listener = tokio::net::TcpListener::bind(&cfg.socket_address) .await - .unwrap(); + .map_err(AppError::Bind)?; tracing::info!("Server started on {}", cfg.socket_address); axum::serve(listener, app) .with_graceful_shutdown(logging::shutdown_signal()) .await - .unwrap(); + .map_err(AppError::Bind)?; + + Ok(()) }