use std::future::Future;
use tracing::warn;
use crate::codec::Codec;
use crate::{Broker, Connected, IncomingMessage, PairError, PublishPolicy, Seekable};
use super::context::Context;
use super::dispatch::Workers;
use super::failure::{FailurePolicies, FailurePolicy};
use super::handler::{Handler, HandlerResult, Settle};
use super::input::{DecodeWith, InputKind};
use super::metadata::HandlerMetadata;
#[derive(Debug)]
pub struct Out<P>(pub P);
#[derive(Debug)]
pub struct Seek<K>(pub K);
pub trait FromStartup<B: Broker, Sub, Extra>: Sized {
fn resolve(
extra: &Extra,
connected: &Connected<B>,
subscriber: &Sub,
) -> impl Future<Output = Result<Self, PairError>> + Send;
}
impl<B, Sub, Extra> FromStartup<B, Sub, Extra> for Out<Extra::Live>
where
B: Broker,
Sub: Sync,
Extra: PublishPolicy<Connected<B>> + Clone + Send + Sync,
{
async fn resolve(
extra: &Extra,
connected: &Connected<B>,
_subscriber: &Sub,
) -> Result<Self, PairError> {
Ok(Self(extra.clone().pair(connected).await?))
}
}
impl<B, Sub, Extra> FromStartup<B, Sub, Extra> for Seek<Sub::Seeker>
where
B: Broker,
Sub: Seekable + Sync,
Extra: Sync,
{
async fn resolve(
_extra: &Extra,
_connected: &Connected<B>,
subscriber: &Sub,
) -> Result<Self, PairError> {
Ok(Self(subscriber.seeker()))
}
}
impl<B: Broker, Sub: Sync, Extra: Sync> FromStartup<B, Sub, Extra> for () {
async fn resolve(
_extra: &Extra,
_connected: &Connected<B>,
_subscriber: &Sub,
) -> Result<Self, PairError> {
Ok(())
}
}
macro_rules! impl_from_startup_for_tuples {
($(($($name:ident),+))+) => {$(
impl<B, Sub, Extra, $($name),+> FromStartup<B, Sub, Extra> for ($($name,)+)
where
B: Broker,
Sub: Sync,
Extra: Sync,
$($name: FromStartup<B, Sub, Extra> + Send,)+
{
async fn resolve(
extra: &Extra,
connected: &Connected<B>,
subscriber: &Sub,
) -> Result<Self, PairError> {
Ok(($($name::resolve(extra, connected, subscriber).await?,)+))
}
}
)+};
}
impl_from_startup_for_tuples! {
(T1)
(T1, T2)
(T1, T2, T3)
(T1, T2, T3, T4)
}
pub trait InjectDef: Send + Sync {
type Input: InputKind;
type Context;
type Source;
type Injections;
fn source(&self) -> Self::Source;
fn workers(&self) -> Workers {
Workers::sequential()
}
fn failure_policies(&self) -> FailurePolicies {
FailurePolicies::default()
}
fn description(&self) -> Option<&str> {
None
}
fn input_schema(&self) -> Option<String> {
None
}
fn message_name(&self) -> Option<&'static str> {
None
}
fn message_description(&self) -> Option<&'static str> {
None
}
}
pub trait InjectCall<S>: InjectDef {
fn call(
&self,
input: &<Self::Input as InputKind>::Target,
injections: &Self::Injections,
ctx: &mut Context<'_, Self::Context, S>,
) -> impl Future<Output = Settle> + Send;
}
pub(crate) fn inject_metadata<D: InjectDef>(name: String, def: &D) -> HandlerMetadata {
let mut meta = HandlerMetadata::raw(name).with_def_details(
def.description(),
def.input_schema(),
def.message_name(),
def.message_description(),
);
meta.input_type = <D::Input as InputKind>::input_label();
meta
}
pub struct InjectHandler<Def: InjectDef, DecodeCodec> {
pub(crate) def: Def,
pub(crate) codec: DecodeCodec,
pub(crate) injections: Def::Injections,
pub(crate) decode: FailurePolicy,
}
impl<Def: InjectDef, DecodeCodec> std::fmt::Debug for InjectHandler<Def, DecodeCodec> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InjectHandler").finish_non_exhaustive()
}
}
impl<Msg, Def, DecodeCodec, State> Handler<Msg, Def::Context, State>
for InjectHandler<Def, DecodeCodec>
where
Msg: IncomingMessage,
Def: InjectCall<State>,
Def::Input: DecodeWith<DecodeCodec>,
Def::Context: Send + Sync,
Def::Injections: Send + Sync,
DecodeCodec: Codec,
State: Send + Sync,
{
async fn handle(&self, msg: &Msg, ctx: &mut Context<'_, Def::Context, State>) -> Settle {
let owned =
match <Def::Input as DecodeWith<DecodeCodec>>::decode(&self.codec, msg.payload()) {
Ok(value) => value,
Err(err) => {
warn!(
target: "ruststream::dispatch",
subscription = %ctx.name(),
message_type = <Def::Input as InputKind>::input_label(),
error = %err,
"codec decode failed",
);
#[cfg(any(feature = "testing", feature = "otel"))]
ctx.mark_decode_failed();
return match self.decode {
FailurePolicy::FailFast => {
ctx.fail_fast(&format!("decode failed: {err}"));
HandlerResult::drop().into()
}
other => other
.settlement()
.unwrap_or_else(HandlerResult::drop)
.into(),
};
}
};
let view = <Def::Input as InputKind>::view(&owned, msg.payload());
self.def.call(view, &self.injections, ctx).await
}
}