use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::time::Duration;
use crate::codec::Codec;
use crate::runtime::batch::BatchResult;
use crate::runtime::batch_inject::{BatchInjectCall, BatchInjectDef};
use crate::runtime::context::Context;
use crate::runtime::handler::HandlerOutcome;
use crate::runtime::inject::FromStartup;
use crate::runtime::inject::{InjectCall, InjectDef};
use crate::runtime::metadata::OutgoingMessageMetadata;
use crate::runtime::publish::{
Admits, HeadersUnset, MessageBody, OutPipeline, PublishBuilder, PublishIdentity,
TransactionScope, TypedTransaction, message_of,
};
use crate::runtime::router::IncludeDef;
use crate::runtime::slot::{
BindSlots, ContainsMessage, HasSlots, OutSlot, PublishedThrough, SlotPublisher,
};
#[cfg(feature = "testing")]
use crate::testing::coordinator::{Origin, paired, publishing_to};
use crate::{
Connected, ConnectedBroker, HeaderMap, Name, OutgoingDestination, OutgoingFor,
OwnedTransactions, PairError, PublishPolicy, Publisher, RequestReply, TransactionalPublisher,
Unnamed,
};
use super::Handle;
use super::axis::{
Axis, AxisDocs, Batch, BatchPair, BatchedAxis, Deserialized, Input, Message, Solo, SoloAxis,
SoloDeserialized, SoloPair,
};
use super::eager::{construct, run_batch, settle_solo};
use super::value::{HandleValue, Sealed};
pub struct Slot<M, W, E, Pipe = PublishIdentity, Body = ()> {
wired: SlotPublisher<W, M>,
codec: E,
pipeline: Pipe,
_declared: PhantomData<fn() -> Body>,
#[cfg(feature = "testing")]
origin: Origin,
}
impl<M, W, E, Pipe, Body> fmt::Debug for Slot<M, W, E, Pipe, Body> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Slot").finish_non_exhaustive()
}
}
impl<M, W, E, Pipe, Body> Deref for Slot<M, W, E, Pipe, Body> {
type Target = W;
fn deref(&self) -> &W {
self.wired.inner()
}
}
impl<M, W, E, Pipe, Body, T, Index> Admits<T, Index> for Slot<M, W, E, Pipe, Body>
where
T: PublishedThrough<M>,
Body: ContainsMessage<T, Index>,
{
}
impl<M: OutSlot, W: Publisher, E: Codec + Send + Sync, Pipe: OutPipeline<W>, Body>
Slot<M, W, E, Pipe, Body>
{
pub fn message<'a, T, Index>(
&'a self,
value: &'a T,
) -> PublishBuilder<&'a Self, MessageBody<'a, T>, &'a E, HeadersUnset, T::Form>
where
Body: ContainsMessage<T, Index>,
T: OutgoingDestination + PublishedThrough<M>,
{
message_of(self, value, &self.codec)
}
}
impl<M: OutSlot, W: TransactionalPublisher, E: Send + Sync, Pipe: Send + Sync, Body>
Slot<M, W, E, Pipe, Body>
{
pub async fn begin(
&self,
) -> Result<TransactionScope<'_, SlotPublisher<W, M>, &'_ E, Self>, W::Error> {
TransactionScope::open(&self.wired, &self.codec).await
}
}
impl<M: OutSlot, W: OwnedTransactions, E: Send + Sync, Pipe: Send + Sync, Body>
Slot<M, W, E, Pipe, Body>
{
pub async fn transaction(
&self,
) -> Result<TypedTransaction<W::Transaction, &'_ E, Self>, W::Error> {
TypedTransaction::open(&self.wired, &self.codec).await
}
}
impl<M: OutSlot, W: Publisher, E: Send + Sync, Pipe: OutPipeline<W>, Body> Publisher
for Slot<M, W, E, Pipe, Body>
{
type Payload = W::Payload;
type Error = Pipe::Error<W::Error>;
type Options = W::Options;
async fn publish(
&self,
msg: OutgoingFor<'_, Self::Payload>,
options: Option<&Self::Options>,
) -> Result<(), Self::Error> {
let publish = self.pipeline.send(&self.wired, msg, options);
#[cfg(feature = "testing")]
let publish = publishing_to(self.origin, publish);
publish.await
}
fn base_headers(&self) -> Option<&HeaderMap> {
self.wired.base_headers()
}
}
impl<M: OutSlot, W: TransactionalPublisher, E: Send + Sync, Pipe: OutPipeline<W>, Body>
TransactionalPublisher for Slot<M, W, E, Pipe, Body>
{
async fn begin_transaction(&self) -> Result<(), Self::Error> {
self.wired
.begin_transaction()
.await
.map_err(Pipe::from_publish_error)
}
async fn commit(&self) -> Result<(), Self::Error> {
self.wired.commit().await.map_err(Pipe::from_publish_error)
}
async fn abort(&self) -> Result<(), Self::Error> {
self.wired.abort().await.map_err(Pipe::from_publish_error)
}
}
impl<M: OutSlot, W: OwnedTransactions, E: Send + Sync, Pipe: OutPipeline<W>, Body> OwnedTransactions
for Slot<M, W, E, Pipe, Body>
{
type Transaction = W::Transaction;
async fn transaction(&self) -> Result<Self::Transaction, Self::Error> {
self.wired
.transaction()
.await
.map_err(Pipe::from_publish_error)
}
}
impl<M: OutSlot, W: RequestReply, E: Send + Sync, Pipe: OutPipeline<W>, Body> RequestReply
for Slot<M, W, E, Pipe, Body>
{
type Reply = W::Reply;
async fn request(
&self,
msg: OutgoingFor<'_, Self::Payload>,
timeout: Duration,
) -> Result<Self::Reply, Self::Error> {
let request = self.wired.request(msg, timeout);
#[cfg(feature = "testing")]
let request = publishing_to(self.origin, request);
request.await.map_err(Pipe::from_publish_error)
}
}
impl<M, W, E, Pipe, Body> Slot<M, W, E, Pipe, Body> {
#[cfg(test)]
pub(crate) fn wired(wired: W, codec: E, pipeline: Pipe) -> Self {
Self {
wired: SlotPublisher::new(wired),
codec,
pipeline,
_declared: PhantomData,
#[cfg(feature = "testing")]
origin: Origin::default(),
}
}
}
impl<B, Sub, Policy, E, Pipe, M, Body> FromStartup<B, Sub, (Policy, E, Pipe)>
for Slot<M, <Policy as PublishPolicy<Connected<B>>>::Live, E, Pipe, Body>
where
B: crate::Broker,
Sub: Sync,
Policy: PublishPolicy<Connected<B>> + Send,
E: Send,
Pipe: Send,
M: OutSlot,
{
async fn resolve(
(policy, codec, pipeline): (Policy, E, Pipe),
connected: &Connected<B>,
_subscriber: &Sub,
) -> Result<Self, PairError> {
let pairing = policy.pair(connected);
#[cfg(feature = "testing")]
let (live, origin) = paired(connected, pairing).await;
#[cfg(not(feature = "testing"))]
let live = pairing.await;
let live = live.map_err(|err| {
PairError::from_boxed(Box::from(format!(
"pairing the publisher for the `{}` slot failed: {err}",
M::NAME,
)))
})?;
Ok(Self {
wired: SlotPublisher::new(live),
codec,
pipeline,
_declared: PhantomData,
#[cfg(feature = "testing")]
origin,
})
}
}
pub trait OutEntry<M, Body = ()>: Send + Sync {
type Wire: Send + Sync;
type Enc: Codec;
type Pipe: OutPipeline<Self::Wire>;
#[doc(hidden)]
fn entry(&self) -> &Slot<M, Self::Wire, Self::Enc, Self::Pipe, Body>;
}
impl<M, W, E, Pipe, Body> OutEntry<M, Body> for Slot<M, W, E, Pipe, Body>
where
W: Send + Sync,
E: Codec,
Pipe: OutPipeline<W>,
{
type Wire = W;
type Enc = E;
type Pipe = Pipe;
fn entry(&self) -> &Self {
self
}
}
pub struct Outs<E> {
entries: E,
}
impl<E> fmt::Debug for Outs<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Outs").finish_non_exhaustive()
}
}
impl<E> Outs<E> {
#[allow(clippy::needless_pass_by_value)]
pub fn get<M, I>(&self, marker: M) -> &<E as SelectSlot<M, I>>::Picked
where
E: SelectSlot<M, I>,
{
let _ = marker;
self.entries.pick()
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(
message = "this arena has no `{M}` slot",
note = "the body's `Outs<(..)>` declaration lists the slots it may publish through; check \
the marker"
)]
pub trait SelectSlot<M, I> {
type Picked;
fn pick(&self) -> &Self::Picked;
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct OutPos<const N: usize>;
macro_rules! impl_select_slot {
($(($($before:ident,)* @ $pos:literal $(, $after:ident)*))+) => {$(
impl<M, Body, Entry $(, $before)* $(, $after)*> SelectSlot<M, (OutPos<$pos>, Body)>
for ($($before,)* Entry, $($after,)*)
where
Entry: OutEntry<M, Body>,
{
type Picked = Slot<M, Entry::Wire, Entry::Enc, Entry::Pipe, Body>;
fn pick(&self) -> &Self::Picked {
#[allow(non_snake_case)]
let ($($before,)* picked, $($after,)*) = self;
$(let _ = $before;)*
$(let _ = $after;)*
picked.entry()
}
}
)+};
}
impl_select_slot! {
(@ 0)
(@ 0, A1)
(A0, @ 1)
(@ 0, A1, A2)
(A0, @ 1, A2)
(A0, A1, @ 2)
}
impl<B, Sub, Extra, E> FromStartup<B, Sub, Extra> for Outs<E>
where
B: crate::Broker,
Sub: Sync,
Extra: Send,
E: FromStartup<B, Sub, Extra>,
{
async fn resolve(
extra: Extra,
connected: &Connected<B>,
subscriber: &Sub,
) -> Result<Self, PairError> {
Ok(Self {
entries: E::resolve(extra, connected, subscriber).await?,
})
}
}
#[doc(hidden)]
pub trait EntryMarkers {
type Markers;
fn outgoing() -> Vec<OutgoingMessageMetadata>;
}
macro_rules! impl_entry_markers {
($(($($m:ident: $w:ident / $e:ident / $p:ident / $b:ident),+))+) => {$(
impl<$($m: OutSlot, $w, $e, $p, $b),+> EntryMarkers
for ($(Slot<$m, $w, $e, $p, $b>,)+)
{
type Markers = ($($m,)+);
fn outgoing() -> Vec<OutgoingMessageMetadata> {
let mut declared = Vec::new();
$(declared.extend($m::outgoing());)+
declared
}
}
)+};
}
impl_entry_markers! {
(M0: W0 / E0 / P0 / B0)
(M0: W0 / E0 / P0 / B0, M1: W1 / E1 / P1 / B1)
(M0: W0 / E0 / P0 / B0, M1: W1 / E1 / P1 / B1, M2: W2 / E2 / P2 / B2)
}
impl<A, E, C, H, Doc> IncludeDef for Sealed<HandleValue<A, (), Outs<E>, C, H, Doc>>
where
A: Axis,
{
type Form = A::SlotForm;
}
impl<A, E, C, H, Doc> HasSlots for Sealed<HandleValue<A, (), Outs<E>, C, H, Doc>>
where
E: EntryMarkers,
{
type Markers = E::Markers;
}
macro_rules! impl_bind_slots {
($(($($m:ident / $p:ident: $e:ident / $pipe:ident / $b:ident),+))+) => {$(
impl<Conn, A, C, H, Doc, $($m, $p, $e, $pipe, $b),+>
BindSlots<Conn, ($(($p, $e, $pipe),)+)>
for Sealed<
HandleValue<
A,
(),
Outs<($(Slot<$m, <$p as PublishPolicy<Conn>>::Live, $e, $pipe, $b>,)+)>,
C,
H,
Doc,
>,
>
where
Conn: ConnectedBroker,
$(
$m: OutSlot,
$p: PublishPolicy<Conn>,
)+
{
type Bound = Self;
type Extra = ($(($p, $e, $pipe),)+);
fn bind(self, sources: ($(($p, $e, $pipe),)+)) -> (Self, Self::Extra) {
(self, sources)
}
}
)+};
}
impl_bind_slots! {
(M0 / P0: E0 / Pipe0 / B0)
(M0 / P0: E0 / Pipe0 / B0, M1 / P1: E1 / Pipe1 / B1)
(M0 / P0: E0 / Pipe0 / B0, M1 / P1: E1 / Pipe1 / B1, M2 / P2: E2 / Pipe2 / B2)
}
impl<A, C, H, Doc, E> InjectDef for Sealed<HandleValue<A, (), Outs<E>, C, H, Doc>>
where
A: SoloAxis,
C: Send + Sync,
H: Send + Sync,
Doc: AxisDocs<A> + Send + Sync,
E: EntryMarkers + Send + Sync,
{
type Input = A::Kind;
type Context = C;
type Source = Unnamed<Name>;
type Injections = Outs<E>;
fn source(&self) -> Unnamed<Name> {
Unnamed::new()
}
fn description(&self) -> Option<&str> {
self.0.docs.description()
}
fn input_schema(&self) -> Option<String> {
self.0
.docs
.input_schema
.clone()
.or_else(Doc::payload_schema)
}
fn headers_schema(&self) -> Option<String> {
self.0
.docs
.headers_schema
.clone()
.or_else(Doc::headers_schema)
}
fn message_name(&self) -> Option<&'static str> {
self.0.docs.message_name
}
fn message_description(&self) -> Option<&'static str> {
self.0.docs.message_description
}
fn outgoing(&self) -> Vec<OutgoingMessageMetadata> {
self.0.docs.outgoing.clone().unwrap_or_else(E::outgoing)
}
}
impl<T, C, S, H, Doc, E> InjectCall<S> for Sealed<HandleValue<Solo<T>, (), Outs<E>, C, H, Doc>>
where
Self: InjectDef<Input = <Solo<T> as Axis>::Kind, Context = C, Injections = Outs<E>>,
T: Input<Axis = Solo<T>> + Send + Sync + 'static,
C: Send + Sync,
S: Send + Sync,
H: Handle<T, (), Outs<E>, C, S>,
E: Send + Sync,
{
async fn call(
&self,
input: &T,
injections: &Outs<E>,
ctx: &mut Context<'_, C, S>,
) -> HandlerOutcome {
settle_solo(self.0.body.handle(input, injections, ctx).await)
}
}
impl<F, C, S, H, Doc, E> InjectCall<S>
for Sealed<HandleValue<SoloDeserialized<F>, (), Outs<E>, C, H, Doc>>
where
Self: InjectDef<Input = <SoloDeserialized<F> as Axis>::Kind, Context = C, Injections = Outs<E>>,
F: Deserialized + Send + Sync + 'static,
for<'p> F::Output<'p>: Input<Axis = SoloDeserialized<F>>,
C: Send + Sync,
S: Send + Sync,
H: for<'p> Handle<F::Output<'p>, (), Outs<E>, C, S>,
E: Send + Sync,
{
async fn call(
&self,
input: &[u8],
injections: &Outs<E>,
ctx: &mut Context<'_, C, S>,
) -> HandlerOutcome {
let input = match construct::<F, C, S>(input, ctx) {
Ok(input) => input,
Err(outcome) => return outcome,
};
settle_solo(self.0.body.handle(&input, injections, ctx).await)
}
}
impl<Hd, P, C, S, H, Doc, E> InjectCall<S>
for Sealed<HandleValue<SoloPair<Hd, P>, (), Outs<E>, C, H, Doc>>
where
Self: InjectDef<Input = <SoloPair<Hd, P> as Axis>::Kind, Context = C, Injections = Outs<E>>,
Message<Hd, P>: Input<Axis = SoloPair<Hd, P>>,
Hd: Send + Sync + 'static,
P: Send + Sync + 'static,
C: Send + Sync,
S: Send + Sync,
H: Handle<Message<Hd, P>, (), Outs<E>, C, S>,
E: Send + Sync,
{
async fn call(
&self,
input: &Message<Hd, P>,
injections: &Outs<E>,
ctx: &mut Context<'_, C, S>,
) -> HandlerOutcome {
settle_solo(self.0.body.handle(input, injections, ctx).await)
}
}
impl<A, C, H, Doc, E> BatchInjectDef for Sealed<HandleValue<A, (), Outs<E>, C, H, Doc>>
where
A: BatchedAxis,
C: Send + Sync,
H: Send + Sync,
Doc: AxisDocs<A> + Send + Sync,
E: EntryMarkers + Send + Sync,
{
type Input = A::Kind;
type Source = Unnamed<Name>;
type Injections = Outs<E>;
type Context = C;
fn source(&self) -> Unnamed<Name> {
Unnamed::new()
}
fn description(&self) -> Option<&str> {
self.0.docs.description()
}
fn input_schema(&self) -> Option<String> {
self.0
.docs
.input_schema
.clone()
.or_else(Doc::payload_schema)
}
fn headers_schema(&self) -> Option<String> {
self.0
.docs
.headers_schema
.clone()
.or_else(Doc::headers_schema)
}
fn message_name(&self) -> Option<&'static str> {
self.0.docs.message_name
}
fn message_description(&self) -> Option<&'static str> {
self.0.docs.message_description
}
fn outgoing(&self) -> Vec<OutgoingMessageMetadata> {
self.0.docs.outgoing.clone().unwrap_or_else(E::outgoing)
}
}
impl<T, C, S, H, Doc, E> BatchInjectCall<S>
for Sealed<HandleValue<Batch<T>, (), Outs<E>, C, H, Doc>>
where
Self: BatchInjectDef<Input = <Batch<T> as Axis>::Kind, Injections = Outs<E>, Context = C>,
[T]: Input<Axis = Batch<T>>,
T: Send + Sync + 'static,
C: Send + Sync,
S: Send + Sync,
H: Handle<[T], (), Outs<E>, C, S>,
E: Send + Sync,
{
async fn call(
&self,
batch: &[T],
injections: &Outs<E>,
ctx: &mut Context<'_, C, S>,
) -> BatchResult {
run_batch(&self.0.body, injections, batch, ctx).await
}
}
impl<Hd, P, C, S, H, Doc, E> BatchInjectCall<S>
for Sealed<HandleValue<BatchPair<Hd, P>, (), Outs<E>, C, H, Doc>>
where
Self:
BatchInjectDef<Input = <BatchPair<Hd, P> as Axis>::Kind, Injections = Outs<E>, Context = C>,
[Message<Hd, P>]: Input<Axis = BatchPair<Hd, P>>,
Hd: Send + Sync + 'static,
P: Send + Sync + 'static,
C: Send + Sync,
S: Send + Sync,
H: Handle<[Message<Hd, P>], (), Outs<E>, C, S>,
E: Send + Sync,
{
async fn call(
&self,
batch: &[Message<Hd, P>],
injections: &Outs<E>,
ctx: &mut Context<'_, C, S>,
) -> BatchResult {
run_batch(&self.0.body, injections, batch, ctx).await
}
}