mockforge_http/
database.rs

1//! Database connection and migration support for mockforge-http
2//!
3//! This module provides optional database support for persistent storage
4//! of drift budgets, incidents, and consumer contracts.
5
6#[cfg(feature = "database")]
7use anyhow::Result as AnyhowResult;
8#[cfg(feature = "database")]
9use sqlx::{postgres::PgPoolOptions, PgPool};
10use std::sync::Arc;
11
12/// Database connection wrapper
13#[derive(Clone)]
14pub struct Database {
15    #[cfg(feature = "database")]
16    pool: Option<Arc<PgPool>>,
17    #[cfg(not(feature = "database"))]
18    _phantom: std::marker::PhantomData<()>,
19}
20
21impl Database {
22    /// Create a new database connection (optional)
23    ///
24    /// If DATABASE_URL is not set or database feature is disabled,
25    /// returns a Database with no connection.
26    /// This allows the application to run without a database.
27    #[cfg(feature = "database")]
28    pub async fn connect_optional(database_url: Option<&str>) -> AnyhowResult<Self> {
29        let pool = if let Some(url) = database_url {
30            if url.is_empty() {
31                None
32            } else {
33                let pool = PgPoolOptions::new().max_connections(10).connect(url).await?;
34                Some(Arc::new(pool))
35            }
36        } else {
37            None
38        };
39
40        Ok(Self { pool })
41    }
42
43    /// Connect to database (no-op when database feature is disabled)
44    #[cfg(not(feature = "database"))]
45    pub async fn connect_optional(_database_url: Option<&str>) -> anyhow::Result<Self> {
46        Ok(Self {
47            _phantom: std::marker::PhantomData,
48        })
49    }
50
51    /// Run migrations if database is connected
52    #[cfg(feature = "database")]
53    pub async fn migrate_if_connected(&self) -> AnyhowResult<()> {
54        if let Some(ref pool) = self.pool {
55            // Run migrations from the migrations directory
56            // Note: This requires the migrations directory to be accessible at runtime
57            match sqlx::migrate!("./migrations").run(pool.as_ref()).await {
58                Ok(_) => {
59                    tracing::info!("Database migrations completed successfully");
60                    Ok(())
61                }
62                Err(e) => {
63                    // If migration was manually applied, log warning but continue
64                    if e.to_string().contains("previously applied but is missing") {
65                        tracing::warn!(
66                            "Migration tracking issue (manually applied migration): {:?}",
67                            e
68                        );
69                        tracing::info!(
70                            "Continuing despite migration tracking issue - database is up to date"
71                        );
72                        Ok(())
73                    } else {
74                        Err(e.into())
75                    }
76                }
77            }
78        } else {
79            tracing::debug!("No database connection, skipping migrations");
80            Ok(())
81        }
82    }
83
84    /// Run database migrations (no-op when database feature is disabled)
85    #[cfg(not(feature = "database"))]
86    pub async fn migrate_if_connected(&self) -> anyhow::Result<()> {
87        tracing::debug!("Database feature not enabled, skipping migrations");
88        Ok(())
89    }
90
91    /// Get the database pool if connected
92    #[cfg(feature = "database")]
93    pub fn pool(&self) -> Option<&PgPool> {
94        self.pool.as_deref()
95    }
96
97    /// Get the database pool (returns None when database feature is disabled)
98    #[cfg(not(feature = "database"))]
99    pub fn pool(&self) -> Option<()> {
100        None
101    }
102
103    /// Check if database is connected
104    pub fn is_connected(&self) -> bool {
105        #[cfg(feature = "database")]
106        {
107            self.pool.is_some()
108        }
109        #[cfg(not(feature = "database"))]
110        {
111            false
112        }
113    }
114}