use std::any::Any;
use std::fmt;
use crate::OutgoingDestination;
#[cfg(any(feature = "json", feature = "cbor", feature = "msgpack"))]
use crate::codec::{Codec, DefaultCodec};
use crate::runtime::{HeadersUnset, PublishBuilder, PublishSink, Shutdown};
use crate::runtime::{MessageBody, UnnamedCodec, message_of};
use crate::{Lend, OutgoingMessage, RawMessage};
use super::assertions::{PublishedAssertions, SubscriberAssertions};
use super::broker::{LivePublish, TestableBroker};
use super::coordinator::Coordinator;
use super::harness::{BrokerEntry, Mode, TestError};
pub struct BrokerHandle<'a> {
pub(super) harness: Harness<'a>,
pub(super) scope_id: usize,
pub(super) transport: Transport<'a>,
pub(super) label: String,
}
#[derive(Clone, Copy)]
pub(super) struct Harness<'a> {
pub(super) coordinator: &'a Coordinator,
pub(super) shutdown: &'a Shutdown,
pub(super) mode: &'a Mode,
pub(super) brokers: &'a [BrokerEntry],
}
#[derive(Clone, Copy)]
pub(super) enum Transport<'a> {
InProcess(&'a dyn TestableBroker),
Live {
connected: &'a (dyn Any + Send + Sync),
publish: Option<LivePublish>,
},
}
impl fmt::Debug for BrokerHandle<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BrokerHandle")
.field("broker", &self.label)
.finish_non_exhaustive()
}
}
pub struct InjectSink<'a>(pub(super) Target<'a>);
pub(super) enum Target<'a> {
Broker {
harness: Harness<'a>,
scope_id: usize,
transport: Transport<'a>,
label: String,
},
Ambiguous,
}
impl fmt::Debug for InjectSink<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InjectSink").finish_non_exhaustive()
}
}
impl PublishSink for InjectSink<'_> {
type Payload = Lend;
type Error = TestError;
type Options = ();
async fn send(
&mut self,
msg: OutgoingMessage<'_, &[u8]>,
_options: Option<&Self::Options>,
) -> Result<(), Self::Error> {
let Target::Broker {
harness,
scope_id,
transport,
label,
} = &self.0
else {
return Err(TestError::Ambiguous);
};
if harness.shutdown.is_cancelled() {
return Err(TestError::ShutDown);
}
match transport {
Transport::InProcess(testable) => testable.inject(msg),
Transport::Live { connected, publish } => {
let publish = publish.ok_or_else(|| TestError::NoTransport(label.clone()))?;
let recorded = RawMessage::new(msg.name().to_owned(), msg.payload().to_vec())
.with_headers(msg.headers().clone());
publish(*connected, msg)
.await
.map_err(|err| TestError::Publish {
broker: label.clone(),
source: err,
})?;
harness.coordinator.record_published(*scope_id, recorded);
}
}
harness
.mode
.settle(harness.coordinator, harness.brokers)
.await
}
}
impl<'a> BrokerHandle<'a> {
pub fn message<'v, T>(
&self,
value: &'v T,
) -> PublishBuilder<InjectSink<'a>, MessageBody<'v, T>, UnnamedCodec, HeadersUnset, T::Form>
where
T: OutgoingDestination,
{
message_of(self.sink(), value, UnnamedCodec::new())
}
pub(super) fn sink(&self) -> InjectSink<'a> {
InjectSink(Target::Broker {
harness: self.harness,
scope_id: self.scope_id,
transport: self.transport,
label: self.label.clone(),
})
}
}
impl BrokerHandle<'_> {
#[cfg(any(feature = "json", feature = "cbor", feature = "msgpack"))]
pub async fn publish<T: serde::Serialize + Sync>(
&self,
name: &str,
value: &T,
) -> Result<(), TestError> {
let bytes = DefaultCodec::default()
.encode(value)
.map_err(|err| TestError::Encode(err.to_string()))?;
self.sink()
.send(OutgoingMessage::new(name, &bytes), None)
.await
}
#[cfg(any(feature = "json", feature = "cbor", feature = "msgpack"))]
pub async fn publish_with_headers<T, H>(
&self,
name: &str,
value: &T,
headers: &H,
) -> Result<(), TestError>
where
T: serde::Serialize + Sync,
H: serde::Serialize + Sync,
{
let bytes = DefaultCodec::default()
.encode(value)
.map_err(|err| TestError::Encode(err.to_string()))?;
let msg = OutgoingMessage::new(name, &bytes)
.with_typed_headers(headers)
.map_err(|err| TestError::Encode(err.to_string()))?;
self.sink().send(msg, None).await
}
#[must_use]
pub fn subscriber(&self, name: &str) -> SubscriberAssertions<'_> {
SubscriberAssertions::new(self.harness.coordinator, self.scope_id, name.to_owned())
}
#[must_use]
pub fn published<T>(&self, name: &str) -> PublishedAssertions<T> {
let coordinator = self.harness.coordinator;
let messages = match self.transport {
Transport::InProcess(testable) => testable.published(name),
Transport::Live { .. } => coordinator.published(self.scope_id, name),
};
PublishedAssertions::new(name.to_owned(), messages, coordinator.reply_published(name))
}
}
#[cfg(test)]
mod tests {
use super::{InjectSink, Target};
#[test]
fn the_injection_sink_names_itself_without_its_broker() {
let sink = InjectSink(Target::Ambiguous);
assert_eq!(format!("{sink:?}"), "InjectSink { .. }");
}
}