use crate::core::platform::container::herald::Herald;
use crate::infrastructure::adapters::herald::{JsonHerald, MarkdownHerald, TableHerald};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Clone)]
pub struct HeraldRegistry {
formatters: Arc<RwLock<HashMap<String, Arc<dyn Herald>>>>,
}
impl HeraldRegistry {
pub fn new() -> Self {
Self {
formatters: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn register(&self, name: impl Into<String>, herald: Arc<dyn Herald>) {
let mut formatters = self.formatters.write().unwrap();
formatters.insert(name.into(), herald);
}
pub fn get(&self, name: &str) -> Option<Arc<dyn Herald>> {
let formatters = self.formatters.read().unwrap();
formatters.get(name).cloned()
}
pub fn list(&self) -> Vec<String> {
let formatters = self.formatters.read().unwrap();
formatters.keys().cloned().collect()
}
pub fn contains(&self, name: &str) -> bool {
let formatters = self.formatters.read().unwrap();
formatters.contains_key(name)
}
pub fn len(&self) -> usize {
let formatters = self.formatters.read().unwrap();
formatters.len()
}
pub fn is_empty(&self) -> bool {
let formatters = self.formatters.read().unwrap();
formatters.is_empty()
}
}
impl Default for HeraldRegistry {
fn default() -> Self {
let registry = Self::new();
registry.register("json", Arc::new(JsonHerald::new()));
registry.register("markdown", Arc::new(MarkdownHerald::new()));
registry.register("table", Arc::new(TableHerald::default()));
registry
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::platform::container::herald::{
BattalionResult, ExecutionMetadata, Herald, HeraldError, PaladinError, PaladinResult,
StreamChunk,
};
struct MockHerald {
name: String,
}
impl MockHerald {
fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}
impl Herald for MockHerald {
fn format_paladin_result(&self, _result: &PaladinResult) -> Result<String, HeraldError> {
Ok(format!("{} formatted", self.name))
}
fn format_battalion_result(
&self,
_result: &BattalionResult,
) -> Result<String, HeraldError> {
Ok(format!("{} battalion formatted", self.name))
}
fn format_stream_chunk(&self, _chunk: &StreamChunk) -> Result<Option<String>, HeraldError> {
Ok(Some(self.name.clone()))
}
fn finalize_stream(&self, _metadata: &ExecutionMetadata) -> Result<String, HeraldError> {
Ok(format!("{} finalized", self.name))
}
fn format_error(&self, _error: &PaladinError) -> String {
format!("{} error", self.name)
}
fn name(&self) -> &str {
&self.name
}
fn mime_type(&self) -> &str {
"text/plain"
}
}
#[test]
fn test_new_registry_is_empty() {
let registry = HeraldRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
}
#[test]
fn test_register_and_get_formatter() {
let registry = HeraldRegistry::new();
let herald = Arc::new(MockHerald::new("test"));
registry.register("test", herald);
assert!(!registry.is_empty());
assert_eq!(registry.len(), 1);
let retrieved = registry.get("test");
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().name(), "test");
}
#[test]
fn test_get_nonexistent_formatter() {
let registry = HeraldRegistry::new();
let result = registry.get("nonexistent");
assert!(result.is_none());
}
#[test]
fn test_register_multiple_formatters() {
let registry = HeraldRegistry::new();
registry.register("json", Arc::new(MockHerald::new("json")));
registry.register("markdown", Arc::new(MockHerald::new("markdown")));
registry.register("table", Arc::new(MockHerald::new("table")));
assert_eq!(registry.len(), 3);
assert!(registry.contains("json"));
assert!(registry.contains("markdown"));
assert!(registry.contains("table"));
}
#[test]
fn test_list_formatters() {
let registry = HeraldRegistry::new();
registry.register("json", Arc::new(MockHerald::new("json")));
registry.register("markdown", Arc::new(MockHerald::new("markdown")));
let mut list = registry.list();
list.sort();
assert_eq!(list.len(), 2);
assert!(list.contains(&"json".to_string()));
assert!(list.contains(&"markdown".to_string()));
}
#[test]
fn test_register_replaces_existing() {
let registry = HeraldRegistry::new();
registry.register("test", Arc::new(MockHerald::new("first")));
registry.register("test", Arc::new(MockHerald::new("second")));
assert_eq!(registry.len(), 1);
let herald = registry.get("test").unwrap();
assert_eq!(herald.name(), "second");
}
#[test]
fn test_contains() {
let registry = HeraldRegistry::new();
assert!(!registry.contains("json"));
registry.register("json", Arc::new(MockHerald::new("json")));
assert!(registry.contains("json"));
assert!(!registry.contains("markdown"));
}
#[test]
fn test_registry_is_cloneable() {
let registry = HeraldRegistry::new();
registry.register("test", Arc::new(MockHerald::new("test")));
let cloned = registry.clone();
assert_eq!(cloned.len(), 1);
assert!(cloned.contains("test"));
}
#[test]
fn test_registry_thread_safety() {
use std::thread;
let registry = Arc::new(HeraldRegistry::new());
let mut handles = vec![];
for i in 0..10 {
let registry_clone = Arc::clone(®istry);
let handle = thread::spawn(move || {
let name = format!("herald_{}", i);
registry_clone.register(name.clone(), Arc::new(MockHerald::new(&name)));
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(registry.len(), 10);
}
#[test]
fn test_default_registry() {
let registry = HeraldRegistry::default();
assert_eq!(registry.len(), 3);
assert!(!registry.is_empty());
assert!(registry.contains("json"));
assert!(registry.contains("markdown"));
assert!(registry.contains("table"));
assert!(registry.get("json").is_some());
assert!(registry.get("markdown").is_some());
assert!(registry.get("table").is_some());
assert_eq!(registry.get("json").unwrap().name(), "json");
assert_eq!(registry.get("markdown").unwrap().name(), "markdown");
assert_eq!(registry.get("table").unwrap().name(), "table");
}
#[test]
fn test_default_registry_can_add_custom_formatters() {
let registry = HeraldRegistry::default();
assert_eq!(registry.len(), 3);
registry.register("custom", Arc::new(MockHerald::new("custom")));
assert_eq!(registry.len(), 4);
assert!(registry.contains("custom"));
assert!(registry.contains("json"));
assert!(registry.contains("markdown"));
assert!(registry.contains("table"));
}
#[test]
fn test_default_registry_can_override_builtin_formatters() {
let registry = HeraldRegistry::default();
let original_json = registry.get("json").unwrap();
assert_eq!(original_json.name(), "json");
registry.register("json", Arc::new(MockHerald::new("custom-json")));
assert_eq!(registry.len(), 3);
let new_json = registry.get("json").unwrap();
assert_eq!(new_json.name(), "custom-json");
assert_eq!(registry.get("markdown").unwrap().name(), "markdown");
assert_eq!(registry.get("table").unwrap().name(), "table");
}
#[test]
fn test_new_vs_default_registry() {
let new_registry = HeraldRegistry::new();
let default_registry = HeraldRegistry::default();
assert!(new_registry.is_empty());
assert_eq!(new_registry.len(), 0);
assert!(!default_registry.is_empty());
assert_eq!(default_registry.len(), 3);
new_registry.register("json", Arc::new(MockHerald::new("json")));
assert_eq!(new_registry.len(), 1);
}
#[test]
fn test_registry_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<HeraldRegistry>();
}
}