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;