#![allow(clippy::type_complexity)]
use rust_dix::ServiceCollection;
use rust_webx_core::error::{Error, Result as LrwfResult};
use rust_webx_core::handler::{IEventHandler, IRequestHandler};
use rust_webx_core::mediator::{IEventRequest, IMediator, IRequest};
use rust_webx_core::mediator::Mediator;
use rust_webx_core::route::scan::HandlerRegistration;
use std::sync::{Arc, Mutex};
struct HelloRequest;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct HelloResponse {
message: String,
}
impl IRequest<HelloResponse> for HelloRequest {}
#[derive(Default)]
struct HelloHandler;
#[async_trait::async_trait]
impl IRequestHandler<HelloRequest, HelloResponse> for HelloHandler {
async fn handle(&mut self, _req: HelloRequest) -> LrwfResult<HelloResponse> {
Ok(HelloResponse {
message: "hello".into(),
})
}
}
fn __factory_hello_handler(
_resolver: &dyn rust_dix::IServiceResolver,
) -> LrwfResult<Box<dyn std::any::Any + Send>> {
Ok(Box::new(HelloHandler) as Box<dyn std::any::Any + Send>)
}
fn __call_hello_handler(
handler: Box<dyn std::any::Any + Send>,
request: Box<dyn std::any::Any + Send>,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = LrwfResult<Box<dyn std::any::Any + Send>>>
+ Send,
>,
> {
Box::pin(async move {
let mut h = *handler
.downcast::<HelloHandler>()
.map_err(|_| Error::Internal("Handler downcast failed".into()))?;
let req = *request
.downcast::<HelloRequest>()
.map_err(|_| Error::Internal("Request downcast failed".into()))?;
let result: HelloResponse = h.handle(req).await?;
Ok(Box::new(result) as Box<dyn std::any::Any + Send>)
})
}
inventory::submit! {
HandlerRegistration {
req_type_id: std::any::TypeId::of::<HelloRequest>(),
req_type_name: "HelloRequest",
factory: __factory_hello_handler,
call: __call_hello_handler,
}
}
#[derive(Default)]
struct FailingHandler;
#[async_trait::async_trait]
impl IRequestHandler<HelloRequest, HelloResponse> for FailingHandler {
async fn handle(&mut self, _req: HelloRequest) -> LrwfResult<HelloResponse> {
Err(Error::Internal("handler failure".into()))
}
}
fn __factory_failing_handler(
_resolver: &dyn rust_dix::IServiceResolver,
) -> LrwfResult<Box<dyn std::any::Any + Send>> {
Ok(Box::new(FailingHandler) as Box<dyn std::any::Any + Send>)
}
fn __call_failing_handler(
handler: Box<dyn std::any::Any + Send>,
request: Box<dyn std::any::Any + Send>,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = LrwfResult<Box<dyn std::any::Any + Send>>>
+ Send,
>,
> {
Box::pin(async move {
let mut h = *handler
.downcast::<FailingHandler>()
.map_err(|_| Error::Internal("Handler downcast failed".into()))?;
let req = *request
.downcast::<HelloRequest>()
.map_err(|_| Error::Internal("Request downcast failed".into()))?;
let result: HelloResponse = h.handle(req).await?;
Ok(Box::new(result) as Box<dyn std::any::Any + Send>)
})
}
inventory::submit! {
HandlerRegistration {
req_type_id: std::any::TypeId::of::<HelloRequest>(),
req_type_name: "HelloRequest",
factory: __factory_failing_handler,
call: __call_failing_handler,
}
}
#[derive(Clone)]
struct TestEvent {
payload: String,
}
impl IEventRequest for TestEvent {}
struct CountingEventHandler {
counter: Arc<Mutex<Vec<String>>>,
}
#[async_trait::async_trait]
impl IEventHandler<TestEvent> for CountingEventHandler {
async fn handle(&self, event: TestEvent) -> LrwfResult<()> {
self.counter.lock().unwrap().push(event.payload);
Ok(())
}
}
struct FailingEventHandler;
#[async_trait::async_trait]
impl IEventHandler<TestEvent> for FailingEventHandler {
async fn handle(&self, _event: TestEvent) -> LrwfResult<()> {
Err(Error::Internal("event handler failure".into()))
}
}
fn build_provider() -> Arc<rust_dix::ServiceProvider> {
ServiceCollection::new().build().unwrap()
}
#[tokio::test]
async fn mediator_send_success_or_failure() {
let mediator = Mediator::new(build_provider());
let result = mediator.send(HelloRequest).await;
match result {
Ok(rsp) => assert_eq!(rsp.message, "hello"),
Err(Error::Internal(msg)) => assert_eq!(msg, "handler failure"),
Err(other) => panic!("Unexpected error variant: {:?}", other),
}
}
struct UnregisteredRequest;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct UnregisteredResponse;
impl IRequest<UnregisteredResponse> for UnregisteredRequest {}
#[tokio::test]
async fn mediator_send_handler_not_registered() {
let mediator = Mediator::new(build_provider());
let result = mediator.send(UnregisteredRequest).await;
assert!(result.is_err());
match result.unwrap_err() {
Error::Di(msg) => assert!(msg.contains("No #[handler] registered")),
other => panic!("Expected Di error, got {:?}", other),
}
}
#[tokio::test]
async fn mediator_publish_single_handler() {
let counter = Arc::new(Mutex::new(Vec::new()));
let counter_clone = Arc::clone(&counter);
let provider = ServiceCollection::new()
.singleton::<dyn IEventHandler<TestEvent>>(move |_| {
Arc::new(CountingEventHandler {
counter: Arc::clone(&counter_clone),
})
})
.build()
.unwrap();
let mediator = Mediator::new(provider);
mediator
.publish(TestEvent {
payload: "event-1".into(),
})
.await
.unwrap();
let events = counter.lock().unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0], "event-1");
}
#[tokio::test]
async fn mediator_publish_multiple_handlers() {
let counter = Arc::new(Mutex::new(Vec::new()));
let c1 = Arc::clone(&counter);
let c2 = Arc::clone(&counter);
let provider = ServiceCollection::new()
.singleton::<dyn IEventHandler<TestEvent>>(move |_| {
Arc::new(CountingEventHandler {
counter: Arc::clone(&c1),
})
})
.singleton::<dyn IEventHandler<TestEvent>>(move |_| {
Arc::new(CountingEventHandler {
counter: Arc::clone(&c2),
})
})
.build()
.unwrap();
let mediator = Mediator::new(provider);
mediator
.publish(TestEvent {
payload: "multi".into(),
})
.await
.unwrap();
let events = counter.lock().unwrap();
assert_eq!(events.len(), 2);
assert_eq!(events[0], "multi");
assert_eq!(events[1], "multi");
}
#[tokio::test]
async fn mediator_publish_handler_returns_error() {
let provider = ServiceCollection::new()
.singleton::<dyn IEventHandler<TestEvent>>(|_| Arc::new(FailingEventHandler))
.build()
.unwrap();
let mediator = Mediator::new(provider);
let result = mediator
.publish(TestEvent {
payload: "will-fail".into(),
})
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn mediator_publish_empty_handler_list() {
let mediator = Mediator::new(build_provider());
let result = mediator
.publish(TestEvent {
payload: "no-handlers".into(),
})
.await;
assert!(result.is_ok());
}
use std::sync::atomic::{AtomicU32, Ordering};
static SCOPED_COUNTER: AtomicU32 = AtomicU32::new(0);
struct ScopedService {
instance_id: u32,
}
struct ScopeProbeRequest;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct ScopeProbeResponse {
within_call: String,
first_id: u32,
}
impl IRequest<ScopeProbeResponse> for ScopeProbeRequest {}
fn __factory_scope_probe_handler(
resolver: &dyn rust_dix::IServiceResolver,
) -> LrwfResult<Box<dyn std::any::Any + Send>> {
let key = std::any::type_name::<ScopedService>();
let a: Arc<ScopedService> = resolver
.get_any(key)
.and_then(|a| a.downcast::<Arc<ScopedService>>().ok())
.map(|d| Arc::clone(&*d))
.ok_or_else(|| Error::Internal("ScopedService not registered".into()))?;
let b: Arc<ScopedService> = resolver
.get_any(key)
.and_then(|a| a.downcast::<Arc<ScopedService>>().ok())
.map(|d| Arc::clone(&*d))
.ok_or_else(|| Error::Internal("ScopedService not registered".into()))?;
let within_call = if std::ptr::eq(Arc::as_ptr(&a), Arc::as_ptr(&b)) {
"same"
} else {
"different"
};
let first_id = a.instance_id;
Ok(Box::new(ScopeProbeResult {
within_call: within_call.to_string(),
first_id,
}) as Box<dyn std::any::Any + Send>)
}
struct ScopeProbeResult {
within_call: String,
first_id: u32,
}
fn __call_scope_probe_handler(
handler: Box<dyn std::any::Any + Send>,
_request: Box<dyn std::any::Any + Send>,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = LrwfResult<Box<dyn std::any::Any + Send>>>
+ Send,
>,
> {
Box::pin(async move {
let h = *handler
.downcast::<ScopeProbeResult>()
.map_err(|_| Error::Internal("ScopeProbeResult downcast failed".into()))?;
let rsp = ScopeProbeResponse {
within_call: h.within_call,
first_id: h.first_id,
};
Ok(Box::new(rsp) as Box<dyn std::any::Any + Send>)
})
}
inventory::submit! {
HandlerRegistration {
req_type_id: std::any::TypeId::of::<ScopeProbeRequest>(),
req_type_name: "ScopeProbeRequest",
factory: __factory_scope_probe_handler,
call: __call_scope_probe_handler,
}
}
#[tokio::test]
async fn mediator_send_uses_per_call_scope_for_scoped_services() {
SCOPED_COUNTER.store(0, Ordering::SeqCst);
let provider = ServiceCollection::new()
.scoped::<ScopedService>(|_| {
let id = SCOPED_COUNTER.fetch_add(1, Ordering::SeqCst) + 1;
Arc::new(ScopedService { instance_id: id })
})
.build()
.unwrap();
let mediator = Mediator::new(provider);
let r1 = mediator.send(ScopeProbeRequest).await.expect("send #1 failed");
let r2 = mediator.send(ScopeProbeRequest).await.expect("send #2 failed");
assert_eq!(r1.within_call, "same", "first send: scope not caching scoped service");
assert_eq!(r2.within_call, "same", "second send: scope not caching scoped service");
assert_eq!(r1.first_id, 1, "first send should observe instance #1");
assert_eq!(r2.first_id, 2, "second send should observe instance #2");
}
use rust_webx_core::pipeline::{BoxedNextFn, IPipelineBehavior};
struct BehaviorProbeRequest;
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
struct BehaviorProbeResponse {
message: String,
source: String,
}
impl IRequest<BehaviorProbeResponse> for BehaviorProbeRequest {}
#[derive(Default)]
struct BehaviorProbeHandler;
#[async_trait::async_trait]
impl IRequestHandler<BehaviorProbeRequest, BehaviorProbeResponse> for BehaviorProbeHandler {
async fn handle(&mut self, _req: BehaviorProbeRequest) -> LrwfResult<BehaviorProbeResponse> {
Ok(BehaviorProbeResponse {
message: "from-handler".into(),
source: "handler".into(),
})
}
}
fn __factory_behavior_probe_handler(
_resolver: &dyn rust_dix::IServiceResolver,
) -> LrwfResult<Box<dyn std::any::Any + Send>> {
Ok(Box::new(BehaviorProbeHandler) as Box<dyn std::any::Any + Send>)
}
fn __call_behavior_probe_handler(
handler: Box<dyn std::any::Any + Send>,
request: Box<dyn std::any::Any + Send>,
) -> std::pin::Pin<
Box<
dyn std::future::Future<Output = LrwfResult<Box<dyn std::any::Any + Send>>>
+ Send,
>,
> {
Box::pin(async move {
let mut h = *handler
.downcast::<BehaviorProbeHandler>()
.map_err(|_| Error::Internal("Handler downcast failed".into()))?;
let req = *request
.downcast::<BehaviorProbeRequest>()
.map_err(|_| Error::Internal("Request downcast failed".into()))?;
let result: BehaviorProbeResponse = h.handle(req).await?;
Ok(Box::new(result) as Box<dyn std::any::Any + Send>)
})
}
inventory::submit! {
HandlerRegistration {
req_type_id: std::any::TypeId::of::<BehaviorProbeRequest>(),
req_type_name: "BehaviorProbeRequest",
factory: __factory_behavior_probe_handler,
call: __call_behavior_probe_handler,
}
}
struct TrackingBehavior {
called: Arc<AtomicU32>,
}
#[async_trait::async_trait]
impl IPipelineBehavior for TrackingBehavior {
async fn handle(
&self,
req: Box<dyn std::any::Any + Send>,
next: BoxedNextFn,
) -> LrwfResult<Box<dyn std::any::Any + Send>> {
self.called.fetch_add(1, Ordering::SeqCst);
next(req).await
}
}
struct ShortCircuitBehavior;
#[async_trait::async_trait]
impl IPipelineBehavior for ShortCircuitBehavior {
async fn handle(
&self,
_req: Box<dyn std::any::Any + Send>,
_next: BoxedNextFn,
) -> LrwfResult<Box<dyn std::any::Any + Send>> {
Ok(Box::new(BehaviorProbeResponse {
message: "short-circuited".into(),
source: "behavior".into(),
}))
}
}
#[tokio::test]
async fn mediator_send_pipeline_behavior_executes_before_handler() {
let behavior_called = Arc::new(AtomicU32::new(0));
let behavior = Arc::new(TrackingBehavior {
called: Arc::clone(&behavior_called),
});
let provider = ServiceCollection::new()
.singleton::<dyn IPipelineBehavior>(move |_| {
Arc::clone(&behavior) as Arc<dyn IPipelineBehavior>
})
.build()
.unwrap();
let mediator = Mediator::new(provider);
let rsp = mediator
.send(BehaviorProbeRequest)
.await
.expect("send failed");
assert_eq!(behavior_called.load(Ordering::SeqCst), 1, "behavior should be called once");
assert_eq!(rsp.source, "handler", "response should come from handler");
assert_eq!(rsp.message, "from-handler");
}
#[tokio::test]
async fn mediator_send_pipeline_behavior_can_short_circuit() {
let provider = ServiceCollection::new()
.singleton::<dyn IPipelineBehavior>(|_| {
Arc::new(ShortCircuitBehavior) as Arc<dyn IPipelineBehavior>
})
.build()
.unwrap();
let mediator = Mediator::new(provider);
let rsp = mediator
.send(BehaviorProbeRequest)
.await
.expect("send failed");
assert_eq!(rsp.source, "behavior", "response should come from behavior (short-circuit)");
assert_eq!(rsp.message, "short-circuited");
}
#[tokio::test]
async fn mediator_send_pipeline_empty_chain_works() {
let provider = build_provider();
let mediator = Mediator::new(provider);
let rsp = mediator
.send(BehaviorProbeRequest)
.await
.expect("send failed");
assert_eq!(rsp.source, "handler");
assert_eq!(rsp.message, "from-handler");
}