use std::future::Future;
use bytes::BytesMut;
use serde::Serialize;
use tracing::warn;
#[cfg(feature = "testing")]
use crate::testing::coordinator::{Origin, publishing_to};
use crate::{BuildBatchContext, IncomingMessage, PayloadForm};
use super::batch::{BatchHandler, BatchResult, decode_batch, settle_batch};
use super::context::Context;
use super::dispatch::Workers;
use super::failure::{FailurePolicies, FailurePolicy};
use super::handler::HandlerOutcome;
use super::input::{DecodeWith, InputKind};
use super::metadata::{HandlerMetadata, OutgoingMessageMetadata};
use super::publish::{PublishContext, PublishIdentity, PublishPipeline, ReplyPublisher};
pub trait BatchPublishingDef: Send + Sync {
type Input: InputKind;
type Injections;
type Context;
type Reply;
type Source;
fn source(&self) -> Self::Source;
fn reply_name(&self) -> &str;
fn ignored_reply_name(&self) -> Option<&str> {
None
}
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 headers_schema(&self) -> Option<String> {
None
}
fn outgoing(&self) -> Vec<OutgoingMessageMetadata> {
Vec::new()
}
fn message_name(&self) -> Option<&'static str> {
None
}
fn message_description(&self) -> Option<&'static str> {
None
}
}
pub(crate) trait BatchPublishingCall<S>: BatchPublishingDef {
fn call(
&self,
batch: &[<Self::Input as InputKind>::Owned],
injections: &Self::Injections,
ctx: &mut Context<'_, Self::Context, S>,
) -> impl Future<Output = Result<Vec<Self::Reply>, BatchResult>> + Send;
}
pub(crate) fn batch_publishing_metadata<D: BatchPublishingDef>(
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.headers_schema(),
def.message_name(),
def.message_description(),
);
meta.input_type = <D::Input as InputKind>::input_label();
meta.deserialized = <D::Input as InputKind>::DESERIALIZED;
meta.outgoing = def.outgoing();
meta
}
pub(crate) struct BatchPublishingHandler<D: BatchPublishingDef, C, R, PP = PublishIdentity> {
pub(crate) def: D,
pub(crate) codec: C,
pub(crate) publisher: R,
pub(crate) pipeline: PP,
pub(crate) injections: D::Injections,
pub(crate) decode: FailurePolicy,
#[cfg(feature = "testing")]
pub(crate) origin: Origin,
}
impl<D: BatchPublishingDef, C, R, PP> std::fmt::Debug for BatchPublishingHandler<D, C, R, PP> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BatchPublishingHandler")
.finish_non_exhaustive()
}
}
impl<M, D, C, R, PP, S> BatchHandler<M, D::Context, S> for BatchPublishingHandler<D, C, R, PP>
where
M: IncomingMessage,
D: BatchPublishingCall<S>,
D::Input: DecodeWith<C>,
D::Injections: Send + Sync,
D::Reply: Serialize + Send + Sync,
D::Context: BuildBatchContext<M> + Send + Sync + 'static,
C: Send + Sync,
R: ReplyPublisher<D::Context>,
PP: PublishPipeline,
S: Send + Sync,
{
type Scratch = Vec<<D::Input as InputKind>::Owned>;
async fn handle_batch(
&self,
batch: Vec<M>,
values: &mut Self::Scratch,
ctx: &mut Context<'_, D::Context, S>,
) {
let subscription = ctx.subscription();
let accepted = decode_batch::<M, D::Input, C, D::Context, S>(
batch,
values,
&self.codec,
self.decode,
ctx,
)
.await;
if accepted.is_empty() {
return;
}
let result = match self.def.call(values, &self.injections, ctx).await {
Ok(replies) => {
let name = self.def.reply_name();
let mut own = BytesMut::new();
let encode = ctx.take_encode_buffer().unwrap_or(&mut own);
let slot = <R::Payload as PayloadForm>::encode_slot(encode);
let pubcx = PublishContext::new(ctx.name(), ctx.delivery_headers(), ctx.cx_ref());
let publish =
self.publisher
.publish_batch(name, &replies, &self.pipeline, &pubcx, slot);
#[cfg(feature = "testing")]
let publish = publishing_to(self.origin, publish);
match publish.await {
Ok(()) => BatchResult::Uniform(HandlerOutcome::ack()),
Err(err) => {
warn!(
target: "ruststream::dispatch",
subscription = %subscription,
reply = %name,
reply_type = std::any::type_name::<D::Reply>(),
error = %err,
"batch reply publish failed",
);
BatchResult::Uniform(HandlerOutcome::retry())
}
}
}
Err(result) => result,
};
values.clear();
settle_batch(accepted, result, subscription, ctx.delivery()).await;
}
}
#[cfg(all(test, feature = "memory", feature = "json"))]
mod tests;