pub mod error;
pub mod host_fn;
pub use error::EffectError;
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
pub type EffectResult<T> = Result<T, EffectError>;
include!(concat!(env!("OUT_DIR"), "/effect_traits.rs"));
#[async_trait]
pub trait EffectHandler: Send + Sync {
fn namespace(&self) -> &str;
async fn handle(&self, effect_type: &str, payload: &[u8]) -> EffectResult<Vec<u8>>;
}
pub struct EffectRegistry {
handlers: HashMap<String, Arc<dyn EffectHandler>>,
}
impl EffectRegistry {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
pub fn register(&mut self, handler: Arc<dyn EffectHandler>) {
let namespace = handler.namespace().to_string();
if self.handlers.contains_key(&namespace) {
panic!(
"Effect handler already registered for namespace: {}",
namespace
);
}
tracing::info!(namespace = %namespace, "Registered effect handler");
self.handlers.insert(namespace, handler);
}
pub fn register_owned(&mut self, handler: impl EffectHandler + 'static) {
self.register(Arc::new(handler));
}
pub async fn dispatch(&self, effect_type: &str, payload: &[u8]) -> EffectResult<Vec<u8>> {
let namespace = effect_type.split('.').next().ok_or_else(|| {
EffectError::invalid_input("Effect type must contain namespace prefix")
})?;
let handler = self
.handlers
.get(namespace)
.ok_or_else(|| EffectError::not_found(format!("handler/{}", namespace)))?;
tracing::debug!(
effect_type = %effect_type,
namespace = %namespace,
payload_bytes = payload.len(),
"Dispatching effect"
);
handler.handle(effect_type, payload).await
}
pub fn has_handler(&self, namespace: &str) -> bool {
self.handlers.contains_key(namespace)
}
pub fn namespaces(&self) -> Vec<&str> {
self.handlers.keys().map(|s| s.as_str()).collect()
}
}
impl Default for EffectRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestHandler {
ns: String,
}
impl TestHandler {
fn new(ns: &str) -> Self {
Self { ns: ns.to_string() }
}
}
#[async_trait]
impl EffectHandler for TestHandler {
fn namespace(&self) -> &str {
&self.ns
}
async fn handle(&self, _effect_type: &str, payload: &[u8]) -> EffectResult<Vec<u8>> {
Ok(payload.to_vec())
}
}
#[tokio::test]
async fn test_registry_dispatch() {
let mut registry = EffectRegistry::new();
registry.register_owned(TestHandler::new("test"));
let payload = b"hello";
let result = registry.dispatch("test.do_thing", payload).await.unwrap();
assert_eq!(result, payload);
}
#[tokio::test]
async fn test_registry_not_found() {
let registry = EffectRegistry::new();
let result = registry.dispatch("unknown.effect", &[]).await;
assert!(result.is_err());
if let Err(EffectError::NotFound { resource }) = result {
assert!(resource.contains("unknown"));
} else {
panic!("Expected NotFound error");
}
}
#[test]
fn test_registry_namespaces() {
let mut registry = EffectRegistry::new();
registry.register_owned(TestHandler::new("alpha"));
registry.register_owned(TestHandler::new("beta"));
let mut namespaces = registry.namespaces();
namespaces.sort();
assert_eq!(namespaces, vec!["alpha", "beta"]);
}
#[test]
#[should_panic(expected = "already registered")]
fn test_duplicate_registration_panics() {
let mut registry = EffectRegistry::new();
registry.register_owned(TestHandler::new("test"));
registry.register_owned(TestHandler::new("test"));
}
}