Skip to main content

pharos_runtime_database/
backend_registry.rs

1#![forbid(unsafe_code)]
2
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use crate::backend::{BackendError, BackendKind, PrimaryBackend};
7use crate::operations::{DatabaseError, DatabaseOperations, valid_identifier};
8
9#[derive(Default)]
10/// Immutable registry of primary backends available to the runtime.
11///
12/// # Fields
13///
14/// - This type stores module-owned state for [`crate::backend_registry::BackendRegistry`].
15///
16/// # Examples
17///
18/// ```rust,ignore
19/// let _ = std::mem::size_of::<pharos_runtime_integrations::backend_registry::BackendRegistry>();
20/// ```
21///
22/// # Related
23///
24/// - [`crate::backend_registry`] for constructors, helpers, and neighboring runtime types.
25pub struct BackendRegistry {
26    backends: BTreeMap<&'static str, Arc<dyn PrimaryBackend>>,
27    operations: BTreeMap<&'static str, Arc<dyn DatabaseOperations>>,
28}
29
30impl BackendRegistry {
31    /// Creates one empty backend registry.
32    ///
33    /// # Returns
34    ///
35    /// Returns the computed value produced by [`crate::backend_registry::new`].
36    ///
37    /// # Examples
38    ///
39    /// ```rust,ignore
40    /// let _result = pharos_runtime_integrations::backend_registry::new();
41    /// ```
42    ///
43    /// # Related
44    ///
45    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Registers one primary backend under its stable backend name.
51    ///
52    /// # Parameters
53    ///
54    /// - `backend`: Input consumed by [`crate::backend_registry::register`].
55    ///
56    /// # Returns
57    ///
58    /// Returns the successful value produced by [`crate::backend_registry::register`] or an error when the operation fails.
59    ///
60    /// # Errors
61    ///
62    /// Returns an error value when [`crate::backend_registry::register`] cannot complete successfully.
63    ///
64    /// # Examples
65    ///
66    /// ```rust,ignore
67    /// let _result = pharos_runtime_integrations::backend_registry::register(todo!());
68    /// ```
69    ///
70    /// # Related
71    ///
72    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
73    pub fn register(&mut self, backend: Arc<dyn PrimaryBackend>) -> Result<(), BackendError> {
74        let name = backend.name();
75        if self.backends.contains_key(name) {
76            return Err(BackendError::DuplicateBackend(name.to_owned()));
77        }
78        self.backends.insert(name, backend);
79        Ok(())
80    }
81
82    /// Registers the executable operations for one backend in this same registry.
83    pub fn register_operations(
84        &mut self,
85        name: &'static str,
86        adapter: Arc<dyn DatabaseOperations>,
87    ) -> Result<(), DatabaseError> {
88        if !valid_identifier(name)
89            || adapter.backend_name() != name
90            || self.operations.contains_key(name)
91        {
92            return Err(DatabaseError::InvalidContract);
93        }
94        // The required operation trait is the execution contract. Metadata
95        // cannot restrict future adapters to the shipped BackendKind enum.
96        if let Some(backend) = self.backends.get(name) {
97            let capabilities = backend.capabilities();
98            if !capabilities.readiness
99                || !capabilities.migrations
100                || !capabilities.transactions
101                || !capabilities.export
102                || !capabilities.import
103            {
104                return Err(DatabaseError::InvalidContract);
105            }
106        }
107        self.operations.insert(name, adapter);
108        Ok(())
109    }
110
111    /// Resolves an executable adapter; metadata registration alone cannot satisfy it.
112    /// Invalid names are input errors; a valid but unregistered name is unavailable.
113    pub fn resolve_operations(
114        &self,
115        name: &str,
116    ) -> Result<Arc<dyn DatabaseOperations>, DatabaseError> {
117        if !valid_identifier(name) {
118            return Err(DatabaseError::InvalidInput);
119        }
120        self.operations
121            .get(name)
122            .cloned()
123            .ok_or(DatabaseError::Unavailable)
124    }
125
126    /// Resolves one backend by its stable config name.
127    ///
128    /// # Parameters
129    ///
130    /// - `name`: Input consumed by [`crate::backend_registry::resolve`].
131    ///
132    /// # Returns
133    ///
134    /// Returns the successful value produced by [`crate::backend_registry::resolve`] or an error when the operation fails.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error value when [`crate::backend_registry::resolve`] cannot complete successfully.
139    ///
140    /// # Examples
141    ///
142    /// ```rust,ignore
143    /// let _result = pharos_runtime_integrations::backend_registry::resolve("name");
144    /// ```
145    ///
146    /// # Related
147    ///
148    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
149    pub fn resolve(&self, name: &str) -> Result<Arc<dyn PrimaryBackend>, BackendError> {
150        self.backends
151            .get(name)
152            .cloned()
153            .ok_or_else(|| BackendError::UnknownBackend(name.to_owned()))
154    }
155
156    /// Resolves one backend by typed backend kind.
157    ///
158    /// # Parameters
159    ///
160    /// - `kind`: Input consumed by [`crate::backend_registry::resolve_kind`].
161    ///
162    /// # Returns
163    ///
164    /// Returns the successful value produced by [`crate::backend_registry::resolve_kind`] or an error when the operation fails.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error value when [`crate::backend_registry::resolve_kind`] cannot complete successfully.
169    ///
170    /// # Examples
171    ///
172    /// ```rust,ignore
173    /// let _result = pharos_runtime_integrations::backend_registry::resolve_kind(todo!());
174    /// ```
175    ///
176    /// # Related
177    ///
178    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
179    pub fn resolve_kind(&self, kind: BackendKind) -> Result<Arc<dyn PrimaryBackend>, BackendError> {
180        self.resolve(kind.as_str())
181    }
182
183    /// Returns whether the registry contains the named backend.
184    ///
185    /// # Parameters
186    ///
187    /// - `name`: Input consumed by [`crate::backend_registry::contains`].
188    ///
189    /// # Returns
190    ///
191    /// Returns `true` when the condition checked by [`crate::backend_registry::contains`] holds and `false` otherwise.
192    ///
193    /// # Examples
194    ///
195    /// ```rust,ignore
196    /// let _result = pharos_runtime_integrations::backend_registry::contains("name");
197    /// ```
198    ///
199    /// # Related
200    ///
201    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
202    pub fn contains(&self, name: &str) -> bool {
203        self.backends.contains_key(name)
204    }
205
206    /// Returns the number of registered backends.
207    ///
208    /// # Returns
209    ///
210    /// Returns the computed value produced by [`crate::backend_registry::len`].
211    ///
212    /// # Examples
213    ///
214    /// ```rust,ignore
215    /// let _result = pharos_runtime_integrations::backend_registry::len();
216    /// ```
217    ///
218    /// # Related
219    ///
220    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
221    pub fn len(&self) -> usize {
222        self.backends.len()
223    }
224
225    /// Returns whether no backends are registered.
226    ///
227    /// # Returns
228    ///
229    /// Returns `true` when the condition checked by [`crate::backend_registry::is_empty`] holds and `false` otherwise.
230    ///
231    /// # Examples
232    ///
233    /// ```rust,ignore
234    /// let _result = pharos_runtime_integrations::backend_registry::is_empty();
235    /// ```
236    ///
237    /// # Related
238    ///
239    /// - [`crate::backend_registry`] for adjacent helpers that share the same runtime surface.
240    pub fn is_empty(&self) -> bool {
241        self.backends.is_empty()
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use std::sync::Arc;
248
249    use super::BackendRegistry;
250    use crate::backend::{BackendCapabilities, BackendError, BackendKind, PrimaryBackend};
251
252    /// Keeps unavailable configuration distinct from malformed user input.
253    #[test]
254    fn executable_resolution_preserves_error_category() {
255        let registry = BackendRegistry::new();
256        assert!(matches!(
257            registry.resolve_operations("missing_adapter"),
258            Err(crate::operations::DatabaseError::Unavailable)
259        ));
260        assert!(matches!(
261            registry.resolve_operations("../adapter"),
262            Err(crate::operations::DatabaseError::InvalidInput)
263        ));
264    }
265
266    struct StubBackend {
267        kind: BackendKind,
268        capabilities: BackendCapabilities,
269    }
270
271    impl PrimaryBackend for StubBackend {
272        /// Performs kind.
273        fn kind(&self) -> BackendKind {
274            self.kind
275        }
276
277        /// Performs capabilities.
278        fn capabilities(&self) -> BackendCapabilities {
279            self.capabilities
280        }
281    }
282
283    #[test]
284    /// Performs registers and resolves backend by name.
285    fn registers_and_resolves_backend_by_name() {
286        let mut registry = BackendRegistry::new();
287        let backend = Arc::new(StubBackend {
288            kind: BackendKind::Sqlite,
289            capabilities: BackendCapabilities {
290                readiness: true,
291                ..BackendCapabilities::default()
292            },
293        });
294        registry
295            .register(backend.clone())
296            .expect("register backend");
297
298        let resolved = registry.resolve("sqlite").expect("resolve backend");
299        assert_eq!(resolved.kind(), BackendKind::Sqlite);
300        assert!(resolved.capabilities().readiness);
301    }
302
303    #[test]
304    /// Performs rejects duplicate backend registration.
305    fn rejects_duplicate_backend_registration() {
306        let mut registry = BackendRegistry::new();
307        registry
308            .register(Arc::new(StubBackend {
309                kind: BackendKind::Sqlite,
310                capabilities: BackendCapabilities::default(),
311            }))
312            .expect("first register");
313
314        let duplicate = registry.register(Arc::new(StubBackend {
315            kind: BackendKind::Sqlite,
316            capabilities: BackendCapabilities::default(),
317        }));
318
319        assert_eq!(
320            duplicate,
321            Err(BackendError::DuplicateBackend("sqlite".to_owned()))
322        );
323    }
324
325    #[test]
326    /// Performs rejects unknown backend name.
327    fn rejects_unknown_backend_name() {
328        let registry = BackendRegistry::new();
329        let result = registry.resolve("oracle");
330        assert!(matches!(
331            result,
332            Err(BackendError::UnknownBackend(name)) if name == "oracle"
333        ));
334    }
335
336    #[test]
337    /// Performs resolves registered MySQL backend.
338    fn resolves_registered_mysql_backend() {
339        let mut registry = BackendRegistry::new();
340        registry
341            .register(Arc::new(StubBackend {
342                kind: BackendKind::Mysql,
343                capabilities: BackendCapabilities {
344                    transactions: true,
345                    ..BackendCapabilities::default()
346                },
347            }))
348            .expect("register mysql backend");
349
350        let resolved = registry.resolve("mysql").expect("resolve mysql backend");
351        assert_eq!(resolved.kind(), BackendKind::Mysql);
352        assert!(resolved.capabilities().transactions);
353    }
354
355    #[test]
356    /// Performs resolves registered MongoDB backend.
357    fn resolves_registered_mongodb_backend() {
358        let mut registry = BackendRegistry::new();
359        registry
360            .register(Arc::new(StubBackend {
361                kind: BackendKind::Mongodb,
362                capabilities: BackendCapabilities {
363                    export: true,
364                    import: true,
365                    ..BackendCapabilities::default()
366                },
367            }))
368            .expect("register mongodb backend");
369
370        let resolved = registry
371            .resolve("mongodb")
372            .expect("resolve mongodb backend");
373        assert_eq!(resolved.kind(), BackendKind::Mongodb);
374        assert!(resolved.capabilities().export);
375        assert!(resolved.capabilities().import);
376    }
377
378    #[test]
379    /// Performs resolves backend by kind.
380    fn resolves_backend_by_kind() {
381        let mut registry = BackendRegistry::new();
382        registry
383            .register(Arc::new(StubBackend {
384                kind: BackendKind::Postgresql,
385                capabilities: BackendCapabilities {
386                    transactions: true,
387                    ..BackendCapabilities::default()
388                },
389            }))
390            .expect("register backend");
391
392        let resolved = registry
393            .resolve_kind(BackendKind::Postgresql)
394            .expect("resolve by kind");
395        assert_eq!(resolved.kind(), BackendKind::Postgresql);
396        assert!(resolved.capabilities().transactions);
397    }
398}