use async_trait::async_trait;
use serde_json::Value as JsonValue;
use crate::app::AppState;
#[async_trait]
pub trait ExportReloadHandler: Send + Sync {
fn addon_id(&self) -> &str;
fn display_name(&self) -> &str;
async fn export_and_reload(&self, app_state: &AppState) -> Result<JsonValue, String>;
}
#[derive(Default)]
pub struct ExportReloadRegistry {
handlers: Vec<Box<dyn ExportReloadHandler>>,
}
impl ExportReloadRegistry {
pub fn register(&mut self, handler: Box<dyn ExportReloadHandler>) {
self.handlers.push(handler);
}
pub fn list(&self) -> Vec<(String, String)> {
self.handlers
.iter()
.map(|h| (h.addon_id().to_string(), h.display_name().to_string()))
.collect()
}
pub async fn invoke_all(&self, app_state: &AppState) -> Vec<(&str, Result<JsonValue, String>)> {
let mut results = Vec::new();
for handler in &self.handlers {
let id = handler.addon_id();
let result = handler.export_and_reload(app_state).await;
results.push((id, result));
}
results
}
pub async fn invoke_selected(
&self,
selected: &[String],
app_state: &AppState,
) -> Vec<(&str, Result<JsonValue, String>)> {
let mut results = Vec::new();
for handler in &self.handlers {
let id = handler.addon_id();
if selected.iter().any(|s| s == id) {
let result = handler.export_and_reload(app_state).await;
results.push((id, result));
}
}
results
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
use serde_json::json;
struct MockHandler {
id: &'static str,
name: &'static str,
should_fail: bool,
}
#[async_trait]
impl ExportReloadHandler for MockHandler {
fn addon_id(&self) -> &str {
self.id
}
fn display_name(&self) -> &str {
self.name
}
async fn export_and_reload(&self, _app_state: &AppState) -> Result<JsonValue, String> {
if self.should_fail {
Err(format!("{} failed", self.id))
} else {
Ok(json!({ "status": "ok", "addon": self.id }))
}
}
}
#[tokio::test]
async fn test_register_and_list() {
let mut registry = ExportReloadRegistry::default();
assert!(registry.list().is_empty());
registry.register(Box::new(MockHandler {
id: "queue",
name: "Queues",
should_fail: false,
}));
registry.register(Box::new(MockHandler {
id: "ivr",
name: "IVR",
should_fail: false,
}));
let list = registry.list();
assert_eq!(list.len(), 2);
assert!(list.contains(&("queue".to_string(), "Queues".to_string())));
assert!(list.contains(&("ivr".to_string(), "IVR".to_string())));
}
#[tokio::test]
async fn test_invoke_selected_returns_matching_only() {
let mut registry = ExportReloadRegistry::default();
registry.register(Box::new(MockHandler {
id: "queue",
name: "Queues",
should_fail: false,
}));
registry.register(Box::new(MockHandler {
id: "ivr",
name: "IVR",
should_fail: false,
}));
registry.register(Box::new(MockHandler {
id: "cc",
name: "CC",
should_fail: false,
}));
let ids: Vec<String> = registry.list().iter().map(|(id, _)| id.clone()).collect();
assert_eq!(
ids,
vec!["queue".to_string(), "ivr".to_string(), "cc".to_string()]
);
}
#[tokio::test]
async fn test_empty_registry_returns_empty_lists() {
let registry: ExportReloadRegistry = ExportReloadRegistry::default();
assert!(registry.list().is_empty());
}
#[tokio::test]
async fn test_register_same_id_twice() {
let mut registry = ExportReloadRegistry::default();
registry.register(Box::new(MockHandler {
id: "queue",
name: "Queues",
should_fail: false,
}));
registry.register(Box::new(MockHandler {
id: "queue",
name: "Queues v2",
should_fail: false,
}));
assert_eq!(registry.list().len(), 2);
}
}