#[cfg(feature = "testing")]
use std::any::{Any, TypeId};
use std::{
any::type_name,
error::Error as StdError,
future::Future,
pin::Pin,
sync::{Arc, Mutex},
};
use crate::{Broker, ConnectedBroker};
pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
pub(crate) type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[cfg(feature = "testing")]
pub(crate) type InProcessConnect =
fn(Box<dyn Any + Send>) -> BoxFuture<'static, Result<Box<dyn Any + Send>, BoxError>>;
#[doc(hidden)]
pub type ConnectedSlot<B> = Arc<Mutex<Option<Arc<<B as Broker>::Connected>>>>;
pub(crate) trait BrokerLifecycle: Send + Sync {
fn connect(
self: Box<Self>,
) -> BoxFuture<'static, Result<Box<dyn ConnectedLifecycle>, BoxError>>;
#[cfg(feature = "testing")]
fn broker_type(&self) -> TypeId;
#[cfg(feature = "testing")]
fn broker_name(&self) -> &'static str;
#[cfg(feature = "testing")]
fn connect_in_process(
self: Box<Self>,
transition: InProcessConnect,
) -> BoxFuture<'static, Result<Box<dyn ConnectedLifecycle>, BoxError>>;
}
pub(crate) trait ConnectedLifecycle: Send + Sync {
fn shutdown(self: Box<Self>) -> BoxFuture<'static, Result<(), BoxError>>;
fn name(&self) -> &'static str;
#[cfg(feature = "testing")]
fn as_any(&self) -> &(dyn Any + Send + Sync)
where
Self: 'static;
#[cfg(feature = "testing")]
fn broker_type(&self) -> TypeId;
}
pub(crate) struct BrokerCell<B: Broker> {
pub(crate) broker: B,
pub(crate) slot: ConnectedSlot<B>,
}
impl<B: Broker + 'static> BrokerLifecycle for BrokerCell<B> {
fn connect(
self: Box<Self>,
) -> BoxFuture<'static, Result<Box<dyn ConnectedLifecycle>, BoxError>> {
Box::pin(async move {
let connected = self
.broker
.connect()
.await
.map_err(|e| Box::new(e) as BoxError)?;
Ok(publish_connected::<B>(connected, self.slot))
})
}
#[cfg(feature = "testing")]
fn broker_type(&self) -> TypeId {
TypeId::of::<B>()
}
#[cfg(feature = "testing")]
fn broker_name(&self) -> &'static str {
type_name::<B>()
}
#[cfg(feature = "testing")]
fn connect_in_process(
self: Box<Self>,
transition: InProcessConnect,
) -> BoxFuture<'static, Result<Box<dyn ConnectedLifecycle>, BoxError>> {
let Self { broker, slot } = *self;
let connecting = transition(Box::new(broker));
Box::pin(async move {
let connected = connecting.await?.downcast::<B::Connected>().map_err(|_| {
Box::from(format!(
"the in-process transition registered for {} produced another connected form",
type_name::<B>(),
)) as BoxError
})?;
Ok(publish_connected::<B>(*connected, slot))
})
}
}
fn publish_connected<B: Broker + 'static>(
connected: B::Connected,
slot: ConnectedSlot<B>,
) -> Box<dyn ConnectedLifecycle> {
let connected = Arc::new(connected);
*slot.lock().expect("connected slot mutex poisoned") = Some(Arc::clone(&connected));
Box::new(ConnectedCell::<B> { connected, slot })
}
struct ConnectedCell<B: Broker> {
connected: Arc<B::Connected>,
slot: ConnectedSlot<B>,
}
impl<B: Broker + 'static> ConnectedLifecycle for ConnectedCell<B> {
fn name(&self) -> &'static str {
type_name::<B>()
}
#[cfg(feature = "testing")]
fn as_any(&self) -> &(dyn Any + Send + Sync)
where
Self: 'static,
{
self.connected.as_ref()
}
#[cfg(feature = "testing")]
fn broker_type(&self) -> TypeId {
TypeId::of::<B>()
}
fn shutdown(self: Box<Self>) -> BoxFuture<'static, Result<(), BoxError>> {
Box::pin(async move {
self.slot
.lock()
.expect("connected slot mutex poisoned")
.take();
let connected = Arc::try_unwrap(self.connected).map_err(|_| {
Box::from(format!(
"connected broker {} still aliased at shutdown",
type_name::<B>(),
)) as BoxError
})?;
connected
.shutdown()
.await
.map(|_closed| ())
.map_err(|e| Box::new(e) as BoxError)
})
}
}