use std::fmt;
use std::num::NonZeroUsize;
use std::sync::Arc;
use crate::{
BatchSubscriber, Broker, BuildBatchContext, BuildContext, Connected, SubscriptionSource,
};
use crate::runtime::batch_inject::{BatchInjectCall, BatchInjectDef, BatchInjectHandler};
use crate::runtime::dispatch::{Workers, spawn_dispatch_workers};
use crate::runtime::failure::{DispatchFailure, FailurePolicies};
use crate::runtime::inject::{FromStartup, InjectCall, InjectDef, InjectHandler};
use crate::runtime::input::DecodeWith;
use crate::runtime::lifecycle::BoxError;
use crate::runtime::metadata::HandlerMetadata;
use crate::runtime::middleware::BlanketLayer;
use crate::runtime::publish::PublishPipeline;
use crate::runtime::redelivery::{CopyPathAddress, CopyPathPairing, RetrySetup, open_subscription};
use crate::runtime::retry::RetryOpen;
use super::SourceMessage;
use super::routes::{
MountRoute, RouteMeta, RouteMetadata, RouteSubscription, impl_destination_declared,
};
use super::sink::RouterSink;
#[doc(hidden)]
pub struct InjectRoute<Source, Def, DecodeCodec, Extra> {
pub(super) source: Source,
pub(super) def: Def,
pub(super) codec: DecodeCodec,
pub(super) extra: Extra,
pub(super) meta: HandlerMetadata,
pub(super) policies: FailurePolicies,
pub(super) workers: Workers,
}
#[doc(hidden)]
pub struct BatchInjectRoute<Source, Def, DecodeCodec, Extra> {
pub(super) source: Source,
pub(super) def: Def,
pub(super) codec: DecodeCodec,
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!(
InjectRoute<Source, Def, DecodeCodec, Extra>,
BatchInjectRoute<Source, Def, DecodeCodec, Extra>,
);
macro_rules! impl_route_markers {
($($route:ident<$($param:ident),+>),+ $(,)?) => {$(
impl<$($param),+> RetryOpen for $route<$($param),+> {}
impl<$($param),+> RouteMetadata for $route<$($param),+> {
fn metadata_mut(&mut self) -> &mut HandlerMetadata {
&mut self.meta
}
}
)+};
}
impl_route_markers!(
InjectRoute<Source, Def, DecodeCodec, Extra>,
BatchInjectRoute<Source, Def, DecodeCodec, Extra>,
);
impl_destination_declared!(
[Source, Def, DecodeCodec, Extra] InjectRoute<Source, Def, DecodeCodec, Extra>,
[Source, Def, DecodeCodec, Extra] BatchInjectRoute<Source, Def, DecodeCodec, Extra>,
);
impl<B, Source, Def, DecodeCodec, Extra> RouteSubscription<B>
for InjectRoute<Source, Def, DecodeCodec, Extra>
where
B: Broker,
Source: SubscriptionSource<Connected<B>>,
Def: InjectDef,
{
type Source = Source;
type Copies = <Source as SubscriptionSource<Connected<B>>>::Copies;
type Context = Def::Context;
}
impl<B, Source, Def, DecodeCodec, Extra> RouteSubscription<B>
for BatchInjectRoute<Source, Def, DecodeCodec, Extra>
where
B: Broker,
Source: SubscriptionSource<Connected<B>>,
Def: BatchInjectDef,
{
type Source = Source;
type Copies = <Source as SubscriptionSource<Connected<B>>>::Copies;
type Context = Def::Context;
}
impl<B, Source, Def, DecodeCodec, Extra, State, RetryPipeline> MountRoute<B, State, RetryPipeline>
for InjectRoute<Source, Def, DecodeCodec, Extra>
where
B: Broker + 'static,
Source: SubscriptionSource<Connected<B>> + Send + 'static,
Source::Copies:
CopyPathPairing<B, Def::Context, RetryPipeline> + CopyPathAddress<Connected<B>, Source>,
Def::Context: Send + Sync + 'static,
RetryPipeline: Clone,
Source::Subscriber: Sync + Send + 'static,
SourceMessage<B, Source>: Send + Sync + 'static,
State: Send + Sync + 'static,
Def: InjectCall<State> + 'static,
Def::Input: DecodeWith<DecodeCodec>,
Def::Injections: FromStartup<B, Source::Subscriber, Extra> + Send + Sync + 'static,
Def::Context: BuildContext<SourceMessage<B, Source>> + Send + Sync + 'static,
DecodeCodec: Send + Sync + 'static,
Extra: 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,
extra,
mut meta,
policies,
workers,
} = self;
let global = global.clone();
let name: Arc<str> = Arc::from(meta.name.as_ref());
let setup = setup.resolve::<Source::Copies, _>(retry_pipeline, &mut meta);
sink.push_raw(
Box::new(move |connected, state, scope, shutdown| {
Box::pin(async move {
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, _>(
InjectHandler {
def,
codec,
injections,
decode: policies.decode,
},
);
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, Extra, State, RetryPipeline> MountRoute<B, State, RetryPipeline>
for BatchInjectRoute<Source, Def, DecodeCodec, Extra>
where
B: Broker + 'static,
Source: SubscriptionSource<Connected<B>> + Send + 'static,
Source::Copies:
CopyPathPairing<B, Def::Context, RetryPipeline> + CopyPathAddress<Connected<B>, Source>,
Def::Context: Send + Sync + 'static,
RetryPipeline: Clone,
Source::Subscriber: BatchSubscriber + Sync + Send + 'static,
SourceMessage<B, Source>: Send + 'static,
State: Send + Sync + 'static,
Def: BatchInjectCall<State> + 'static,
Def::Input: DecodeWith<DecodeCodec>,
Def::Injections: FromStartup<B, Source::Subscriber, Extra> + Send + Sync + 'static,
Def::Context: BuildBatchContext<SourceMessage<B, Source>> + Send + Sync + 'static,
DecodeCodec: Send + Sync + 'static,
Extra: 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,
extra,
mut meta,
policies,
workers,
batch_size,
} = self;
let setup = setup.resolve::<Source::Copies, _>(retry_pipeline, &mut meta);
sink.push_injected_batch::<_, _, _, _, Def::Context>(
source,
async move |connected: Arc<Connected<B>>, subscriber| {
let injections = Def::Injections::resolve(extra, connected.as_ref(), &subscriber)
.await
.map_err(|e| Box::new(e) as BoxError)?;
let handler = BatchInjectHandler {
def,
codec,
injections,
decode: policies.decode,
};
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;
#[test]
fn an_injection_route_debug_names_the_registration() {
let single = InjectRoute {
source: Name::new("jobs"),
def: (),
codec: JsonCodec,
extra: ((),),
meta: HandlerMetadata::raw("jobs"),
policies: FailurePolicies::default(),
workers: Workers::sequential(),
};
let rendered = format!("{single:?}");
assert!(rendered.contains("InjectRoute"), "{rendered}");
assert!(rendered.contains("jobs"), "{rendered}");
let batched = BatchInjectRoute {
source: Name::new("bulk-jobs"),
def: (),
codec: JsonCodec,
extra: ((),),
meta: HandlerMetadata::raw("bulk-jobs"),
policies: FailurePolicies::default(),
workers: Workers::sequential(),
batch_size: crate::nonzero!(8),
};
let rendered = format!("{batched:?}");
assert!(rendered.contains("BatchInjectRoute"), "{rendered}");
assert!(rendered.contains("bulk-jobs"), "{rendered}");
}
}