Skip to main content

rskit_database/
registry.rs

1//! Explicit database backend registry.
2
3use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use rskit_errors::{AppError, AppResult, ErrorCode};
7
8use crate::config::DatabaseConfig;
9use crate::database::{DatabaseClient, memory_from_config};
10
11/// Factory for a named database backend.
12#[async_trait::async_trait]
13pub trait DatabaseFactory: Send + Sync {
14    /// Build a database client from database configuration.
15    async fn create(&self, config: &DatabaseConfig) -> AppResult<Arc<dyn DatabaseClient>>;
16}
17
18/// Explicit database backend registry.
19#[derive(Default)]
20pub struct DatabaseRegistry {
21    factories: BTreeMap<String, Arc<dyn DatabaseFactory>>,
22}
23
24impl DatabaseRegistry {
25    /// Create an empty database backend registry.
26    #[must_use]
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Register a backend factory under `name`.
32    pub fn register(
33        &mut self,
34        name: impl Into<String>,
35        factory: Arc<dyn DatabaseFactory>,
36    ) -> AppResult<()> {
37        let name = name.into().trim().to_owned();
38        if name.is_empty() {
39            return Err(AppError::new(
40                ErrorCode::InvalidInput,
41                "database backend name is required",
42            ));
43        }
44        if self.factories.contains_key(&name) {
45            return Err(AppError::new(
46                ErrorCode::AlreadyExists,
47                format!("database backend '{name}' is already registered"),
48            ));
49        }
50        self.factories.insert(name, factory);
51        Ok(())
52    }
53
54    /// Return true when the backend has been explicitly registered.
55    #[must_use]
56    pub fn contains(&self, name: &str) -> bool {
57        self.factories.contains_key(name)
58    }
59
60    /// Number of registered database backends.
61    #[must_use]
62    pub fn len(&self) -> usize {
63        self.factories.len()
64    }
65
66    /// Return true when no backends are registered.
67    #[must_use]
68    pub fn is_empty(&self) -> bool {
69        self.factories.is_empty()
70    }
71
72    /// Build the backend selected by [`DatabaseConfig::backend`].
73    pub async fn build(&self, config: &DatabaseConfig) -> AppResult<Arc<dyn DatabaseClient>> {
74        let backend = config.backend.trim();
75        if backend.is_empty() {
76            return Err(AppError::new(
77                ErrorCode::InvalidInput,
78                "database backend name is required",
79            ));
80        }
81        self.factories
82            .get(backend)
83            .ok_or_else(|| {
84                AppError::new(
85                    ErrorCode::NotFound,
86                    format!("database backend '{backend}' is not registered"),
87                )
88            })?
89            .create(config)
90            .await
91    }
92}
93
94struct MemoryFactory;
95
96#[async_trait::async_trait]
97impl DatabaseFactory for MemoryFactory {
98    async fn create(&self, config: &DatabaseConfig) -> AppResult<Arc<dyn DatabaseClient>> {
99        Ok(Arc::new(memory_from_config(config)))
100    }
101}
102
103/// Explicitly register the in-memory backend.
104pub fn register_memory(registry: &mut DatabaseRegistry) -> AppResult<()> {
105    registry.register("memory", Arc::new(MemoryFactory))
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[tokio::test]
113    async fn build_rejects_blank_backend() {
114        let registry = DatabaseRegistry::new();
115        let config = DatabaseConfig {
116            backend: "  ".to_owned(),
117            ..DatabaseConfig::default()
118        };
119
120        let err = registry.build(&config).await.err().unwrap();
121
122        assert_eq!(err.code(), ErrorCode::InvalidInput);
123    }
124
125    #[tokio::test]
126    async fn build_normalizes_backend_before_lookup() {
127        let mut registry = DatabaseRegistry::new();
128        register_memory(&mut registry).unwrap();
129        let config = DatabaseConfig {
130            backend: " memory ".to_owned(),
131            ..DatabaseConfig::default()
132        };
133
134        let database = registry.build(&config).await.unwrap();
135
136        assert!(Arc::strong_count(&database) >= 1);
137    }
138}