use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use crate::errors::ModuleError;
pub trait ErrorFormatter: Send + Sync {
fn format(&self, error: &ModuleError, context: Option<&dyn std::any::Any>)
-> serde_json::Value;
}
static FORMATTER_REGISTRY: OnceLock<RwLock<HashMap<String, Arc<dyn ErrorFormatter>>>> =
OnceLock::new();
fn global_formatters() -> &'static RwLock<HashMap<String, Arc<dyn ErrorFormatter>>> {
FORMATTER_REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
}
pub struct ErrorFormatterRegistry;
impl ErrorFormatterRegistry {
pub fn register(
adapter_name: &str,
formatter: Box<dyn ErrorFormatter>,
) -> Result<(), ModuleError> {
let mut map = global_formatters().write();
if map.contains_key(adapter_name) {
return Err(ModuleError::error_formatter_duplicate(adapter_name));
}
map.insert(adapter_name.to_string(), Arc::from(formatter));
Ok(())
}
#[must_use]
pub fn get(adapter_name: &str) -> Option<Arc<dyn ErrorFormatter>> {
global_formatters().read().get(adapter_name).cloned()
}
#[must_use]
pub fn format(
adapter_name: &str,
error: &ModuleError,
context: Option<&dyn std::any::Any>,
) -> serde_json::Value {
let map = global_formatters().read();
match map.get(adapter_name) {
Some(formatter) => formatter.format(error, context),
None => error.to_dict(),
}
}
#[must_use]
pub fn is_registered(adapter_name: &str) -> bool {
global_formatters().read().contains_key(adapter_name)
}
#[must_use]
pub fn registered_adapters() -> Vec<String> {
global_formatters().read().keys().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct PrefixFormatter {
prefix: String,
}
impl ErrorFormatter for PrefixFormatter {
fn format(
&self,
error: &ModuleError,
_context: Option<&dyn std::any::Any>,
) -> serde_json::Value {
serde_json::json!({
"adapter_prefix": self.prefix,
"message": error.message,
})
}
}
#[test]
fn test_error_formatter_registry_format_fallback() {
let error = ModuleError::new(crate::errors::ErrorCode::GeneralInternalError, "oops");
let result = ErrorFormatterRegistry::format("nonexistent_adapter_xyz", &error, None);
assert_eq!(result["message"], serde_json::json!("oops"));
}
#[test]
fn test_error_formatter_registry_register_and_format() {
let adapter = "test_adapter_unique_abc123";
if !ErrorFormatterRegistry::is_registered(adapter) {
let formatter = Box::new(PrefixFormatter {
prefix: "TEST".to_string(),
});
ErrorFormatterRegistry::register(adapter, formatter).unwrap();
}
let error = ModuleError::new(crate::errors::ErrorCode::GeneralInternalError, "fail");
let result = ErrorFormatterRegistry::format(adapter, &error, None);
assert_eq!(result["adapter_prefix"], serde_json::json!("TEST"));
assert_eq!(result["message"], serde_json::json!("fail"));
}
#[test]
fn test_error_formatter_registry_duplicate_returns_error() {
let adapter = "test_adapter_dup_xyz987";
let formatter1 = Box::new(PrefixFormatter {
prefix: "A".to_string(),
});
let formatter2 = Box::new(PrefixFormatter {
prefix: "B".to_string(),
});
if !ErrorFormatterRegistry::is_registered(adapter) {
ErrorFormatterRegistry::register(adapter, formatter1).unwrap();
}
let result = ErrorFormatterRegistry::register(adapter, formatter2);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, crate::errors::ErrorCode::ErrorFormatterDuplicate);
}
}