Skip to main content

ruststream/runtime/app/include/
guard.rs

1//! The guards a scope's `include` hands back: a router chain plus the scope it drains into.
2//!
3//! There are two of them, because a registration has two ways to finish. One that is complete as
4//! it stands commits when the guard drops at the end of the statement ([`Mounting`]), so the type
5//! carries no `#[must_use]`: dropping it IS the registration. One carrying
6//! [`Out`](crate::runtime::Out) slots has nothing to commit until every slot is bound, so its
7//! terminal is a call ([`MountingSlots::build`]) and the type says so with `#[must_use]` - a
8//! forgotten `.build()` is then a warning at the mount site rather than a panic at startup.
9//!
10//! A form with nothing of the handler's own to attach still gets a [`Mounting`] guard, because
11//! every registration can bind the deferred-retry slot ([`Retry`](crate::runtime::Retry)), and
12//! binding it opens a mount chain over the finished route so the slot steps follow.
13
14use std::borrow::Cow;
15use std::fmt;
16use std::num::NonZeroU32;
17
18use crate::Broker;
19
20use crate::runtime::app::scope::BrokerScope;
21use crate::runtime::middleware::BlanketLayer;
22use crate::runtime::publish::{PublishIdentity, PublishPipeline};
23use crate::runtime::retry::{
24    Absent, CapOpen, DeadLetterOpen, DeclareCap, DeclareDeadLetter, DeclareMount, DestinationLast,
25    DestinationOpen, Present, Retry, RetryOpen, RouteDeclaring, RoutePosition,
26};
27use crate::runtime::router::{
28    MapPublisher, Router, RouterBroker, RouterCommit, RouterDef, RouterWith,
29};
30use crate::runtime::slot::{
31    AdmitsAt, BatchTransformLast, CodecLast, MapPolicyLast, NamedStep, OutPosition, Reply,
32    ReplyLast, ReplyStep, TransactionalLast, TransformLast,
33};
34
35/// A mount chain that can be drained into a scope's sink. Machinery; never named directly.
36#[doc(hidden)]
37pub trait ScopeCommit<B: Broker, Layers, C, State, Pipeline>: Sized {
38    fn commit_into(self, scope: &mut BrokerScope<B, Layers, C, State, Pipeline>);
39}
40
41// A finished chain is a router: mounting it is the same operation `include_router` performs, so
42// a scope registration and a router one reach the sink through one path.
43impl<B, Layers, C, State, Pipeline, Routes, RC, RL, RP> ScopeCommit<B, Layers, C, State, Pipeline>
44    for Router<B, Routes, RC, RL, RP>
45where
46    B: Broker + 'static,
47    Routes: RouterDef<B, State, RP>,
48    RL: BlanketLayer + Clone + Send + Sync + 'static,
49    Layers: BlanketLayer + Clone + Send + Sync + 'static,
50    Pipeline: PublishPipeline + Clone + Send + 'static,
51    State: Send + Sync + 'static,
52{
53    fn commit_into(self, scope: &mut BrokerScope<B, Layers, C, State, Pipeline>) {
54        // The chain's router carries the scope's own publish path, so the fallback here is never
55        // the one a registration takes.
56        RouterDef::mount(
57            self,
58            &scope.global,
59            &scope.pipeline,
60            &PublishIdentity,
61            &mut scope.sink,
62        );
63    }
64}
65
66// An unfinished chain commits by running the router's own terminal first.
67impl<B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last>
68    ScopeCommit<B, Layers, C, State, Pipeline> for RouterWith<Mount, R, Def, Attach, Last>
69where
70    B: Broker + 'static,
71    Attach: RouterCommit<Mount, R, Def>,
72    Attach::Out: ScopeCommit<B, Layers, C, State, Pipeline>,
73{
74    fn commit_into(self, scope: &mut BrokerScope<B, Layers, C, State, Pipeline>) {
75        // Not `build()`: see `RouterWith::commit_chain`, a scope's chain answers for its retry
76        // destinations at startup rather than here.
77        self.commit_chain().commit_into(scope);
78    }
79}
80
81/// The guard [`BrokerScope::include`](crate::runtime::BrokerScope::include) hands back for a
82/// registration that is complete as it stands: one router mount chain and the scope it drains
83/// into.
84///
85/// Every step forwards to the chain underneath, so the vocabulary, the typestate and the
86/// diagnostics are the router's. Dropping the guard at the end of the statement is what commits,
87/// which is why the type is not `#[must_use]`: `b.include(respond).out_reply(Publish);` is a
88/// whole registration. A registration whose [`Out`](crate::runtime::Out) slots have to be bound
89/// first gets [`MountingSlots`] instead.
90pub struct Mounting<'s, B, Layers, C, State, Pipeline, Chain>
91where
92    B: Broker + 'static,
93    Chain: ScopeCommit<B, Layers, C, State, Pipeline>,
94{
95    // Options only so a step can move the pieces into the next state out of a Drop type; both
96    // stay `Some` until the terminal or that replacement takes them.
97    scope: Option<&'s mut BrokerScope<B, Layers, C, State, Pipeline>>,
98    chain: Option<Chain>,
99}
100
101impl<'s, B, Layers, C, State, Pipeline, Chain> Mounting<'s, B, Layers, C, State, Pipeline, Chain>
102where
103    B: Broker + 'static,
104    Chain: ScopeCommit<B, Layers, C, State, Pipeline>,
105{
106    pub(super) fn new(
107        chain: Chain,
108        scope: &'s mut BrokerScope<B, Layers, C, State, Pipeline>,
109    ) -> Self {
110        Self {
111            scope: Some(scope),
112            chain: Some(chain),
113        }
114    }
115
116    /// Rebuilds the guard over a stepped chain: how every forwarded step reaches the chain.
117    ///
118    /// # Panics
119    ///
120    /// Never in practice: both pieces stay present until the terminal or a step takes them.
121    fn map_chain<NewChain>(
122        mut self,
123        f: impl FnOnce(Chain) -> NewChain,
124    ) -> Mounting<'s, B, Layers, C, State, Pipeline, NewChain>
125    where
126        NewChain: ScopeCommit<B, Layers, C, State, Pipeline>,
127    {
128        let chain = self
129            .chain
130            .take()
131            .expect("the guard holds its chain until the terminal or a step takes it");
132        let scope = self
133            .scope
134            .take()
135            .expect("the guard holds its scope until the terminal or a step takes it");
136        Mounting::new(f(chain), scope)
137    }
138}
139
140/// The guard over a stepped chain: what each forwarded step of [`Mounting`] returns.
141type Stepped<'s, B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last> =
142    Mounting<'s, B, Layers, C, State, Pipeline, RouterWith<Mount, R, Def, Attach, Last>>;
143
144/// The chain one step of [`Mounting`] produces, named once so the bound below reads.
145type SteppedChain<Mount, R, Def, Attach, Last> = RouterWith<Mount, R, Def, Attach, Last>;
146
147impl<'s, B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last>
148    Mounting<'s, B, Layers, C, State, Pipeline, RouterWith<Mount, R, Def, Attach, Last>>
149where
150    B: Broker + 'static,
151    RouterWith<Mount, R, Def, Attach, Last>: ScopeCommit<B, Layers, C, State, Pipeline>,
152{
153    /// See [`RouterWith::out`]: names the publish policy of one position, the reply's
154    /// ([`Reply`](crate::runtime::Reply)), one slot's, or the deferred retry's
155    /// ([`Retry`](crate::runtime::Retry)).
156    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
157    pub fn out<M, Policy, Index>(
158        self,
159        marker: M,
160        policy: Policy,
161    ) -> Stepped<
162        's,
163        B,
164        Layers,
165        C,
166        State,
167        Pipeline,
168        Mount,
169        R,
170        Def,
171        <M as OutPosition<Mount, R::Broker, Attach, Policy, Index>>::Out,
172        Index,
173    >
174    where
175        R: RouterBroker,
176        M: OutPosition<Mount, R::Broker, Attach, Policy, Index>,
177        SteppedChain<
178            Mount,
179            R,
180            Def,
181            <M as OutPosition<Mount, R::Broker, Attach, Policy, Index>>::Out,
182            Index,
183        >: ScopeCommit<B, Layers, C, State, Pipeline>,
184    {
185        self.map_chain(|chain| chain.out(marker, policy))
186    }
187
188    /// See [`RouterWith::out_reply`].
189    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
190    pub fn out_reply<Policy>(
191        self,
192        policy: Policy,
193    ) -> Stepped<
194        's,
195        B,
196        Layers,
197        C,
198        State,
199        Pipeline,
200        Mount,
201        R,
202        Def,
203        <Reply as OutPosition<Mount, R::Broker, Attach, Policy, ReplyLast>>::Out,
204        ReplyLast,
205    >
206    where
207        R: RouterBroker,
208        Reply: OutPosition<Mount, R::Broker, Attach, Policy, ReplyLast>,
209        SteppedChain<
210            Mount,
211            R,
212            Def,
213            <Reply as OutPosition<Mount, R::Broker, Attach, Policy, ReplyLast>>::Out,
214            ReplyLast,
215        >: ScopeCommit<B, Layers, C, State, Pipeline>,
216    {
217        self.out(Reply, policy)
218    }
219
220    /// See [`RouterWith::out_retry`].
221    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
222    pub fn out_retry<Policy, Index>(
223        self,
224        policy: Policy,
225    ) -> Stepped<
226        's,
227        B,
228        Layers,
229        C,
230        State,
231        Pipeline,
232        Mount,
233        R,
234        Def,
235        <Retry as OutPosition<Mount, R::Broker, Attach, Policy, Index>>::Out,
236        Index,
237    >
238    where
239        R: RouterBroker,
240        Retry: OutPosition<Mount, R::Broker, Attach, Policy, Index>,
241        SteppedChain<
242            Mount,
243            R,
244            Def,
245            <Retry as OutPosition<Mount, R::Broker, Attach, Policy, Index>>::Out,
246            Index,
247        >: ScopeCommit<B, Layers, C, State, Pipeline>,
248    {
249        self.out(Retry, policy)
250    }
251
252    /// See [`RouterWith::max_attempts`].
253    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
254    pub fn max_attempts(
255        self,
256        attempts: NonZeroU32,
257    ) -> Stepped<'s, B, Layers, C, State, Pipeline, Mount, R, Def, <Attach as DeclareCap>::Out, Last>
258    where
259        Attach: DeclareCap<Step: CapOpen>,
260        SteppedChain<Mount, R, Def, <Attach as DeclareCap>::Out, Last>:
261            ScopeCommit<B, Layers, C, State, Pipeline>,
262    {
263        self.map_chain(|chain| chain.max_attempts(attempts))
264    }
265
266    /// See [`RouterWith::dead_letter`].
267    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
268    pub fn dead_letter(
269        self,
270        destination: impl Into<Cow<'static, str>>,
271    ) -> Stepped<
272        's,
273        B,
274        Layers,
275        C,
276        State,
277        Pipeline,
278        Mount,
279        R,
280        Def,
281        <Attach as DeclareDeadLetter>::Out,
282        Last,
283    >
284    where
285        Attach: DeclareDeadLetter<Step: DeadLetterOpen>,
286        SteppedChain<Mount, R, Def, <Attach as DeclareDeadLetter>::Out, Last>:
287            ScopeCommit<B, Layers, C, State, Pipeline>,
288    {
289        self.map_chain(|chain| chain.dead_letter(destination))
290    }
291
292    /// See [`RouterWith::to`].
293    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
294    pub fn to(
295        self,
296        destination: impl Into<Cow<'static, str>>,
297    ) -> Stepped<
298        's,
299        B,
300        Layers,
301        C,
302        State,
303        Pipeline,
304        Mount,
305        R,
306        Def,
307        <Attach as DestinationLast<Last>>::Out,
308        Last,
309    >
310    where
311        Attach: DestinationLast<Last, Step: DestinationOpen>,
312        SteppedChain<Mount, R, Def, <Attach as DestinationLast<Last>>::Out, Last>:
313            ScopeCommit<B, Layers, C, State, Pipeline>,
314    {
315        self.map_chain(|chain| chain.to(destination))
316    }
317
318    /// See [`RouterWith::codec`].
319    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
320    pub fn codec<Cd>(
321        self,
322        codec: Cd,
323    ) -> Stepped<
324        's,
325        B,
326        Layers,
327        C,
328        State,
329        Pipeline,
330        Mount,
331        R,
332        Def,
333        <Attach as CodecLast<Cd, Last>>::Out,
334        Last,
335    >
336    where
337        Attach: CodecLast<Cd, Last, Step: NamedStep>,
338        SteppedChain<Mount, R, Def, <Attach as CodecLast<Cd, Last>>::Out, Last>:
339            ScopeCommit<B, Layers, C, State, Pipeline>,
340    {
341        self.map_chain(|chain| chain.codec(codec))
342    }
343
344    /// See [`RouterWith::transform`].
345    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
346    pub fn transform<N>(
347        self,
348        transform: N,
349    ) -> Stepped<
350        's,
351        B,
352        Layers,
353        C,
354        State,
355        Pipeline,
356        Mount,
357        R,
358        Def,
359        <Attach as TransformLast<N, Last>>::Out,
360        Last,
361    >
362    where
363        R: RouterBroker<Broker = B>,
364        Attach: TransformLast<N, Last, Step: NamedStep> + AdmitsAt<N, Last, Mount, Def, B>,
365        SteppedChain<Mount, R, Def, <Attach as TransformLast<N, Last>>::Out, Last>:
366            ScopeCommit<B, Layers, C, State, Pipeline>,
367    {
368        self.map_chain(|chain| chain.transform(transform))
369    }
370
371    /// See [`RouterWith::batch_transform`].
372    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
373    pub fn batch_transform<N>(
374        self,
375        transform: N,
376    ) -> Stepped<
377        's,
378        B,
379        Layers,
380        C,
381        State,
382        Pipeline,
383        Mount,
384        R,
385        Def,
386        <Attach as BatchTransformLast<N, Last>>::Out,
387        Last,
388    >
389    where
390        Attach: BatchTransformLast<N, Last, Step: ReplyStep>,
391        SteppedChain<Mount, R, Def, <Attach as BatchTransformLast<N, Last>>::Out, Last>:
392            ScopeCommit<B, Layers, C, State, Pipeline>,
393    {
394        self.map_chain(|chain| chain.batch_transform(transform))
395    }
396
397    /// See [`RouterWith::transactional`].
398    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
399    pub fn transactional(
400        self,
401    ) -> Stepped<
402        's,
403        B,
404        Layers,
405        C,
406        State,
407        Pipeline,
408        Mount,
409        R,
410        Def,
411        <Attach as TransactionalLast<Last>>::Out,
412        Last,
413    >
414    where
415        Attach: TransactionalLast<Last, Step: ReplyStep>,
416        SteppedChain<Mount, R, Def, <Attach as TransactionalLast<Last>>::Out, Last>:
417            ScopeCommit<B, Layers, C, State, Pipeline>,
418    {
419        self.map_chain(RouterWith::transactional)
420    }
421}
422
423/// The chain a form with nothing to attach hands the guard: its registration, already grown into
424/// a router of one.
425type EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe> =
426    Router<B, (Head, Tail), RouteCodec, RouteLayers, RoutePipe>;
427
428// A form that attaches nothing has its registration finished the moment `include` returns, so its
429// chain is a router rather than a `RouterWith`. The one position such a registration can still
430// bind is the deferred retry, which belongs to the registration and not to a publish the handler
431// makes - hence this impl, on the guard over a finished router.
432impl<'s, B, Layers, C, State, Pipeline, Head, Tail, RouteCodec, RouteLayers, RoutePipe>
433    Mounting<
434        's,
435        B,
436        Layers,
437        C,
438        State,
439        Pipeline,
440        EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
441    >
442where
443    B: Broker + 'static,
444    EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>:
445        ScopeCommit<B, Layers, C, State, Pipeline>,
446{
447    /// Names the publish policy of one position. A registration whose handler publishes nothing
448    /// of its own has exactly one, the deferred retry: see [`Retry`](crate::runtime::Retry), and
449    /// it binds once. The call opens a mount chain over the finished route, so the steps after it
450    /// are the slot steps.
451    // The unit marker drives inference, so it travels by value to keep the call site
452    // `.out(Retry, ..)`, like every other position's.
453    #[allow(clippy::needless_pass_by_value, clippy::type_complexity)]
454    pub fn out<M, Policy>(
455        self,
456        marker: M,
457        policy: Policy,
458    ) -> Mounting<
459        's,
460        B,
461        Layers,
462        C,
463        State,
464        Pipeline,
465        <M as RoutePosition<
466            B,
467            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
468            Policy,
469        >>::Out,
470    >
471    where
472        M: RoutePosition<B, EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>, Policy>,
473        <M as RoutePosition<
474            B,
475            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
476            Policy,
477        >>::Out: ScopeCommit<B, Layers, C, State, Pipeline>,
478    {
479        let _ = marker;
480        self.map_chain(|chain| M::bind(chain, policy))
481    }
482
483    /// See [`RouterWith::max_attempts`]: the same declaration on a registration that was a
484    /// finished route the moment `include` returned. It opens a mount chain, so the steps after
485    /// it are the chain's.
486    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
487    pub fn max_attempts(
488        self,
489        attempts: NonZeroU32,
490    ) -> Mounting<
491        's,
492        B,
493        Layers,
494        C,
495        State,
496        Pipeline,
497        RouterWith<
498            DeclareMount,
499            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
500            (),
501            RouteDeclaring<Present, Absent>,
502        >,
503    >
504    where
505        Head: RetryOpen,
506        RouterWith<
507            DeclareMount,
508            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
509            (),
510            RouteDeclaring<Present, Absent>,
511        >: ScopeCommit<B, Layers, C, State, Pipeline>,
512    {
513        self.map_chain(|chain| chain.max_attempts(attempts))
514    }
515
516    /// See [`RouterWith::dead_letter`].
517    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
518    pub fn dead_letter(
519        self,
520        destination: impl Into<Cow<'static, str>>,
521    ) -> Mounting<
522        's,
523        B,
524        Layers,
525        C,
526        State,
527        Pipeline,
528        RouterWith<
529            DeclareMount,
530            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
531            (),
532            RouteDeclaring<Absent, Present>,
533        >,
534    >
535    where
536        Head: RetryOpen,
537        RouterWith<
538            DeclareMount,
539            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
540            (),
541            RouteDeclaring<Absent, Present>,
542        >: ScopeCommit<B, Layers, C, State, Pipeline>,
543    {
544        self.map_chain(|chain| chain.dead_letter(destination))
545    }
546
547    /// The same call with the marker spelled out: `.out(Retry, policy)`, and the same chain
548    /// afterwards.
549    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
550    pub fn out_retry<Policy>(
551        self,
552        policy: Policy,
553    ) -> Mounting<
554        's,
555        B,
556        Layers,
557        C,
558        State,
559        Pipeline,
560        <Retry as RoutePosition<
561            B,
562            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
563            Policy,
564        >>::Out,
565    >
566    where
567        Retry:
568            RoutePosition<B, EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>, Policy>,
569        <Retry as RoutePosition<
570            B,
571            EagerChain<B, Head, Tail, RouteCodec, RouteLayers, RoutePipe>,
572            Policy,
573        >>::Out: ScopeCommit<B, Layers, C, State, Pipeline>,
574    {
575        self.out(Retry, policy)
576    }
577}
578
579// The broker's own publisher settings reach a scope registration exactly as they reach a router
580// one: through the chain the guard forwards to. The replacement policy has the same type, so the
581// chain the guard hands on is the one it already held.
582impl<B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last> MapPublisher
583    for Mounting<'_, B, Layers, C, State, Pipeline, RouterWith<Mount, R, Def, Attach, Last>>
584where
585    B: Broker + 'static,
586    Attach: MapPolicyLast<Last, Step: NamedStep>,
587    RouterWith<Mount, R, Def, Attach, Last>: ScopeCommit<B, Layers, C, State, Pipeline>,
588{
589    type Policy = Attach::Policy;
590
591    fn map_publisher(self, f: impl FnOnce(Self::Policy) -> Self::Policy) -> Self {
592        self.map_chain(|chain| chain.map_publisher(f))
593    }
594}
595
596impl<B, Layers, C, State, Pipeline, Chain> fmt::Debug
597    for Mounting<'_, B, Layers, C, State, Pipeline, Chain>
598where
599    B: Broker + 'static,
600    Chain: ScopeCommit<B, Layers, C, State, Pipeline>,
601{
602    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603        f.debug_struct("Mounting").finish_non_exhaustive()
604    }
605}
606
607impl<B, Layers, C, State, Pipeline, Chain> Drop
608    for Mounting<'_, B, Layers, C, State, Pipeline, Chain>
609where
610    B: Broker + 'static,
611    Chain: ScopeCommit<B, Layers, C, State, Pipeline>,
612{
613    fn drop(&mut self) {
614        if let (Some(chain), Some(scope)) = (self.chain.take(), self.scope.take()) {
615            chain.commit_into(scope);
616        }
617    }
618}
619
620/// The guard a registration carrying [`Out`](crate::runtime::Out) slots gets.
621///
622/// It holds the same chain as [`Mounting`], finished by [`build`](Self::build) once every slot is
623/// bound. A chain that still holds an unbound slot has nothing to commit, so this guard's
624/// terminal is a call rather than a drop - and the type is `#[must_use]` for exactly that reason:
625/// a chain dropped before `.build()` registered nothing, so the warning lands on the mount site
626/// that forgot it. The drop of such a chain panics rather than starting a service whose handler
627/// would never consume.
628#[must_use = "a handler with Out slots is not mounted until `.build()` commits it: bind every \
629              slot with `.out(marker, policy)`, then finish the chain with `.build()`"]
630pub struct MountingSlots<'s, B, Layers, C, State, Pipeline, Chain>
631where
632    B: Broker + 'static,
633{
634    // See `Mounting`: the pieces move out of a Drop type one step at a time.
635    scope: Option<&'s mut BrokerScope<B, Layers, C, State, Pipeline>>,
636    chain: Option<Chain>,
637}
638
639impl<'s, B, Layers, C, State, Pipeline, Chain>
640    MountingSlots<'s, B, Layers, C, State, Pipeline, Chain>
641where
642    B: Broker + 'static,
643{
644    pub(super) fn new(
645        chain: Chain,
646        scope: &'s mut BrokerScope<B, Layers, C, State, Pipeline>,
647    ) -> Self {
648        Self {
649            scope: Some(scope),
650            chain: Some(chain),
651        }
652    }
653
654    /// The chain and the scope, moved out without running the terminal.
655    ///
656    /// # Panics
657    ///
658    /// Never in practice: both stay present until the terminal or a step takes them.
659    fn take(mut self) -> (Chain, &'s mut BrokerScope<B, Layers, C, State, Pipeline>) {
660        let chain = self
661            .chain
662            .take()
663            .expect("the guard holds its chain until the terminal or a step takes it");
664        let scope = self
665            .scope
666            .take()
667            .expect("the guard holds its scope until the terminal or a step takes it");
668        (chain, scope)
669    }
670
671    /// Rebuilds the guard over a stepped chain: how every forwarded step reaches the chain.
672    ///
673    /// # Panics
674    ///
675    /// Never in practice: see [`take`](Self::take).
676    fn map_chain<NewChain>(
677        self,
678        f: impl FnOnce(Chain) -> NewChain,
679    ) -> MountingSlots<'s, B, Layers, C, State, Pipeline, NewChain> {
680        let (chain, scope) = self.take();
681        MountingSlots::new(f(chain), scope)
682    }
683
684    /// Commits the registration. Exists only once every [`Out`](crate::runtime::Out) slot is
685    /// bound: a chain that still has a `MissingSlot<..>` in its attachment fails to compile here,
686    /// naming the slot.
687    ///
688    /// # Panics
689    ///
690    /// Never in practice: the internal expects guard invariants that hold until this terminal
691    /// consumes them.
692    pub fn build(self)
693    where
694        Chain: ScopeCommit<B, Layers, C, State, Pipeline>,
695    {
696        let (chain, scope) = self.take();
697        chain.commit_into(scope);
698    }
699}
700
701/// The guard over a stepped chain: what each forwarded step of [`MountingSlots`] returns.
702type SteppedSlots<'s, B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last> =
703    MountingSlots<'s, B, Layers, C, State, Pipeline, RouterWith<Mount, R, Def, Attach, Last>>;
704
705impl<'s, B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last>
706    MountingSlots<'s, B, Layers, C, State, Pipeline, RouterWith<Mount, R, Def, Attach, Last>>
707where
708    B: Broker + 'static,
709{
710    /// See [`RouterWith::out`]: names the publish policy of one position, the reply's
711    /// ([`Reply`](crate::runtime::Reply)), one slot's, or the deferred retry's
712    /// ([`Retry`](crate::runtime::Retry)).
713    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
714    pub fn out<M, Policy, Index>(
715        self,
716        marker: M,
717        policy: Policy,
718    ) -> SteppedSlots<
719        's,
720        B,
721        Layers,
722        C,
723        State,
724        Pipeline,
725        Mount,
726        R,
727        Def,
728        <M as OutPosition<Mount, R::Broker, Attach, Policy, Index>>::Out,
729        Index,
730    >
731    where
732        R: RouterBroker,
733        M: OutPosition<Mount, R::Broker, Attach, Policy, Index>,
734    {
735        self.map_chain(|chain| chain.out(marker, policy))
736    }
737
738    /// See [`RouterWith::out_reply`].
739    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
740    pub fn out_reply<Policy>(
741        self,
742        policy: Policy,
743    ) -> SteppedSlots<
744        's,
745        B,
746        Layers,
747        C,
748        State,
749        Pipeline,
750        Mount,
751        R,
752        Def,
753        <Reply as OutPosition<Mount, R::Broker, Attach, Policy, ReplyLast>>::Out,
754        ReplyLast,
755    >
756    where
757        R: RouterBroker,
758        Reply: OutPosition<Mount, R::Broker, Attach, Policy, ReplyLast>,
759    {
760        self.out(Reply, policy)
761    }
762
763    /// See [`RouterWith::out_retry`].
764    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
765    pub fn out_retry<Policy, Index>(
766        self,
767        policy: Policy,
768    ) -> SteppedSlots<
769        's,
770        B,
771        Layers,
772        C,
773        State,
774        Pipeline,
775        Mount,
776        R,
777        Def,
778        <Retry as OutPosition<Mount, R::Broker, Attach, Policy, Index>>::Out,
779        Index,
780    >
781    where
782        R: RouterBroker,
783        Retry: OutPosition<Mount, R::Broker, Attach, Policy, Index>,
784    {
785        self.out(Retry, policy)
786    }
787
788    /// See [`RouterWith::max_attempts`].
789    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
790    pub fn max_attempts(
791        self,
792        attempts: NonZeroU32,
793    ) -> SteppedSlots<
794        's,
795        B,
796        Layers,
797        C,
798        State,
799        Pipeline,
800        Mount,
801        R,
802        Def,
803        <Attach as DeclareCap>::Out,
804        Last,
805    >
806    where
807        Attach: DeclareCap<Step: CapOpen>,
808    {
809        self.map_chain(|chain| chain.max_attempts(attempts))
810    }
811
812    /// See [`RouterWith::dead_letter`].
813    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
814    pub fn dead_letter(
815        self,
816        destination: impl Into<Cow<'static, str>>,
817    ) -> SteppedSlots<
818        's,
819        B,
820        Layers,
821        C,
822        State,
823        Pipeline,
824        Mount,
825        R,
826        Def,
827        <Attach as DeclareDeadLetter>::Out,
828        Last,
829    >
830    where
831        Attach: DeclareDeadLetter<Step: DeadLetterOpen>,
832    {
833        self.map_chain(|chain| chain.dead_letter(destination))
834    }
835
836    /// See [`RouterWith::to`].
837    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
838    pub fn to(
839        self,
840        destination: impl Into<Cow<'static, str>>,
841    ) -> SteppedSlots<
842        's,
843        B,
844        Layers,
845        C,
846        State,
847        Pipeline,
848        Mount,
849        R,
850        Def,
851        <Attach as DestinationLast<Last>>::Out,
852        Last,
853    >
854    where
855        Attach: DestinationLast<Last, Step: DestinationOpen>,
856    {
857        self.map_chain(|chain| chain.to(destination))
858    }
859
860    /// See [`RouterWith::codec`].
861    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
862    pub fn codec<Cd>(
863        self,
864        codec: Cd,
865    ) -> SteppedSlots<
866        's,
867        B,
868        Layers,
869        C,
870        State,
871        Pipeline,
872        Mount,
873        R,
874        Def,
875        <Attach as CodecLast<Cd, Last>>::Out,
876        Last,
877    >
878    where
879        Attach: CodecLast<Cd, Last, Step: NamedStep>,
880    {
881        self.map_chain(|chain| chain.codec(codec))
882    }
883
884    /// See [`RouterWith::transform`].
885    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
886    pub fn transform<N>(
887        self,
888        transform: N,
889    ) -> SteppedSlots<
890        's,
891        B,
892        Layers,
893        C,
894        State,
895        Pipeline,
896        Mount,
897        R,
898        Def,
899        <Attach as TransformLast<N, Last>>::Out,
900        Last,
901    >
902    where
903        R: RouterBroker<Broker = B>,
904        Attach: TransformLast<N, Last, Step: NamedStep> + AdmitsAt<N, Last, Mount, Def, B>,
905    {
906        self.map_chain(|chain| chain.transform(transform))
907    }
908
909    /// See [`RouterWith::batch_transform`].
910    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
911    pub fn batch_transform<N>(
912        self,
913        transform: N,
914    ) -> SteppedSlots<
915        's,
916        B,
917        Layers,
918        C,
919        State,
920        Pipeline,
921        Mount,
922        R,
923        Def,
924        <Attach as BatchTransformLast<N, Last>>::Out,
925        Last,
926    >
927    where
928        Attach: BatchTransformLast<N, Last, Step: ReplyStep>,
929    {
930        self.map_chain(|chain| chain.batch_transform(transform))
931    }
932
933    /// See [`RouterWith::transactional`].
934    #[allow(clippy::type_complexity)] // the chain's own state; an alias would hide the position
935    pub fn transactional(
936        self,
937    ) -> SteppedSlots<
938        's,
939        B,
940        Layers,
941        C,
942        State,
943        Pipeline,
944        Mount,
945        R,
946        Def,
947        <Attach as TransactionalLast<Last>>::Out,
948        Last,
949    >
950    where
951        Attach: TransactionalLast<Last, Step: ReplyStep>,
952    {
953        self.map_chain(RouterWith::transactional)
954    }
955}
956
957// See `Mounting`'s own impl: the settings hook is the chain's, forwarded.
958impl<B, Layers, C, State, Pipeline, Mount, R, Def, Attach, Last> MapPublisher
959    for MountingSlots<'_, B, Layers, C, State, Pipeline, RouterWith<Mount, R, Def, Attach, Last>>
960where
961    B: Broker + 'static,
962    Attach: MapPolicyLast<Last, Step: NamedStep>,
963{
964    type Policy = Attach::Policy;
965
966    fn map_publisher(self, f: impl FnOnce(Self::Policy) -> Self::Policy) -> Self {
967        self.map_chain(|chain| chain.map_publisher(f))
968    }
969}
970
971impl<B, Layers, C, State, Pipeline, Chain> fmt::Debug
972    for MountingSlots<'_, B, Layers, C, State, Pipeline, Chain>
973where
974    B: Broker + 'static,
975{
976    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
977        f.debug_struct("MountingSlots").finish_non_exhaustive()
978    }
979}
980
981impl<B, Layers, C, State, Pipeline, Chain> Drop
982    for MountingSlots<'_, B, Layers, C, State, Pipeline, Chain>
983where
984    B: Broker + 'static,
985{
986    fn drop(&mut self) {
987        // The `must_use` warning is the diagnostic that belongs at the mount site; this is the
988        // backstop for a mount that silenced or ignored it, like the on_startup ordering check.
989        // A deliberately discarded incomplete registration must not vanish quietly - the handler
990        // would never consume.
991        assert!(
992            self.chain.is_none(),
993            "a handler with Out slots was included but never mounted: bind each slot with \
994             .out(marker, policy) and finish the chain with .build()"
995        );
996    }
997}