use std::collections::HashMap;
use std::sync::Arc;
use crate::backend::StorageBackend;
use crate::types::BackendConfig;
#[derive(Clone)]
pub struct BackendRegistry {
backends: HashMap<String, (Arc<dyn StorageBackend>, BackendConfig)>,
default_type: Option<String>,
}
impl BackendRegistry {
pub fn new() -> Self {
Self {
backends: HashMap::new(),
default_type: None,
}
}
pub fn register(
&mut self,
backend: impl StorageBackend + 'static,
config: BackendConfig,
) -> &mut Self {
let bt = backend.backend_type().to_string();
self.backends.insert(bt, (Arc::new(backend), config));
self
}
pub fn from_discovered(&mut self) -> &mut Self {
for entry in crate::types::STORAGE_BACKENDS {
let backend = (entry.constructor_fn)();
let config = (entry.config_fn)();
self.backends.insert(
entry.backend_type.to_string(),
(Arc::from(backend), config),
);
}
self
}
pub fn with_default(&mut self, backend_type: &str) -> &mut Self {
self.default_type = Some(backend_type.to_string());
self
}
pub fn get(&self, backend_type: &str) -> Option<&Arc<dyn StorageBackend>> {
self.backends.get(backend_type).map(|(b, _)| b)
}
pub fn default_backend(&self) -> Option<&Arc<dyn StorageBackend>> {
self.default_type
.as_ref()
.and_then(|t| self.backends.get(t))
.or_else(|| self.backends.values().next())
.map(|(b, _)| b)
}
pub fn get_config(&self, backend_type: &str) -> Option<&BackendConfig> {
self.backends.get(backend_type).map(|(_, c)| c)
}
pub fn backend_types(&self) -> Vec<String> {
self.backends.keys().cloned().collect()
}
pub fn len(&self) -> usize {
self.backends.len()
}
pub fn is_empty(&self) -> bool {
self.backends.is_empty()
}
pub async fn health_check_all(&self) -> Vec<(String, Result<(), String>)> {
let mut results = Vec::with_capacity(self.backends.len());
for (bt, (backend, _)) in &self.backends {
results.push((bt.clone(), backend.health_check().await));
}
results
}
}
impl Default for BackendRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::FileMeta;
use async_trait::async_trait;
struct TestBackend;
#[async_trait]
impl StorageBackend for TestBackend {
fn backend_type(&self) -> &str { "test" }
async fn write(&self, _name: &str, _data: &[u8]) -> Result<FileMeta, String> {
Err("not implemented".into())
}
async fn read(&self, _path: &str) -> Result<Vec<u8>, String> {
Err("not implemented".into())
}
async fn delete(&self, _path: &str) -> Result<(), String> { Ok(()) }
async fn exists(&self, _path: &str) -> bool { false }
}
#[test]
fn test_register_and_get() {
let mut reg = BackendRegistry::new();
reg.register(TestBackend, BackendConfig {
backend_type: "test".into(),
..Default::default()
});
assert_eq!(reg.len(), 1);
assert!(reg.get("test").is_some());
assert!(reg.get("unknown").is_none());
}
#[test]
fn test_default_backend() {
let mut reg = BackendRegistry::new();
reg.register(TestBackend, BackendConfig {
backend_type: "test".into(),
..Default::default()
});
assert!(reg.default_backend().is_some());
let reg2 = BackendRegistry::new();
assert!(reg2.default_backend().is_none());
}
#[test]
fn test_with_default() {
let mut reg = BackendRegistry::new();
reg.register(TestBackend, BackendConfig {
backend_type: "test".into(),
..Default::default()
});
reg.with_default("test");
assert!(reg.default_backend().is_some());
reg.with_default("nonexistent");
assert!(reg.default_backend().is_some()); }
}