feat(config): add database configuration layer with backend detection
Add AppConfig loaded from .env via dotenvy. DATABASE_URL prefix determines the backend (sqlite:// → SQLite, postgresql:// → PostgreSQL). ConfigError via thiserror gives clear messages on missing or unknown URLs. Server logs the chosen backend at startup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
10
src/main.rs
10
src/main.rs
@@ -21,7 +21,7 @@ async fn main() {
|
||||
use leptos::view;
|
||||
use rust_ipam::{
|
||||
app::{App, Shell},
|
||||
server::routes::not_found_handler,
|
||||
server::{config::AppConfig, routes::not_found_handler},
|
||||
};
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
@@ -36,6 +36,14 @@ async fn main() {
|
||||
|
||||
tracing::info!("Démarrage du serveur Rust IPAM...");
|
||||
|
||||
// Charge la configuration depuis .env / variables d'environnement.
|
||||
// On arrête le serveur immédiatement si la config est invalide — il ne peut
|
||||
// pas fonctionner sans savoir à quelle base de données se connecter.
|
||||
let app_config = AppConfig::from_env()
|
||||
.expect("Erreur de configuration — vérifier le fichier .env");
|
||||
|
||||
tracing::info!("Base de données : {} ({})", app_config.backend, app_config.database_url);
|
||||
|
||||
// `Some("Cargo.toml")` indique à Leptos de lire la section
|
||||
// [package.metadata.leptos] du Cargo.toml pour la configuration
|
||||
// (noms de fichiers, chemins, adresse serveur...).
|
||||
|
||||
101
src/server/config.rs
Normal file
101
src/server/config.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
// server/config.rs — Configuration de l'application
|
||||
//
|
||||
// Ce module charge et valide la configuration au démarrage du serveur.
|
||||
// Toute la configuration passe par des variables d'environnement,
|
||||
// elles-mêmes chargées depuis un fichier `.env` via dotenvy.
|
||||
//
|
||||
// Principe : une seule variable suffit pour choisir la base de données.
|
||||
// DATABASE_URL=sqlite://data/ipam.db → SQLite (dev, pas de serveur nécessaire)
|
||||
// DATABASE_URL=postgresql://user:pw@host/db → PostgreSQL (production)
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
// ─── Erreurs de configuration ─────────────────────────────────────────────────
|
||||
|
||||
// `#[derive(Error)]` de la crate thiserror génère automatiquement l'impl
|
||||
// du trait standard `std::error::Error` — pas besoin de l'écrire à la main.
|
||||
//
|
||||
// `#[error("...")]` définit le message affiché par Display / println!("{}", err).
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
// `#[from]` permet de convertir automatiquement un VarError en ConfigError
|
||||
// via l'opérateur `?`. Ex: std::env::var("X")? dans une fn -> Result<_, ConfigError>
|
||||
#[error("Variable d'environnement manquante : {0}")]
|
||||
MissingVar(#[from] std::env::VarError),
|
||||
|
||||
#[error("URL de base de données non reconnue : '{0}' — doit commencer par sqlite:// ou postgresql://")]
|
||||
UnknownBackend(String),
|
||||
}
|
||||
|
||||
// ─── Backend de base de données ───────────────────────────────────────────────
|
||||
|
||||
// `#[derive(Debug, Clone)]` génère automatiquement :
|
||||
// - Debug : permet d'afficher la valeur avec {:?} dans les logs
|
||||
// - Clone : permet de copier la valeur (nécessaire pour l'état Axum)
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DatabaseBackend {
|
||||
Postgres,
|
||||
Sqlite,
|
||||
}
|
||||
|
||||
// Impl Display pour pouvoir écrire tracing::info!("Backend : {}", backend)
|
||||
impl std::fmt::Display for DatabaseBackend {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DatabaseBackend::Postgres => write!(f, "PostgreSQL"),
|
||||
DatabaseBackend::Sqlite => write!(f, "SQLite"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Configuration principale ─────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppConfig {
|
||||
/// URL complète de connexion à la base de données.
|
||||
/// Ex: "sqlite://data/ipam.db" ou "postgresql://user:pw@localhost/ipam"
|
||||
pub database_url: String,
|
||||
|
||||
/// Backend détecté automatiquement depuis le préfixe de DATABASE_URL.
|
||||
pub backend: DatabaseBackend,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Charge la configuration depuis les variables d'environnement.
|
||||
///
|
||||
/// Ordre de priorité pour les variables :
|
||||
/// 1. Variables déjà définies dans le shell (ex: export DATABASE_URL=...)
|
||||
/// 2. Fichier `.env` à la racine du projet
|
||||
///
|
||||
/// Retourne une `ConfigError` si DATABASE_URL est absente ou invalide.
|
||||
pub fn from_env() -> Result<Self, ConfigError> {
|
||||
// Charge le fichier .env s'il existe.
|
||||
// `let _ =` ignore silencieusement l'erreur si .env est absent —
|
||||
// c'est voulu : en production les variables sont injectées directement.
|
||||
let _ = dotenvy::dotenv();
|
||||
|
||||
// `std::env::var` retourne Result<String, VarError>.
|
||||
// Le `?` propage l'erreur VarError, convertie en ConfigError::MissingVar
|
||||
// grâce au `#[from]` défini plus haut.
|
||||
let database_url = std::env::var("DATABASE_URL")?;
|
||||
|
||||
let backend = Self::detect_backend(&database_url)?;
|
||||
|
||||
Ok(Self { database_url, backend })
|
||||
}
|
||||
|
||||
/// Déduit le backend depuis le préfixe de l'URL.
|
||||
///
|
||||
/// `&str` : référence vers une chaîne — pas de copie, juste un emprunt.
|
||||
fn detect_backend(url: &str) -> Result<DatabaseBackend, ConfigError> {
|
||||
// `starts_with` est une méthode de &str qui vérifie le préfixe
|
||||
if url.starts_with("postgresql://") || url.starts_with("postgres://") {
|
||||
Ok(DatabaseBackend::Postgres)
|
||||
} else if url.starts_with("sqlite://") {
|
||||
Ok(DatabaseBackend::Sqlite)
|
||||
} else {
|
||||
// `to_string()` crée un String owned depuis un &str
|
||||
Err(ConfigError::UnknownBackend(url.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,4 +9,5 @@
|
||||
// Elles apparaissent comme des appels asynchrones normaux côté client,
|
||||
// mais s'exécutent exclusivement sur le serveur — transparence totale.
|
||||
|
||||
pub mod config;
|
||||
pub mod routes;
|
||||
|
||||
Reference in New Issue
Block a user