use std::future::Future;
use serde::Serialize;
use tracing::warn;
use crate::codec::Codec;
use crate::{IncomingMessage, OutgoingMessage, Publisher};
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;
use super::publish::{
PublishContext, PublishIdentity, PublishPipeline, PublishTransform, TypedPublisher,
};
pub trait ReplySink<Reply, DeliveryCx, Pipeline>: Send + Sync {
type Error: std::fmt::Display;
fn deliver(
&self,
name: &str,
reply: &Reply,
pipeline: &Pipeline,
cx: &PublishContext<'_, DeliveryCx>,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
}
impl<Reply, DeliveryCx, Pipeline, Leaf, ReplyCodec, Transforms>
ReplySink<Reply, DeliveryCx, Pipeline> for TypedPublisher<Leaf, ReplyCodec, Transforms>
where
Reply: Serialize + Sync,
DeliveryCx: Sync,
Pipeline: PublishPipeline,
Leaf: Publisher,
ReplyCodec: Codec,
Transforms: PublishTransform<DeliveryCx>,
{
type Error = Box<dyn std::error::Error + Send + Sync>;
async fn deliver(
&self,
name: &str,
reply: &Reply,
pipeline: &Pipeline,
cx: &PublishContext<'_, DeliveryCx>,
) -> Result<(), Self::Error> {
self.publish(name, reply, pipeline, cx).await
}
}
impl<Reply, DeliveryCx, Pipeline, Bare> ReplySink<Reply, DeliveryCx, Pipeline> for Bare
where
Reply: AsRef<[u8]> + Sync,
DeliveryCx: Sync,
Pipeline: Send + Sync,
Bare: Publisher,
{
type Error = Bare::Error;
async fn deliver(
&self,
name: &str,
reply: &Reply,
_pipeline: &Pipeline,
_cx: &PublishContext<'_, DeliveryCx>,
) -> Result<(), Self::Error> {
self.publish(OutgoingMessage::new(name, reply.as_ref()))
.await
}
}
pub trait PublishingDef: Send + Sync {
type Input: InputKind;
type Injections;
type Reply;
type Context;
type Source;
fn source(&self) -> Self::Source;
fn reply_name(&self) -> &str;
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 PublishingCall<S>: PublishingDef {
fn call(
&self,
input: &<Self::Input as InputKind>::Target,
injections: &Self::Injections,
ctx: &mut Context<'_, Self::Context, S>,
) -> impl Future<Output = Result<Self::Reply, HandlerResult>> + Send;
}
pub(crate) fn publishing_metadata<D: PublishingDef>(name: String, def: &D) -> HandlerMetadata {
let mut meta = HandlerMetadata::raw(name)
.with_output_type(std::any::type_name::<D::Reply>())
.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 PublishingHandler<Def: PublishingDef, DecodeCodec, Wiring, Pipeline = PublishIdentity> {
pub(crate) def: Def,
pub(crate) codec: DecodeCodec,
pub(crate) publisher: Wiring,
pub(crate) pipeline: Pipeline,
pub(crate) injections: Def::Injections,
pub(crate) decode: FailurePolicy,
}
impl<Def: PublishingDef, DecodeCodec, Wiring, Pipeline> std::fmt::Debug
for PublishingHandler<Def, DecodeCodec, Wiring, Pipeline>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PublishingHandler").finish_non_exhaustive()
}
}
impl<Msg, Def, DecodeCodec, Wiring, Pipeline, State> Handler<Msg, Def::Context, State>
for PublishingHandler<Def, DecodeCodec, Wiring, Pipeline>
where
Msg: IncomingMessage,
Def: PublishingCall<State>,
Def::Input: DecodeWith<DecodeCodec>,
Def::Injections: Send + Sync,
Def::Reply: Send + Sync,
Def::Context: Send + Sync,
DecodeCodec: Codec,
Wiring: ReplySink<Def::Reply, Def::Context, Pipeline>,
Pipeline: Send + Sync,
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());
let reply = match self.def.call(view, &self.injections, ctx).await {
Ok(reply) => reply,
Err(result) => return result.into(),
};
let name = self.def.reply_name();
let pubcx = PublishContext::new(ctx.name(), ctx.headers(), ctx.cx_ref());
let publish = self.publisher.deliver(name, &reply, &self.pipeline, &pubcx);
if let Err(err) = publish.await {
warn!(
target: "ruststream::dispatch",
subscription = %ctx.name(),
reply = %name,
reply_type = std::any::type_name::<Def::Reply>(),
error = %err,
"reply publish failed",
);
return HandlerResult::retry().into();
}
HandlerResult::Ack.into()
}
}
#[cfg(test)]
mod tests {
use super::{PublishingCall, PublishingDef, publishing_metadata};
use crate::Headers;
use crate::Name;
use crate::runtime::context::Context;
use crate::runtime::dispatch::{Delivery, Workers};
use crate::runtime::handler::HandlerResult;
struct ManualPub;
impl PublishingDef for ManualPub {
type Input = crate::runtime::Decoded<u32>;
type Injections = ();
type Reply = u32;
type Context = ();
type Source = Name;
fn source(&self) -> Name {
Name::new("in")
}
#[allow(clippy::unnecessary_literal_bound)]
fn reply_name(&self) -> &str {
"out"
}
}
impl<S: Send + Sync> PublishingCall<S> for ManualPub {
async fn call(
&self,
input: &u32,
(): &(),
_ctx: &mut Context<'_, (), S>,
) -> Result<u32, HandlerResult> {
Ok(*input)
}
}
#[tokio::test]
async fn defaults_metadata_and_call() {
let def = ManualPub;
assert_eq!(def.workers(), Workers::sequential());
assert!(def.description().is_none());
assert!(def.input_schema().is_none());
assert!(def.message_name().is_none());
assert!(def.message_description().is_none());
assert_eq!(def.reply_name(), "out");
let _source = def.source();
let meta = publishing_metadata("in".to_owned(), &def);
assert_eq!(meta.name, "in");
let state = ();
let delivery = Delivery::empty();
let headers = Headers::new();
let mut ctx = Context::new("in", &headers, &state, (), &delivery);
assert_eq!(def.call(&5, &(), &mut ctx).await.unwrap(), 5);
}
}