use std::any::type_name;
use std::fmt;
use std::num::NonZeroUsize;
use std::sync::Arc;
use serde::Serialize;
use crate::codec::Codec;
use crate::{
BatchSubscriber, Broker, BuildBatchContext, BuildContext, Connected, PublishPolicy, Publisher,
SubscriptionSource,
};
use crate::runtime::batch_publishing::{
BatchPublishingCall, BatchPublishingDef, BatchPublishingHandler,
};
use crate::runtime::dispatch::{Workers, spawn_dispatch_workers};
use crate::runtime::failure::{DispatchFailure, FailurePolicies};
use crate::runtime::inject::FromStartup;
use crate::runtime::input::{DecodeWith, Materialize};
use crate::runtime::lifecycle::BoxError;
use crate::runtime::metadata::{HandlerMetadata, PublishDescription};
use crate::runtime::middleware::BlanketLayer;
use crate::runtime::publish::{
ForReply, NamesDestination, PublishPipeline, PublishTransform, ReplyPublisher, TypedPublisher,
};
use crate::runtime::publishing::{
PublishingCall, PublishingDef, PublishingHandler, warn_ignored_reply_name,
};
use crate::runtime::redelivery::{CopyPathAddress, CopyPathPairing, RetrySetup, open_subscription};
use crate::runtime::retry::RetryOpen;
#[cfg(feature = "testing")]
use crate::testing::coordinator::paired;
use super::SourceMessage;
use super::routes::{
MountRoute, RouteMeta, RouteMetadata, RouteSubscription, impl_destination_declared,
};
use super::sink::RouterSink;
type ReplyUse<Transforms, Cx, Options> =
<Transforms as PublishTransform<ForReply<Cx>, Options>>::Destination;
#[doc(hidden)]
pub struct PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra> {
pub(super) source: Source,
pub(super) def: Def,
pub(super) codec: DecodeCodec,
pub(super) publisher: ReplySource,
pub(super) extra: Extra,
pub(super) meta: HandlerMetadata,
pub(super) policies: FailurePolicies,
pub(super) workers: Workers,
}
#[doc(hidden)]
pub struct RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra> {
pub(super) source: Source,
pub(super) def: Def,
pub(super) codec: DecodeCodec,
pub(super) publisher: ReplySource,
pub(super) extra: Extra,
pub(super) meta: HandlerMetadata,
pub(super) policies: FailurePolicies,
pub(super) workers: Workers,
}
#[doc(hidden)]
pub struct BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra> {
pub(super) source: Source,
pub(super) def: Def,
pub(super) codec: DecodeCodec,
pub(super) publisher: ReplySource,
pub(super) extra: Extra,
pub(super) meta: HandlerMetadata,
pub(super) policies: FailurePolicies,
pub(super) workers: Workers,
pub(super) batch_size: NonZeroUsize,
}
macro_rules! debug_by_metadata {
($($route:ident<$($param:ident),+>),+ $(,)?) => {$(
impl<$($param),+> fmt::Debug for $route<$($param),+> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($route))
.field("meta", &self.meta)
.finish_non_exhaustive()
}
}
impl<$($param),+> RouteMeta for $route<$($param),+> {
fn collect(&self, out: &mut Vec<HandlerMetadata>) {
out.push(self.meta.clone());
}
}
)+};
}
debug_by_metadata!(
PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
);
macro_rules! impl_retry_open {
($($route:ident<$($param:ident),+>),+ $(,)?) => {$(
impl<$($param),+> RetryOpen for $route<$($param),+> {}
)+};
}
impl_retry_open!(
PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
);
macro_rules! impl_route_metadata {
($($route:ident<$($param:ident),+>),+ $(,)?) => {$(
impl<$($param),+> RouteMetadata for $route<$($param),+> {
fn metadata_mut(&mut self) -> &mut HandlerMetadata {
&mut self.meta
}
}
)+};
}
impl_route_metadata!(
PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
);
macro_rules! impl_route_subscription {
($($route:ident<$($param:ident),+> via $def:ident),+ $(,)?) => {$(
impl<B, $($param),+> RouteSubscription<B> for $route<$($param),+>
where
B: Broker,
Source: SubscriptionSource<Connected<B>>,
Def: $def,
{
type Source = Source;
type Copies = <Source as SubscriptionSource<Connected<B>>>::Copies;
type Context = <Def as $def>::Context;
}
)+};
}
impl_route_subscription!(
PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra> via PublishingDef,
RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra> via PublishingDef,
BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra> via BatchPublishingDef,
);
impl_destination_declared!(
[Source, Def, DecodeCodec, ReplySource, Extra]
PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
[Source, Def, DecodeCodec, ReplySource, Extra]
RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
[Source, Def, DecodeCodec, ReplySource, Extra]
BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>,
);
impl<
B,
Source,
Def,
DecodeCodec,
ReplySource,
Extra,
State,
Leaf,
ReplyCodec,
Transforms,
RetryPipeline,
> MountRoute<B, State, RetryPipeline>
for PublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>
where
B: Broker + 'static,
Source: SubscriptionSource<Connected<B>> + Send + 'static,
Source::Copies:
CopyPathPairing<B, Def::Context, RetryPipeline> + CopyPathAddress<Connected<B>, Source>,
RetryPipeline: Clone,
Source::Subscriber: Sync + Send + 'static,
SourceMessage<B, Source>: Send + Sync + 'static,
State: Send + Sync + 'static,
Def: PublishingCall<State> + 'static,
Def::Input: Materialize<DecodeCodec, SourceMessage<B, Source>>,
Def::Injections: FromStartup<B, Source::Subscriber, Extra> + Send + Sync + 'static,
Def::Reply: crate::runtime::publishing::EncodeReply + Send + Sync + 'static,
Def::Context: BuildContext<SourceMessage<B, Source>> + Send + Sync + 'static,
DecodeCodec: Send + Sync + 'static,
Extra: Send + Sync + 'static,
ReplySource: PublishPolicy<Connected<B>, Live = TypedPublisher<Leaf, ReplyCodec, Transforms>>
+ Send
+ 'static,
Leaf: Publisher + 'static,
ReplyCodec: Codec + Send + Sync + 'static,
Transforms: PublishTransform<ForReply<Def::Context>, Leaf::Options> + Send + Sync + 'static,
{
type Context = Def::Context;
fn mount_one<G, PP>(
self,
global: &G,
pipeline: &PP,
retry_pipeline: &RetryPipeline,
sink: &mut RouterSink<B, State>,
setup: RetrySetup<B, Def::Context>,
) where
G: BlanketLayer + Clone + Send + Sync + 'static,
PP: PublishPipeline + Clone + Send + 'static,
{
let Self {
source,
def,
codec,
publisher,
extra,
mut meta,
policies,
workers,
} = self;
let global = global.clone();
let pipeline = pipeline.clone();
let name: Arc<str> = Arc::from(meta.name.as_ref());
meta.describe_reply(|channel| {
PublishDescription::of::<Connected<B>, ReplySource>(
&publisher,
Some(ReplyCodec::CONTENT_TYPE),
<ReplyUse<Transforms, Def::Context, Leaf::Options> as NamesDestination>::NAMES,
channel,
)
});
let setup = setup.resolve::<Source::Copies, _>(retry_pipeline, &mut meta);
sink.push_raw(
Box::new(move |connected, state, scope, shutdown| {
Box::pin(async move {
warn_ignored_reply_name(
&name,
type_name::<Def::Reply>(),
def.reply_name(),
def.ignored_reply_name(),
);
let pairing = publisher.pair(connected.as_ref());
#[cfg(feature = "testing")]
let (publisher, origin) = paired(connected.as_ref(), pairing).await;
#[cfg(not(feature = "testing"))]
let publisher = pairing.await;
let publisher = publisher.map_err(|e| Box::new(e) as BoxError)?;
let (subscriber, delivery) = open_subscription::<B, _, Def::Context>(
source,
connected.as_ref(),
&scope,
&name,
setup,
)
.await?;
let injections =
Def::Injections::resolve(extra, connected.as_ref(), &subscriber)
.await
.map_err(|e| Box::new(e) as BoxError)?;
let handler = global.apply::<SourceMessage<B, Source>, Def::Context, State, _>(
PublishingHandler {
def,
codec,
publisher,
pipeline,
injections,
decode: policies.decode,
#[cfg(feature = "testing")]
origin,
},
);
let failure = DispatchFailure::new(policies, shutdown.clone());
Ok(spawn_dispatch_workers(
subscriber,
Arc::new(handler),
shutdown,
name,
state,
delivery,
failure,
workers,
)?)
})
}),
meta,
);
}
}
impl<B, Source, Def, DecodeCodec, ReplySource, Extra, State, Live, RetryPipeline>
MountRoute<B, State, RetryPipeline>
for RawReplyRoute<Source, Def, DecodeCodec, ReplySource, Extra>
where
B: Broker + 'static,
Source: SubscriptionSource<Connected<B>> + Send + 'static,
Source::Copies:
CopyPathPairing<B, Def::Context, RetryPipeline> + CopyPathAddress<Connected<B>, Source>,
RetryPipeline: Clone,
Source::Subscriber: Sync + Send + 'static,
SourceMessage<B, Source>: Send + Sync + 'static,
State: Send + Sync + 'static,
Def: PublishingCall<State> + 'static,
Def::Input: Materialize<DecodeCodec, SourceMessage<B, Source>>,
Def::Injections: FromStartup<B, Source::Subscriber, Extra> + Send + Sync + 'static,
Def::Reply: crate::runtime::Serialized + Send + Sync + 'static,
Def::Context: BuildContext<SourceMessage<B, Source>> + Send + Sync + 'static,
DecodeCodec: Send + Sync + 'static,
Extra: Send + Sync + 'static,
ReplySource: PublishPolicy<Connected<B>, Live = Live> + Send + 'static,
Live: Publisher + Send + Sync + 'static,
{
type Context = Def::Context;
fn mount_one<G, PP>(
self,
global: &G,
pipeline: &PP,
retry_pipeline: &RetryPipeline,
sink: &mut RouterSink<B, State>,
setup: RetrySetup<B, Def::Context>,
) where
G: BlanketLayer + Clone + Send + Sync + 'static,
PP: PublishPipeline + Clone + Send + 'static,
{
let Self {
source,
def,
codec,
publisher,
extra,
mut meta,
policies,
workers,
} = self;
let global = global.clone();
let pipeline = pipeline.clone();
let name: Arc<str> = Arc::from(meta.name.as_ref());
meta.describe_reply(|channel| {
PublishDescription::of::<Connected<B>, ReplySource>(&publisher, None, false, channel)
});
let setup = setup.resolve::<Source::Copies, _>(retry_pipeline, &mut meta);
sink.push_raw(
Box::new(move |connected, state, scope, shutdown| {
Box::pin(async move {
warn_ignored_reply_name(
&name,
type_name::<Def::Reply>(),
def.reply_name(),
def.ignored_reply_name(),
);
let pairing = publisher.pair(connected.as_ref());
#[cfg(feature = "testing")]
let (publisher, origin) = paired(connected.as_ref(), pairing).await;
#[cfg(not(feature = "testing"))]
let publisher = pairing.await;
let publisher = publisher.map_err(|e| Box::new(e) as BoxError)?;
let (subscriber, delivery) = open_subscription::<B, _, Def::Context>(
source,
connected.as_ref(),
&scope,
&name,
setup,
)
.await?;
let injections =
Def::Injections::resolve(extra, connected.as_ref(), &subscriber)
.await
.map_err(|e| Box::new(e) as BoxError)?;
let handler = global.apply::<SourceMessage<B, Source>, Def::Context, State, _>(
PublishingHandler {
def,
codec,
publisher,
pipeline,
injections,
decode: policies.decode,
#[cfg(feature = "testing")]
origin,
},
);
let failure = DispatchFailure::new(policies, shutdown.clone());
Ok(spawn_dispatch_workers(
subscriber,
Arc::new(handler),
shutdown,
name,
state,
delivery,
failure,
workers,
)?)
})
}),
meta,
);
}
}
impl<B, Source, Def, DecodeCodec, ReplySource, Extra, BatchReply, State, RetryPipeline>
MountRoute<B, State, RetryPipeline>
for BatchPublishingRoute<Source, Def, DecodeCodec, ReplySource, Extra>
where
B: Broker + 'static,
Source: SubscriptionSource<Connected<B>> + Send + 'static,
Source::Copies:
CopyPathPairing<B, Def::Context, RetryPipeline> + CopyPathAddress<Connected<B>, Source>,
RetryPipeline: Clone,
Source::Subscriber: BatchSubscriber + Sync + Send + 'static,
SourceMessage<B, Source>: Send + 'static,
State: Send + Sync + 'static,
Def: BatchPublishingCall<State> + 'static,
Def::Input: DecodeWith<DecodeCodec>,
Def::Injections: FromStartup<B, Source::Subscriber, Extra> + Send + Sync + 'static,
Def::Reply: Serialize + Send + Sync + 'static,
Def::Context: BuildBatchContext<SourceMessage<B, Source>> + Send + Sync + 'static,
DecodeCodec: Send + Sync + 'static,
Extra: Send + Sync + 'static,
ReplySource: PublishPolicy<Connected<B>, Live = BatchReply> + Send + 'static,
BatchReply: ReplyPublisher<Def::Context> + 'static,
{
type Context = Def::Context;
fn mount_one<G, PP>(
self,
_global: &G,
pipeline: &PP,
retry_pipeline: &RetryPipeline,
sink: &mut RouterSink<B, State>,
setup: RetrySetup<B, Def::Context>,
) where
G: BlanketLayer + Clone + Send + Sync + 'static,
PP: PublishPipeline + Clone + Send + 'static,
{
let pipeline = pipeline.clone();
let Self {
source,
def,
codec,
publisher,
extra,
mut meta,
policies,
workers,
batch_size,
} = self;
meta.describe_reply(|channel| {
PublishDescription::of::<Connected<B>, ReplySource>(
&publisher,
Some(<BatchReply as ReplyPublisher<Def::Context>>::Codec::CONTENT_TYPE),
false,
channel,
)
});
let setup = setup.resolve::<Source::Copies, _>(retry_pipeline, &mut meta);
let subscription = meta.name.clone();
sink.push_injected_batch::<_, _, _, _, Def::Context>(
source,
async move |connected: Arc<Connected<B>>, subscriber| {
warn_ignored_reply_name(
&subscription,
type_name::<Def::Reply>(),
def.reply_name(),
def.ignored_reply_name(),
);
let pairing = publisher.pair(connected.as_ref());
#[cfg(feature = "testing")]
let (publisher, origin) = paired(connected.as_ref(), pairing).await;
#[cfg(not(feature = "testing"))]
let publisher = pairing.await;
let publisher = publisher.map_err(|e| Box::new(e) as BoxError)?;
let injections = Def::Injections::resolve(extra, connected.as_ref(), &subscriber)
.await
.map_err(|e| Box::new(e) as BoxError)?;
let handler = BatchPublishingHandler {
def,
codec,
publisher,
pipeline,
injections,
decode: policies.decode,
#[cfg(feature = "testing")]
origin,
};
Ok((subscriber, handler))
},
meta,
policies,
workers,
batch_size,
setup,
);
}
}
#[cfg(all(test, feature = "memory", feature = "json"))]
mod tests {
use super::*;
use crate::Name;
use crate::codec::JsonCodec;
use crate::memory::MemoryPublish;
#[test]
fn deferred_route_debug_names_the_registration() {
let publishing = PublishingRoute {
source: Name::new("orders"),
def: (),
codec: JsonCodec,
publisher: TypedPublisher::with_codec(MemoryPublish, JsonCodec),
extra: ((),),
meta: HandlerMetadata::raw("orders"),
policies: FailurePolicies::default(),
workers: Workers::sequential(),
};
let rendered = format!("{publishing:?}");
assert!(rendered.contains("PublishingRoute"), "{rendered}");
assert!(rendered.contains("orders"), "{rendered}");
let raw_reply = RawReplyRoute {
source: Name::new("raw-orders"),
def: (),
codec: JsonCodec,
publisher: MemoryPublish,
extra: ((),),
meta: HandlerMetadata::raw("raw-orders"),
policies: FailurePolicies::default(),
workers: Workers::sequential(),
};
let rendered = format!("{raw_reply:?}");
assert!(rendered.contains("RawReplyRoute"), "{rendered}");
assert!(rendered.contains("raw-orders"), "{rendered}");
let batch_publishing = BatchPublishingRoute {
source: Name::new("bulk-orders"),
def: (),
codec: JsonCodec,
publisher: TypedPublisher::with_codec(MemoryPublish, JsonCodec),
extra: ((),),
meta: HandlerMetadata::raw("bulk-orders"),
policies: FailurePolicies::default(),
workers: Workers::sequential(),
batch_size: crate::nonzero!(8),
};
let rendered = format!("{batch_publishing:?}");
assert!(rendered.contains("BatchPublishingRoute"), "{rendered}");
assert!(rendered.contains("bulk-orders"), "{rendered}");
}
}