use std::fmt;
use std::marker::PhantomData;
use super::{
BatchPublishTransformStack, BatchTransformIdentity, CallCodec, PublishCodec,
PublishTransformIdentity, PublishTransformStack, Transactional, TypedPublisher, UnnamedCodec,
};
#[cfg(feature = "asyncapi")]
use crate::asyncapi::Bindings;
use crate::{ConnectedBroker, PairError, PublishPolicy, TransactionalPublisher};
pub struct RawReplyWiring<Policy>(Policy);
impl<Policy> RawReplyWiring<Policy> {
pub(crate) const fn new(policy: Policy) -> Self {
Self(policy)
}
}
impl<Policy> fmt::Debug for RawReplyWiring<Policy> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawReplyWiring").finish_non_exhaustive()
}
}
impl<CB: ConnectedBroker, Policy: PublishPolicy<CB> + Send> PublishPolicy<CB>
for RawReplyWiring<Policy>
{
type Live = Policy::Live;
async fn pair(self, connected: &CB) -> Result<Self::Live, PairError> {
self.0.pair(connected).await
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self, channel: &str) -> Bindings {
self.0.channel_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn operation_bindings(&self, channel: &str) -> Bindings {
self.0.operation_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn message_bindings(&self, channel: &str) -> Bindings {
self.0.message_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn reply_address_location(&self) -> Option<&'static str> {
self.0.reply_address_location()
}
}
#[doc(hidden)]
pub trait MapReplyPolicy: Sized {
type Policy;
#[must_use]
fn map_policy(self, f: impl FnOnce(Self::Policy) -> Self::Policy) -> Self;
}
impl<Policy, Enc, PL, BL, Tx> MapReplyPolicy for ReplyWiring<Policy, Enc, PL, BL, Tx> {
type Policy = Policy;
fn map_policy(self, f: impl FnOnce(Policy) -> Policy) -> Self {
Self {
policy: f(self.policy),
enc: self.enc,
layers: self.layers,
batch_layers: self.batch_layers,
_tx: PhantomData,
}
}
}
impl<Policy> MapReplyPolicy for RawReplyWiring<Policy> {
type Policy = Policy;
fn map_policy(self, f: impl FnOnce(Policy) -> Policy) -> Self {
Self(f(self.0))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Direct;
#[derive(Debug, Clone, Copy, Default)]
pub struct InTransaction;
pub struct ReplyWiring<
Policy,
Enc = UnnamedCodec,
PL = PublishTransformIdentity,
BL = BatchTransformIdentity,
Tx = Direct,
> {
policy: Policy,
enc: Enc,
layers: PL,
batch_layers: BL,
_tx: PhantomData<fn() -> Tx>,
}
impl<Policy> ReplyWiring<Policy> {
pub(crate) fn new(policy: Policy) -> Self {
Self {
policy,
enc: UnnamedCodec::new(),
layers: PublishTransformIdentity,
batch_layers: BatchTransformIdentity,
_tx: PhantomData,
}
}
}
impl<Policy, Enc, PL, BL, Tx> fmt::Debug for ReplyWiring<Policy, Enc, PL, BL, Tx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReplyWiring").finish_non_exhaustive()
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`{Self}` does not take a reply codec",
label = "this reply's codec cannot be named here",
note = "`.codec(..)` names an encoded reply's codec, right after `.out(Reply, ..)`; a \
`Serialized` reply carries its own bytes and takes no codec at all"
)]
pub trait NameReplyCodec<C> {
type Slot;
type Out;
fn name_codec(self, codec: C) -> Self::Out;
}
impl<Policy, Enc, PL, BL, Tx, C> NameReplyCodec<C> for ReplyWiring<Policy, Enc, PL, BL, Tx> {
type Slot = Enc;
type Out = ReplyWiring<Policy, CallCodec<C>, PL, BL, Tx>;
fn name_codec(self, codec: C) -> Self::Out {
ReplyWiring {
policy: self.policy,
enc: CallCodec(codec),
layers: self.layers,
batch_layers: self.batch_layers,
_tx: PhantomData,
}
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "this reply's codec is already named",
label = "`.codec(..)` fills the reply's codec slot, and it is filled",
note = "a reply encodes with exactly one codec: drop one of the `.codec(..)` calls"
)]
pub trait CodecSlotOpen {}
impl CodecSlotOpen for UnnamedCodec {}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`{Self}` does not take a publish transform",
label = "this reply has no transform stack",
note = "`.transform(..)` composes a `PublishTransform` onto an encoded reply, right after \
`.out(Reply, ..)`; a `Serialized` reply's bytes leave as they are"
)]
pub trait AddReplyTransform<N> {
type Out;
fn add_transform(self, transform: N) -> Self::Out;
}
impl<Policy, Enc, PL, BL, Tx, N> AddReplyTransform<N> for ReplyWiring<Policy, Enc, PL, BL, Tx> {
type Out = ReplyWiring<Policy, Enc, PublishTransformStack<PL, N>, BL, Tx>;
fn add_transform(self, transform: N) -> Self::Out {
ReplyWiring {
policy: self.policy,
enc: self.enc,
layers: PublishTransformStack {
inner: self.layers,
outer: transform,
},
batch_layers: self.batch_layers,
_tx: PhantomData,
}
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`{Self}` does not take a batch publish transform",
label = "this reply has no batch transform stack",
note = "`.batch_transform(..)` composes a `BatchPublishTransform` onto an encoded batch reply \
(`&[T]` plus `reply(..)`), right after `.out(Reply, ..)`"
)]
pub trait AddBatchReplyTransform<N> {
type Out;
fn add_batch_transform(self, transform: N) -> Self::Out;
}
impl<Policy, Enc, PL, BL, Tx, N> AddBatchReplyTransform<N>
for ReplyWiring<Policy, Enc, PL, BL, Tx>
{
type Out = ReplyWiring<Policy, Enc, PL, BatchPublishTransformStack<BL, N>, Tx>;
fn add_batch_transform(self, transform: N) -> Self::Out {
ReplyWiring {
policy: self.policy,
enc: self.enc,
layers: self.layers,
batch_layers: BatchPublishTransformStack {
inner: self.batch_layers,
outer: transform,
},
_tx: PhantomData,
}
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot publish its replies inside a transaction",
label = "this reply has no transaction to open",
note = "`.transactional()` marks an encoded batch reply's wiring, right after \
`.out(Reply, ..)`; the policy it marks must pair into a `TransactionalPublisher`"
)]
pub trait TransactionalReply {
type State;
type Out;
fn into_transactional(self) -> Self::Out;
}
impl<Policy, Enc, PL, BL, Tx> TransactionalReply for ReplyWiring<Policy, Enc, PL, BL, Tx> {
type State = Tx;
type Out = ReplyWiring<Policy, Enc, PL, BL, InTransaction>;
fn into_transactional(self) -> Self::Out {
ReplyWiring {
policy: self.policy,
enc: self.enc,
layers: self.layers,
batch_layers: self.batch_layers,
_tx: PhantomData,
}
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "this reply already publishes inside a transaction",
label = "`.transactional()` marks the reply's publish state, and it is marked",
note = "a batch's replies ride one transaction: drop one of the `.transactional()` calls"
)]
pub trait PublishingDirectly {}
impl PublishingDirectly for Direct {}
impl<CB, Policy, Enc, PL, BL> PublishPolicy<CB> for ReplyWiring<Policy, Enc, PL, BL, Direct>
where
CB: ConnectedBroker,
Policy: PublishPolicy<CB> + Send,
Enc: PublishCodec<Codec: Clone> + Send,
PL: Send,
BL: Send,
{
type Live = TypedPublisher<Policy::Live, Enc::Codec, PL, BL>;
async fn pair(self, connected: &CB) -> Result<Self::Live, PairError> {
let codec = self.enc.codec().clone();
Ok(TypedPublisher::live(
self.policy.pair(connected).await?,
codec,
self.layers,
self.batch_layers,
))
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self, channel: &str) -> Bindings {
self.policy.channel_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn operation_bindings(&self, channel: &str) -> Bindings {
self.policy.operation_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn message_bindings(&self, channel: &str) -> Bindings {
self.policy.message_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn reply_address_location(&self) -> Option<&'static str> {
self.policy.reply_address_location()
}
}
impl<CB, Policy, Enc, PL, BL> PublishPolicy<CB> for ReplyWiring<Policy, Enc, PL, BL, InTransaction>
where
CB: ConnectedBroker,
Policy: PublishPolicy<CB> + Send,
Policy::Live: TransactionalPublisher,
Enc: PublishCodec<Codec: Clone> + Send,
PL: Send,
BL: Send,
{
type Live = Transactional<Policy::Live, Enc::Codec, PL, BL>;
async fn pair(self, connected: &CB) -> Result<Self::Live, PairError> {
let codec = self.enc.codec().clone();
Ok(Transactional::live(TypedPublisher::live(
self.policy.pair(connected).await?,
codec,
self.layers,
self.batch_layers,
)))
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self, channel: &str) -> Bindings {
self.policy.channel_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn operation_bindings(&self, channel: &str) -> Bindings {
self.policy.operation_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn message_bindings(&self, channel: &str) -> Bindings {
self.policy.message_bindings(channel)
}
#[cfg(feature = "asyncapi")]
fn reply_address_location(&self) -> Option<&'static str> {
self.policy.reply_address_location()
}
}
#[cfg(all(test, feature = "memory", feature = "json", feature = "asyncapi"))]
mod tests {
use std::collections::BTreeMap;
use std::future::Future;
use super::{RawReplyWiring, ReplyWiring, TransactionalReply};
use crate::asyncapi::{Binding, Bindings};
use crate::memory::{ConnectedMemoryBroker, MemoryPublish, MemoryPublisher};
use crate::runtime::publish::{Names, NarrowToUse};
use crate::{PairError, PublishPolicy};
fn one(protocol: &'static str, channel: &str) -> Bindings {
let body = BTreeMap::from([("channel".to_owned(), channel.to_owned())]);
Bindings::new().with(Binding::new(protocol, "0.1.0", &body).expect("a listed protocol"))
}
#[derive(Clone, Copy)]
struct Described;
impl PublishPolicy<ConnectedMemoryBroker> for Described {
type Live = MemoryPublisher;
fn pair(
self,
connected: &ConnectedMemoryBroker,
) -> impl Future<Output = Result<Self::Live, PairError>> {
<MemoryPublish as PublishPolicy<ConnectedMemoryBroker>>::pair(MemoryPublish, connected)
}
fn channel_bindings(&self, channel: &str) -> Bindings {
one("nats", channel)
}
fn operation_bindings(&self, channel: &str) -> Bindings {
one("kafka", channel)
}
fn message_bindings(&self, channel: &str) -> Bindings {
one("mqtt", channel)
}
fn reply_address_location(&self) -> Option<&'static str> {
Some("$message.header#/reply-to")
}
}
fn assert_forwards<P: PublishPolicy<ConnectedMemoryBroker>>(wrapper: &P) {
assert_eq!(wrapper.channel_bindings("orders"), one("nats", "orders"));
assert_eq!(wrapper.operation_bindings("orders"), one("kafka", "orders"));
assert_eq!(wrapper.message_bindings("orders"), one("mqtt", "orders"));
assert_eq!(
wrapper.reply_address_location(),
Some("$message.header#/reply-to"),
);
}
#[test]
fn every_wrapper_forwards_what_the_leaf_policy_says() {
assert_forwards(&RawReplyWiring::new(Described));
assert_forwards(&ReplyWiring::new(Described));
assert_forwards(&ReplyWiring::new(Described).into_transactional());
assert_forwards(&<Names as NarrowToUse<Described>>::narrow(Described));
}
}