Skip to main content

dbrest_core/app/
state.rs

1//! Application state shared across all handlers.
2//!
3//! [`AppState`] is the central struct holding the database backend,
4//! configuration, schema cache, authentication state, metrics, and
5//! database version info. It is cheaply cloneable (all fields are
6//! `Arc`-wrapped) and passed to every axum handler via `State<AppState>`.
7
8use std::sync::Arc;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11use arc_swap::ArcSwap;
12
13use crate::auth::JwtCache;
14use crate::auth::middleware::AuthState;
15use crate::backend::{DatabaseBackend, DbVersion, SqlDialect};
16use crate::config::AppConfig;
17use crate::error::Error;
18use crate::schema_cache::SchemaCache;
19
20// Keep PgVersion as a compatibility alias during migration
21/// PostgreSQL server version.
22///
23/// Deprecated — use [`DbVersion`] from the backend module instead.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct PgVersion {
26    pub major: u32,
27    pub minor: u32,
28    pub patch: u32,
29}
30
31impl std::fmt::Display for PgVersion {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
34    }
35}
36
37impl From<&DbVersion> for PgVersion {
38    fn from(v: &DbVersion) -> Self {
39        Self {
40            major: v.major,
41            minor: v.minor,
42            patch: v.patch,
43        }
44    }
45}
46
47/// Application-level metrics (atomic counters).
48#[derive(Debug, Default)]
49pub struct Metrics {
50    pub requests_total: AtomicU64,
51    pub requests_success: AtomicU64,
52    pub requests_error: AtomicU64,
53    pub db_queries_total: AtomicU64,
54    pub schema_cache_reloads: AtomicU64,
55    pub jwt_cache_hits: AtomicU64,
56    pub jwt_cache_misses: AtomicU64,
57}
58
59/// Central application state.
60///
61/// Constructed once at startup and shared across all handlers.
62/// The `config` and `schema_cache` fields use `ArcSwap` for
63/// lock-free reads and atomic replacement during live reload.
64#[derive(Clone)]
65pub struct AppState {
66    /// Database backend (connection pool + execution + introspection).
67    pub db: Arc<dyn DatabaseBackend>,
68    /// SQL dialect for the active backend.
69    pub dialect: Arc<dyn SqlDialect>,
70    /// Atomically swappable configuration.
71    pub config: Arc<ArcSwap<AppConfig>>,
72    /// Atomically swappable schema cache.
73    pub schema_cache: Arc<ArcSwap<Option<SchemaCache>>>,
74    /// Authentication state (JWT cache + config reference).
75    pub auth: AuthState,
76    /// JWT validation cache (shared with AuthState).
77    pub jwt_cache: JwtCache,
78    /// Application metrics.
79    pub metrics: Arc<Metrics>,
80    /// Database version.
81    pub db_version: DbVersion,
82    /// PostgreSQL version (legacy — prefer `db_version` field).
83    pub pg_version: PgVersion,
84}
85
86impl AppState {
87    /// Create a new `AppState` from a database backend, dialect, config, and version.
88    ///
89    /// The schema cache starts as `None` — call [`Self::reload_schema_cache`]
90    /// after construction.
91    pub fn new_with_backend(
92        db: Arc<dyn DatabaseBackend>,
93        dialect: Arc<dyn SqlDialect>,
94        config: AppConfig,
95        db_version: DbVersion,
96    ) -> Self {
97        let pg_version = PgVersion::from(&db_version);
98        let config_swap = Arc::new(ArcSwap::new(Arc::new(config)));
99        let auth = AuthState::with_shared_config(config_swap.clone());
100        let jwt_cache = auth.cache.clone();
101        Self {
102            db,
103            dialect,
104            config: config_swap,
105            schema_cache: Arc::new(ArcSwap::new(Arc::new(None))),
106            auth,
107            jwt_cache,
108            metrics: Arc::new(Metrics::default()),
109            db_version,
110            pg_version,
111        }
112    }
113
114    /// Get the current config snapshot.
115    pub fn config(&self) -> arc_swap::Guard<Arc<AppConfig>> {
116        self.config.load()
117    }
118
119    /// Get the current schema cache (may be `None` if not loaded).
120    pub fn schema_cache_guard(&self) -> arc_swap::Guard<Arc<Option<SchemaCache>>> {
121        self.schema_cache.load()
122    }
123
124    /// Reload configuration from file and environment.
125    pub async fn reload_config(&self) -> Result<(), Error> {
126        let current = self.config.load();
127        let file_path = current.config_file_path.clone();
128        let new_config =
129            crate::config::load_config(file_path.as_deref(), std::collections::HashMap::new())
130                .await
131                .map_err(|e| Error::InvalidConfig {
132                    message: e.to_string(),
133                })?;
134
135        self.config.store(Arc::new(new_config));
136
137        tracing::info!("Configuration reloaded successfully");
138        Ok(())
139    }
140
141    /// Load or reload the schema cache from the database.
142    pub async fn reload_schema_cache(&self) -> Result<(), Error> {
143        let config = self.config.load();
144        let introspector = self.db.introspector();
145        let cache = SchemaCache::load(&*introspector, &config).await?;
146
147        self.metrics
148            .schema_cache_reloads
149            .fetch_add(1, Ordering::Relaxed);
150        self.schema_cache.store(Arc::new(Some(cache)));
151
152        tracing::info!("Schema cache reloaded successfully");
153        Ok(())
154    }
155}