Skip to main content

signet_cold_sql/
connector.rs

1//! SQL cold storage connector.
2
3use crate::{SqlColdBackend, SqlColdError};
4use signet_cold::ColdConnect;
5use sqlx::pool::PoolOptions;
6use std::time::Duration;
7
8/// Errors that can occur when initializing SQL connectors.
9#[derive(Debug, thiserror::Error)]
10pub enum SqlConnectorError {
11    /// Missing environment variable.
12    #[error("missing environment variable: {0}")]
13    MissingEnvVar(&'static str),
14
15    /// Cold storage initialization failed.
16    #[error("cold storage initialization failed: {0}")]
17    ColdInit(#[from] SqlColdError),
18}
19
20/// Connector for SQL cold storage (PostgreSQL or SQLite).
21///
22/// Automatically detects the database type from the URL:
23/// - URLs starting with `postgres://` or `postgresql://` use PostgreSQL
24/// - URLs starting with `sqlite:` use SQLite
25///
26/// Pool behaviour is configured via builder methods that mirror
27/// [`sqlx::pool::PoolOptions`], or by passing a complete
28/// [`PoolOptions`] via [`with_pool_options`](Self::with_pool_options).
29/// For in-memory SQLite URLs, `max_connections` is forced to 1
30/// regardless of the provided options.
31///
32/// # Example
33///
34/// ```ignore
35/// use signet_cold_sql::SqlConnector;
36///
37/// // PostgreSQL with custom pool size
38/// let pg = SqlConnector::new("postgres://localhost/signet")
39///     .with_max_connections(20);
40/// let backend = pg.connect().await?;
41///
42/// // SQLite (defaults)
43/// let sqlite = SqlConnector::new("sqlite::memory:");
44/// let backend = sqlite.connect().await?;
45/// ```
46#[cfg(any(feature = "sqlite", feature = "postgres"))]
47#[derive(Debug, Clone)]
48pub struct SqlConnector {
49    url: String,
50    pool_opts: PoolOptions<sqlx::Any>,
51}
52
53#[cfg(any(feature = "sqlite", feature = "postgres"))]
54impl SqlConnector {
55    /// Create a new SQL connector with default pool options.
56    ///
57    /// The database type is detected from the URL prefix.
58    pub fn new(url: impl Into<String>) -> Self {
59        Self { url: url.into(), pool_opts: PoolOptions::new() }
60    }
61
62    /// Get a reference to the connection URL.
63    pub fn url(&self) -> &str {
64        &self.url
65    }
66
67    /// Replace the pool options entirely.
68    ///
69    /// For in-memory SQLite URLs, `max_connections` is forced to 1
70    /// regardless of the value set here.
71    pub fn with_pool_options(mut self, pool_opts: PoolOptions<sqlx::Any>) -> Self {
72        self.pool_opts = pool_opts;
73        self
74    }
75
76    /// Set the maximum number of pool connections.
77    ///
78    /// Ignored for in-memory SQLite URLs, which always use 1.
79    pub fn with_max_connections(mut self, n: u32) -> Self {
80        self.pool_opts = self.pool_opts.max_connections(n);
81        self
82    }
83
84    /// Set the minimum number of connections to maintain at all times.
85    pub fn with_min_connections(mut self, n: u32) -> Self {
86        self.pool_opts = self.pool_opts.min_connections(n);
87        self
88    }
89
90    /// Set the connection acquire timeout.
91    pub fn with_acquire_timeout(mut self, timeout: Duration) -> Self {
92        self.pool_opts = self.pool_opts.acquire_timeout(timeout);
93        self
94    }
95
96    /// Set the maximum lifetime of individual connections.
97    pub fn with_max_lifetime(mut self, lifetime: Option<Duration>) -> Self {
98        self.pool_opts = self.pool_opts.max_lifetime(lifetime);
99        self
100    }
101
102    /// Set the idle timeout for connections.
103    pub fn with_idle_timeout(mut self, timeout: Option<Duration>) -> Self {
104        self.pool_opts = self.pool_opts.idle_timeout(timeout);
105        self
106    }
107
108    /// Create a connector from environment variables.
109    ///
110    /// Reads the SQL URL from the specified environment variable.
111    /// Uses default pool settings.
112    ///
113    /// # Example
114    ///
115    /// ```ignore
116    /// use signet_cold_sql::SqlConnector;
117    ///
118    /// let cold = SqlConnector::from_env("SIGNET_COLD_SQL_URL")?;
119    /// ```
120    pub fn from_env(env_var: &'static str) -> Result<Self, SqlConnectorError> {
121        let url = std::env::var(env_var).map_err(|_| SqlConnectorError::MissingEnvVar(env_var))?;
122        Ok(Self::new(url))
123    }
124}
125
126#[cfg(any(feature = "sqlite", feature = "postgres"))]
127impl ColdConnect for SqlConnector {
128    type Cold = SqlColdBackend;
129    type Error = SqlColdError;
130
131    fn connect(&self) -> impl std::future::Future<Output = Result<Self::Cold, Self::Error>> + Send {
132        let url = self.url.clone();
133        let pool_opts = self.pool_opts.clone();
134        async move { SqlColdBackend::connect_with(&url, pool_opts).await }
135    }
136}