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}