use std::{fmt, marker::PhantomData};
use crate::IncomingMessage;
use crate::codec::Codec;
use serde::de::DeserializeOwned;
use tracing::warn;
use super::context::Context;
use super::failure::FailurePolicy;
use super::handler::{Handler, HandlerOutcome};
use super::input::{DecodeWith, Decoded};
pub fn typed<M, T, C, H>(codec: C, inner: H) -> Typed<M, Decoded<T>, C, H>
where
M: IncomingMessage,
T: DeserializeOwned + Send + Sync + 'static,
C: Codec,
{
Typed::over(codec, inner)
}
pub struct Typed<M, Input, DecodeCodec, Inner> {
codec: DecodeCodec,
inner: Inner,
decode: FailurePolicy,
_phantom: PhantomData<fn(M, Input)>,
}
impl<M, Input, DecodeCodec, Inner> Typed<M, Input, DecodeCodec, Inner> {
#[must_use]
pub fn over(codec: DecodeCodec, inner: Inner) -> Self
where
Input: DecodeWith<DecodeCodec>,
{
Self {
codec,
inner,
decode: FailurePolicy::Drop,
_phantom: PhantomData,
}
}
#[must_use]
pub fn on_decode_failure(mut self, decode: FailurePolicy) -> Self {
self.decode = decode;
self
}
}
impl<M, Input, DecodeCodec, Inner> fmt::Debug for Typed<M, Input, DecodeCodec, Inner> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Typed")
.field("decode", &self.decode)
.finish_non_exhaustive()
}
}
impl<M, Input, DecodeCodec, Inner, Cx, St> Handler<M, Cx, St>
for Typed<M, Input, DecodeCodec, Inner>
where
M: IncomingMessage,
Input: DecodeWith<DecodeCodec>,
DecodeCodec: Send + Sync,
Cx: Send,
St: Send + Sync,
Inner: Handler<Input::Target, Cx, St>,
{
async fn handle(&self, msg: &M, ctx: &mut Context<'_, Cx, St>) -> HandlerOutcome {
match Input::decode(&self.codec, msg) {
Ok(owned) => {
self.inner
.handle(Input::view(&owned, msg.payload()), ctx)
.await
}
Err(err) => {
warn!(
target: "ruststream::dispatch",
subscription = %ctx.name(),
message_type = Input::input_label(),
error = %err,
"codec decode failed",
);
#[cfg(any(feature = "testing", feature = "otel"))]
ctx.mark_decode_failed();
match self.decode {
FailurePolicy::FailFast => {
ctx.fail_fast(&format!("decode failed: {err}"));
HandlerOutcome::drop()
}
other => other
.settlement()
.map_or_else(HandlerOutcome::drop, Into::into),
}
}
}
}
}
#[cfg(all(test, feature = "json", feature = "memory"))]
mod tests {
use std::future::ready;
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
};
use serde::Deserialize;
use super::{Typed, typed};
use crate::codec::JsonCodec;
use crate::memory::{MemoryBroker, MemoryMessage};
use crate::runtime::context::Context;
use crate::runtime::dispatch::Delivery;
use crate::runtime::failure::FailurePolicy;
use crate::runtime::handle::Message;
use crate::runtime::handler::{Handler, HandlerOutcome, HandlerResult};
use crate::runtime::input::DecodedPair;
use crate::testkit::delivery::one_delivery;
use crate::{AckError, HeaderMap, IncomingMessage};
fn counting_inner(seen: &Arc<AtomicU32>) -> impl Handler<u32> {
let seen = Arc::clone(seen);
move |value: &u32, _ctx: &mut Context| {
let seen = Arc::clone(&seen);
let value = *value;
async move {
seen.store(value, Ordering::SeqCst);
HandlerOutcome::ack()
}
}
}
#[test]
fn the_adapter_renders_its_decode_policy() {
let seen = Arc::new(AtomicU32::new(0));
let handler = typed::<MemoryMessage, u32, _, _>(JsonCodec, counting_inner(&seen))
.on_decode_failure(FailurePolicy::Retry);
assert_eq!(format!("{handler:?}"), "Typed { decode: Retry, .. }");
}
#[tokio::test]
async fn raw_bytes_lend_the_payload_itself() {
use super::Typed;
use crate::runtime::input::Provided;
struct Frame;
let seen = Arc::new(AtomicU32::new(0));
let inner = {
let seen = Arc::clone(&seen);
move |bytes: &[u8], _ctx: &mut Context| {
let seen = Arc::clone(&seen);
let len = u32::try_from(bytes.len()).unwrap();
async move {
seen.store(len, Ordering::SeqCst);
HandlerOutcome::ack()
}
}
};
let handler = Typed::<MemoryMessage, Provided<Frame>, (), _>::over((), inner);
let state = ();
let delivery = Delivery::empty();
let headers = HeaderMap::new();
let mut ctx = Context::new("frames", &headers, &state, (), &delivery);
let msg = one_delivery(&MemoryBroker::new(), "frames", b"not json at all").await;
assert_eq!(
handler.handle(&msg, &mut ctx).await.outcome(),
HandlerResult::Ack
);
assert_eq!(seen.load(Ordering::SeqCst), 15);
}
#[tokio::test]
async fn decode_failure_drops_by_default() {
let seen = Arc::new(AtomicU32::new(0));
let handler = typed(JsonCodec, counting_inner(&seen));
let state = ();
let delivery = Delivery::empty();
let headers = HeaderMap::new();
let mut ctx = Context::new("typed", &headers, &state, (), &delivery);
let msg = one_delivery(&MemoryBroker::new(), "typed", b"not json").await;
assert_eq!(
handler.handle(&msg, &mut ctx).await.outcome(),
HandlerResult::drop()
);
assert_eq!(seen.load(Ordering::SeqCst), 0, "inner must not run");
}
#[tokio::test]
async fn decode_failure_requeues_when_overridden() {
let seen = Arc::new(AtomicU32::new(0));
let handler =
typed(JsonCodec, counting_inner(&seen)).on_decode_failure(FailurePolicy::Retry);
let state = ();
let delivery = Delivery::empty();
let headers = HeaderMap::new();
let mut ctx = Context::new("typed", &headers, &state, (), &delivery);
let msg = one_delivery(&MemoryBroker::new(), "typed", b"not json").await;
assert_eq!(
handler.handle(&msg, &mut ctx).await.outcome(),
HandlerResult::retry()
);
assert_eq!(seen.load(Ordering::SeqCst), 0, "inner must not run");
}
#[cfg(feature = "logging")]
#[tokio::test]
async fn decode_failure_log_names_subscription_and_type() {
use crate::testkit::log_capture;
let (events, guard) = log_capture::start();
let seen = Arc::new(AtomicU32::new(0));
let handler = typed(JsonCodec, counting_inner(&seen));
let state = ();
let delivery = Delivery::empty();
let headers = HeaderMap::new();
let mut ctx = Context::new("orders.inbound", &headers, &state, (), &delivery);
let msg = one_delivery(&MemoryBroker::new(), "typed", b"not json").await;
let _ = handler.handle(&msg, &mut ctx).await;
drop(guard);
let decode_event = log_capture::find(&events, "codec decode failed");
assert_eq!(
decode_event.get("subscription").map(String::as_str),
Some("orders.inbound")
);
assert_eq!(
decode_event.get("message_type").map(String::as_str),
Some("u32")
);
}
struct CountingMsg {
payload: Vec<u8>,
headers: HeaderMap,
reads: Arc<AtomicUsize>,
}
impl IncomingMessage for CountingMsg {
fn payload(&self) -> &[u8] {
&self.payload
}
fn headers(&self) -> &HeaderMap {
self.reads.fetch_add(1, Ordering::SeqCst);
&self.headers
}
fn ack(self) -> impl Future<Output = Result<(), AckError>> {
ready(Ok(()))
}
fn nack(self, _requeue: bool) -> impl Future<Output = Result<(), AckError>> {
ready(Ok(()))
}
}
#[tokio::test]
async fn a_decoded_input_never_asks_the_delivery_for_its_headers() {
let reads = Arc::new(AtomicUsize::new(0));
let seen = Arc::new(AtomicU32::new(0));
let handler = typed(JsonCodec, counting_inner(&seen));
let state = ();
let delivery = Delivery::empty();
let headers = HeaderMap::new();
let mut ctx = Context::new("typed", &headers, &state, (), &delivery);
let msg = CountingMsg {
payload: b"7".to_vec(),
headers: HeaderMap::new(),
reads: Arc::clone(&reads),
};
assert_eq!(
handler.handle(&msg, &mut ctx).await.outcome(),
HandlerResult::Ack
);
assert_eq!(seen.load(Ordering::SeqCst), 7);
assert_eq!(
reads.load(Ordering::SeqCst),
0,
"a payload decode reads the payload and nothing else"
);
}
#[derive(Deserialize)]
struct Tenant {
tenant: String,
}
#[tokio::test]
async fn a_pair_input_asks_the_delivery_for_its_headers_once() {
let reads = Arc::new(AtomicUsize::new(0));
let paired = Arc::new(AtomicBool::new(false));
let inner = {
let paired = Arc::clone(&paired);
move |msg: &Message<Tenant, u32>, _ctx: &mut Context| {
let paired = Arc::clone(&paired);
let matched = msg.headers.tenant == "acme" && msg.body == 7;
async move {
paired.store(matched, Ordering::SeqCst);
HandlerOutcome::ack()
}
}
};
let handler = Typed::<CountingMsg, DecodedPair<Tenant, u32>, _, _>::over(JsonCodec, inner);
let state = ();
let delivery = Delivery::empty();
let headers = HeaderMap::new();
let mut ctx = Context::new("pair", &headers, &state, (), &delivery);
let mut delivered = HeaderMap::new();
delivered.insert("tenant", "acme");
let msg = CountingMsg {
payload: b"7".to_vec(),
headers: delivered,
reads: Arc::clone(&reads),
};
assert_eq!(
handler.handle(&msg, &mut ctx).await.outcome(),
HandlerResult::Ack
);
assert!(
paired.load(Ordering::SeqCst),
"the pair reached the handler"
);
assert_eq!(
reads.load(Ordering::SeqCst),
1,
"the kind that needs the map asks for it, once"
);
}
}