Skip to main content

pharos_runtime_database/
backend.rs

1#![forbid(unsafe_code)]
2
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
6/// Stable identity for one primary database backend supported by Pharos.
7///
8/// # Variants
9///
10/// - `Sqlite`: Supported `BackendKind` variant.
11/// - `Mysql`: Supported `BackendKind` variant.
12/// - `Mongodb`: Supported `BackendKind` variant.
13/// - `Postgresql`: Supported `BackendKind` variant.
14///
15/// # Examples
16///
17/// ```rust,ignore
18/// let _ = pharos_runtime_integrations::backend::BackendKind::Sqlite;
19/// ```
20///
21/// # Related
22///
23/// - [`crate::backend`] for constructors, helpers, and neighboring runtime types.
24pub enum BackendKind {
25    Sqlite,
26    Mysql,
27    Mongodb,
28    Postgresql,
29}
30
31impl BackendKind {
32    /// Returns the stable config and registry name for the backend.
33    ///
34    /// # Examples
35    ///
36    /// ```rust,ignore
37    /// let _ = pharos_runtime_integrations::backend::fn;
38    /// ```
39    ///
40    /// # Related
41    ///
42    /// - [`crate::backend`] for the surrounding module surface.
43    pub const fn as_str(self) -> &'static str {
44        match self {
45            Self::Sqlite => "sqlite",
46            Self::Mysql => "mysql",
47            Self::Mongodb => "mongodb",
48            Self::Postgresql => "postgresql",
49        }
50    }
51
52    /// Resolves one backend kind from its stable config or registry name.
53    ///
54    /// # Parameters
55    ///
56    /// - `name`: Input consumed by [`crate::backend::parse`].
57    ///
58    /// # Returns
59    ///
60    /// Returns the successful value produced by [`crate::backend::parse`] or an error when the operation fails.
61    ///
62    /// # Errors
63    ///
64    /// Returns an error value when [`crate::backend::parse`] cannot complete successfully.
65    ///
66    /// # Examples
67    ///
68    /// ```rust,ignore
69    /// let _result = pharos_runtime_integrations::backend::parse("name");
70    /// ```
71    ///
72    /// # Related
73    ///
74    /// - [`crate::backend`] for adjacent helpers that share the same runtime surface.
75    pub fn parse(name: &str) -> Result<Self, BackendError> {
76        match name {
77            "sqlite" => Ok(Self::Sqlite),
78            "mysql" => Ok(Self::Mysql),
79            "mongodb" => Ok(Self::Mongodb),
80            "postgresql" => Ok(Self::Postgresql),
81            other => Err(BackendError::UnknownBackend(other.to_owned())),
82        }
83    }
84}
85
86#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
87/// Typed capability flags used to validate backend-owned operations.
88///
89/// # Fields
90///
91/// - `readiness`: Stored field on `BackendCapabilities`.
92/// - `migrations`: Stored field on `BackendCapabilities`.
93/// - `prepared_execution`: Stored field on `BackendCapabilities`.
94/// - `transactions`: Stored field on `BackendCapabilities`.
95/// - `export`: Stored field on `BackendCapabilities`.
96/// - `import`: Stored field on `BackendCapabilities`.
97/// - `pooling`: Stored field on `BackendCapabilities`.
98///
99/// # Examples
100///
101/// ```rust,ignore
102/// let _ = std::mem::size_of::<pharos_runtime_integrations::backend::BackendCapabilities>();
103/// ```
104///
105/// # Related
106///
107/// - [`crate::backend`] for constructors, helpers, and neighboring runtime types.
108pub struct BackendCapabilities {
109    pub readiness: bool,
110    pub migrations: bool,
111    pub prepared_execution: bool,
112    pub transactions: bool,
113    pub export: bool,
114    pub import: bool,
115    pub pooling: bool,
116}
117
118#[derive(Debug, Clone, Default, Eq, PartialEq)]
119/// Shared backend configuration resolved from runtime config before backend.
120///
121/// # Fields
122///
123/// - `backend`: Stored field on `DbBackendConfig`.
124/// - `sqlite_path`: Stored field on `DbBackendConfig`.
125/// - `host`: Stored field on `DbBackendConfig`.
126/// - `port`: Stored field on `DbBackendConfig`.
127/// - `database`: Stored field on `DbBackendConfig`.
128/// - `user`: Stored field on `DbBackendConfig`.
129/// - `password_env_name`: Stored field on `DbBackendConfig`.
130/// - `tls_mode`: Stored field on `DbBackendConfig`.
131/// - `tls_root_cert`: Stored field on `DbBackendConfig`.
132/// - `tls_cert`: Stored field on `DbBackendConfig`.
133/// - `tls_key`: Stored field on `DbBackendConfig`.
134/// - `socket_path`: Stored field on `DbBackendConfig`.
135/// - `charset`: Stored field on `DbBackendConfig`.
136/// - `options`: Stored field on `DbBackendConfig`.
137///
138/// # Examples
139///
140/// ```rust,ignore
141/// let _ = std::mem::size_of::<pharos_runtime_integrations::backend::DbBackendConfig>();
142/// ```
143///
144/// # Related
145///
146/// - [`crate::backend`] for constructors, helpers, and neighboring runtime types.
147pub struct DbBackendConfig {
148    pub backend: String,
149    pub sqlite_path: Option<PathBuf>,
150    pub host: String,
151    pub port: String,
152    pub database: String,
153    pub user: String,
154    pub password_env_name: String,
155    pub tls_mode: String,
156    pub tls_root_cert: String,
157    pub tls_cert: String,
158    pub tls_key: String,
159    pub socket_path: Option<PathBuf>,
160    pub charset: Option<String>,
161    pub options: Option<String>,
162}
163
164impl DbBackendConfig {
165    /// Keeps the configured adapter and credentials while isolating a generated
166    /// app's storage namespace and optional local database file.
167    pub fn isolated(
168        &self,
169        namespace: &str,
170        data_root: &std::path::Path,
171        app_id: &str,
172    ) -> Result<Self, BackendError> {
173        if !crate::operations::valid_identifier(namespace)
174            || !crate::operations::valid_identifier(app_id)
175            || data_root.as_os_str().is_empty()
176        {
177            return Err(BackendError::InvalidConfig("isolated namespace"));
178        }
179        let mut isolated = self.clone();
180        isolated.database = namespace.to_owned();
181        if isolated.sqlite_path.is_some() {
182            isolated.sqlite_path = Some(data_root.join(format!("{app_id}.sqlite3")));
183        }
184        crate::validate_backend_config(&isolated)?;
185        Ok(isolated)
186    }
187
188    /// Serializes validated connection settings without resolving secret values.
189    /// This keeps preview and operator configuration on one shared contract.
190    pub fn settings(&self) -> Result<Vec<(&'static str, String)>, BackendError> {
191        crate::validate_backend_config(self)?;
192        let mut settings = vec![
193            ("db_backend", self.backend.clone()),
194            ("db_host", self.host.clone()),
195            ("db_port", self.port.clone()),
196            ("db_name", self.database.clone()),
197            ("db_user", self.user.clone()),
198            ("db_password_env", self.password_env_name.clone()),
199            ("db_tls_mode", self.tls_mode.clone()),
200            ("db_tls_root_cert", self.tls_root_cert.clone()),
201            ("db_tls_cert", self.tls_cert.clone()),
202            ("db_tls_key", self.tls_key.clone()),
203        ];
204        if let Some(path) = &self.sqlite_path {
205            settings.push(("db_path", path.to_string_lossy().into_owned()));
206        }
207        if let Some(path) = &self.socket_path {
208            settings.push(("db_socket", path.to_string_lossy().into_owned()));
209        }
210        if let Some(value) = &self.charset {
211            settings.push(("db_charset", value.clone()));
212        }
213        if let Some(value) = &self.options {
214            settings.push(("db_options", value.clone()));
215        }
216        if settings
217            .iter()
218            .any(|(_, value)| value.len() > 2048 || value.contains(['\n', '\r', '\0']))
219        {
220            return Err(BackendError::InvalidConfig("connection setting"));
221        }
222        Ok(settings)
223    }
224
225    /// Resolves the shared connection contract from effective app/runtime values.
226    /// Callers supply their own cached or operator lookup; defaults remain identical.
227    pub fn from_lookup(
228        app_id: &str,
229        mut lookup: impl FnMut(&str) -> Option<String>,
230    ) -> Result<Self, BackendError> {
231        if !crate::operations::valid_identifier(app_id) {
232            return Err(BackendError::InvalidConfig("app_id"));
233        }
234        let backend = lookup("db_backend")
235            .filter(|value| !value.is_empty())
236            .unwrap_or_else(|| "sqlite".to_owned());
237        let sqlite_path = lookup("db_path")
238            .filter(|value| !value.is_empty())
239            .map(PathBuf::from)
240            .or_else(|| {
241                (backend == "sqlite")
242                    .then(|| PathBuf::from(format!("/var/lib/pharos/{app_id}.sqlite3")))
243            });
244        let config = Self {
245            backend,
246            sqlite_path,
247            host: lookup("db_host").unwrap_or_default(),
248            port: lookup("db_port").unwrap_or_default(),
249            database: lookup("db_name").unwrap_or_default(),
250            user: lookup("db_user").unwrap_or_default(),
251            password_env_name: lookup("db_password_env").unwrap_or_default(),
252            tls_mode: lookup("db_tls_mode")
253                .filter(|value| !value.is_empty())
254                .unwrap_or_else(|| "require".to_owned()),
255            tls_root_cert: lookup("db_tls_root_cert").unwrap_or_default(),
256            tls_cert: lookup("db_tls_cert").unwrap_or_default(),
257            tls_key: lookup("db_tls_key").unwrap_or_default(),
258            socket_path: lookup("db_socket")
259                .filter(|value| !value.is_empty())
260                .map(PathBuf::from),
261            charset: lookup("db_charset").filter(|value| !value.is_empty()),
262            options: lookup("db_options").filter(|value| !value.is_empty()),
263        };
264        crate::validate_backend_config(&config)?;
265        Ok(config)
266    }
267
268    /// Accepts a legacy local-path hint only when it names the configured source.
269    /// The hint never changes backend selection or redirects an app to another file.
270    pub fn matches_local_path_hint(&self, hint: &std::path::Path) -> bool {
271        self.sqlite_path.as_deref().is_some_and(|configured| {
272            match (configured.canonicalize(), hint.canonicalize()) {
273                (Ok(configured), Ok(hint)) => configured == hint,
274                _ => false,
275            }
276        })
277    }
278}
279
280#[derive(Debug, Clone, Eq, PartialEq)]
281/// Stable backend error categories returned by shared backend resolution and.
282///
283/// # Variants
284///
285/// - `UnknownBackend`: Supported `BackendError` variant.
286/// - `DuplicateBackend`: Supported `BackendError` variant.
287/// - `UnsupportedCapability`: Supported `BackendError` variant.
288/// - `InvalidConfig`: Supported `BackendError` variant.
289///
290/// # Examples
291///
292/// ```rust,ignore
293/// let _ = pharos_runtime_integrations::backend::BackendError::UnknownBackend;
294/// ```
295///
296/// # Related
297///
298/// - [`crate::backend`] for constructors, helpers, and neighboring runtime types.
299pub enum BackendError {
300    UnknownBackend(String),
301    DuplicateBackend(String),
302    UnsupportedCapability {
303        backend: BackendKind,
304        capability: &'static str,
305    },
306    InvalidConfig(&'static str),
307}
308
309/// One configured primary backend implementation registered with the runtime.
310///
311/// # Related
312///
313/// - [`crate::backend`] for neighboring helpers and related types.
314///
315/// # Examples
316///
317/// ```rust,ignore
318/// let _ = std::mem::size_of::<pharos_runtime_integrations::backend::PrimaryBackend>();
319/// ```
320///
321/// # Related
322///
323/// - [`crate::backend`] for constructors, helpers, and neighboring runtime types.
324pub trait PrimaryBackend: Send + Sync {
325    /// Performs kind.
326    fn kind(&self) -> BackendKind;
327
328    /// Performs name.
329    fn name(&self) -> &'static str {
330        self.kind().as_str()
331    }
332
333    /// Performs capabilities.
334    fn capabilities(&self) -> BackendCapabilities;
335}
336
337#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
338/// Reserved connection ownership marker for later backend retrofits.
339///
340/// # Fields
341///
342/// - This type stores module-owned state for [`crate::backend::BackendConnection`].
343///
344/// # Examples
345///
346/// ```rust,ignore
347/// let _ = std::mem::size_of::<pharos_runtime_integrations::backend::BackendConnection>();
348/// ```
349///
350/// # Related
351///
352/// - [`crate::backend`] for constructors, helpers, and neighboring runtime types.
353pub struct BackendConnection;