Skip to main content

tocat_api/
plugin.rs

1//! The plugin traits and the contexts they are driven through.
2//!
3//! The lifecycle is four calls and two contexts. [`PluginFactory::build`] runs
4//! once per direction per connection with a [`BuildCtx`]: this is where config
5//! is deserialized, side channels are reserved, and anything derived from the
6//! stage's fixed position is cached. [`Plugin::on_bytes`] then runs per chunk
7//! with a [`Ctx`], [`Plugin::on_tick`] runs on a schedule the stage asks for,
8//! and [`Plugin::on_eof`] once at the end: the last chance for a stage holding
9//! buffered bytes to emit them, and where a codec writes its epilogue.
10//!
11//! The split between the two contexts is the point: everything expensive or
12//! fallible belongs to build time, so the per-chunk path is a synchronous call
13//! that either forwards a slice or writes into a buffer.
14//!
15//! A call emits one unit by default, however many times it forwards: the
16//! pieces concatenate, and the host delivers them as one write. A stage that
17//! needs them kept apart says so with [`Ctx::boundary`], which is what turns a
18//! stage that merely accumulates bytes into one that records them.
19
20use std::time::Duration;
21
22use serde::{Deserialize, Serialize, de::DeserializeOwned};
23use serde_json::{Map, Value};
24/// Severity of a record a stage asked the host to log.
25///
26/// The same type a guest writes into its outbox, since a level that crossed
27/// the WebAssembly boundary and a level a native plugin passed to
28/// [`Ctx::log`] are the same thing.
29pub use tocat_wasm_abi::Level as LogLevel;
30pub use tocat_wasm_abi::*;
31
32use crate::{
33    Direction,
34    channel::{ChannelId, ChannelTarget, HostBuilder},
35    error::{PluginError, Result},
36    forgiving::Forgiving,
37};
38
39/// Where the host runs a stage.
40///
41/// `Inline` stages run on the reading task: one synchronous call per chunk, no
42/// channel, no wakeup, and (see [`Ctx::pass_through`]) no copy. `Detached`
43/// buys concurrency with the reader at the cost of one copy and one task hop
44/// per chunk, so it pays off only for stages doing real work per byte:
45/// compression, encryption, parsing, etc.
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "kebab-case")]
48pub enum Execution {
49    #[default]
50    Inline,
51    Detached,
52}
53
54/// Collects the side effects a plugin asks for during one call.
55///
56/// The host implements this over per-channel staging buffers, so a side write
57/// is an `extend_from_slice` and nothing else. Staged bytes are flushed to
58/// their sinks concurrently with the downstream write.
59pub trait EffectSink {
60    fn write(&mut self, channel: ChannelId, bytes: &[u8]);
61    /// `stage` is the emitting stage's display name, so the host can tag the
62    /// line without every plugin having to repeat itself.
63    fn log(&mut self, level: LogLevel, stage: &str, message: &str);
64
65    /// Wait `delay` before reading upstream again.
66    ///
67    /// The one effect that acts on the reader rather than on a side channel. A
68    /// stage cannot sleep: it is synchronous, it runs on the reading task, and
69    /// a guest has no runtime to sleep on. So it asks, and the host holds off
70    /// its next read. Several stages asking on one chunk get the longest of
71    /// the requests, not the sum.
72    ///
73    /// Defaults to doing nothing, so a host that has no reader to hold (a test
74    /// harness, an offline driver) is not obliged to honour it.
75    fn pace(&mut self, delay: Duration) {
76        let _ = delay;
77    }
78
79    /// Stop reading upstream, as if it had just reached end of stream.
80    ///
81    /// Everything already emitted is still written, `on_eof` still cascades,
82    /// and the path closes down its normal way. This is how a stage ends a
83    /// transfer deliberately, which is not a failure and must not be reported
84    /// as one.
85    fn halt(&mut self, stage: &str, reason: &str) {
86        let _ = (stage, reason);
87    }
88}
89
90/// Static description of the path a pipeline instance sits on.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct PipelineMeta {
93    pub direction: Direction,
94    pub source: String,
95    pub sink: String,
96    pub peer: Option<String>,
97}
98
99impl PipelineMeta {
100    pub fn new(direction: Direction, source: impl Into<String>, sink: impl Into<String>) -> Self {
101        Self {
102            direction,
103            source: source.into(),
104            sink: sink.into(),
105            peer: None,
106        }
107    }
108
109    #[must_use]
110    pub fn with_peer(mut self, peer: Option<impl Into<String>>) -> Self {
111        self.peer = peer.map(Into::into);
112        self
113    }
114
115    /// The endpoint bytes are read from on this path.
116    #[must_use]
117    pub fn upstream(&self) -> &str {
118        match self.direction {
119            Direction::SourceToSink => &self.source,
120            Direction::SinkToSource => &self.sink,
121        }
122    }
123
124    /// The endpoint bytes are written to on this path.
125    #[must_use]
126    pub fn downstream(&self) -> &str {
127        match self.direction {
128            Direction::SourceToSink => &self.sink,
129            Direction::SinkToSource => &self.source,
130        }
131    }
132
133    /// `"source -> sink"`, oriented for this path.
134    #[must_use]
135    pub fn label(&self) -> String {
136        format!("{} -> {}", self.upstream(), self.downstream())
137    }
138}
139
140/// Where a stage sits in its pipeline, and what it is called.
141///
142/// `upstream` and `downstream` are the stage's actual neighbours on this path:
143/// the adjacent stages' display names, or an endpoint name at either end. A
144/// `tee` wedged between two other stages therefore describes the hop it is
145/// really watching rather than the endpoints it is nowhere near.
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub struct StageInfo<'a> {
148    /// Position on this path, after direction filtering and mirroring.
149    pub index: usize,
150    pub total: usize,
151    /// The `as = "..."` alias if one was given, otherwise the plugin name,
152    /// suffixed with `#n` when the same name appears more than once.
153    pub name: &'a str,
154    pub upstream: &'a str,
155    pub downstream: &'a str,
156}
157
158impl StageInfo<'_> {
159    /// `"upstream -> downstream"`, oriented for this path.
160    #[must_use]
161    pub fn label(&self) -> String {
162        format!("{} -> {}", self.upstream, self.downstream)
163    }
164
165    #[must_use]
166    pub fn is_first(&self) -> bool {
167        self.index == 0
168    }
169
170    #[must_use]
171    pub fn is_last(&self) -> bool {
172        self.index + 1 == self.total
173    }
174}
175
176/// What one call to a stage produced: the bytes it emitted, how they are
177/// framed, and what it asked the host to do about its own schedule.
178///
179/// One struct rather than four borrows because they are written together and
180/// read together, and because a stage's whole answer is exactly these four
181/// things. The host keeps one per buffer and resets it between calls, so a
182/// steady stream allocates nothing here after the first few chunks.
183#[derive(Debug, Default)]
184pub struct Emission {
185    /// Bytes the stage wrote. Empty when it passed through or emitted nothing.
186    pub(crate) out: Vec<u8>,
187    /// One offset per unit, each the end of its unit. Empty means unframed,
188    /// which is one unit covering everything.
189    pub(crate) bounds: Vec<usize>,
190    pub(crate) emit: Emit,
191    /// Whether the stage asked for its tick schedule to be restarted.
192    pub(crate) rearm: bool,
193}
194
195impl Emission {
196    #[must_use]
197    pub fn new() -> Self {
198        Self::default()
199    }
200
201    /// Ready for a fresh call, keeping the allocations.
202    pub fn reset(&mut self) {
203        self.out.clear();
204        self.bounds.clear();
205        self.emit = Emit::Pending;
206        self.rearm = false;
207    }
208
209    /// Ready for the next unit of a framed call.
210    ///
211    /// What has been emitted so far stays, because units concatenate into one
212    /// buffer, but the decision is per unit. A rearm request is not cleared
213    /// either: it is about the stage, not about the unit.
214    pub(crate) fn next_unit(&mut self) {
215        self.emit = Emit::Pending;
216    }
217
218    /// Close the unit left open, so `bounds` always ends where `out` does.
219    ///
220    /// Does nothing when no bytes have arrived since the last boundary, so a
221    /// stage cannot declare an empty unit, and nothing at all on an empty
222    /// emission. Note that this frames an emission that had declared no
223    /// framing, so the host calls it only where that is what it means.
224    pub(crate) fn close(&mut self) {
225        if self.bounds.last().copied().unwrap_or(0) < self.out.len() {
226            self.bounds.push(self.out.len());
227        }
228    }
229
230    /// Everything the stage emitted, concatenated.
231    #[must_use]
232    pub fn bytes(&self) -> &[u8] {
233        &self.out
234    }
235
236    /// The framing of [`bytes`](Self::bytes). Empty means one unit.
237    #[must_use]
238    pub fn bounds(&self) -> &[usize] {
239        &self.bounds
240    }
241
242    /// What the stage did with what it was given.
243    #[must_use]
244    pub fn emit(&self) -> Emit {
245        self.emit
246    }
247
248    /// Whether the stage asked for its schedule to be restarted. See
249    /// [`Ctx::rearm`].
250    #[must_use]
251    pub fn rearm_requested(&self) -> bool {
252        self.rearm
253    }
254}
255
256/// Handed to a plugin for each chunk.
257///
258/// A stage must say what happens to the chunk:
259/// [`pass_through`](Self::pass_through) forwards it untouched,
260/// [`forward`](Self::forward) emits different bytes, and doing neither drops
261/// it. Passthrough is the fast path and costs nothing (the next stage receives
262/// the same slice).
263///
264/// Several calls to `forward` in one turn emit one unit, not several: the
265/// bytes concatenate and are delivered together. [`boundary`](Self::boundary)
266/// is how a stage says otherwise.
267pub struct Ctx<'a> {
268    meta: &'a PipelineMeta,
269    stage: &'a str,
270    input: &'a [u8],
271    emission: &'a mut Emission,
272    sink: &'a mut dyn EffectSink,
273}
274
275impl<'a> Ctx<'a> {
276    pub fn new(
277        meta: &'a PipelineMeta,
278        stage: &'a str,
279        input: &'a [u8],
280        emission: &'a mut Emission,
281        sink: &'a mut dyn EffectSink,
282    ) -> Self {
283        Self {
284            meta,
285            stage,
286            input,
287            emission,
288            sink,
289        }
290    }
291
292    /// This stage's display name, as it appears in logs.
293    #[must_use]
294    pub fn stage(&self) -> &str {
295        self.stage
296    }
297
298    #[must_use]
299    pub fn meta(&self) -> &PipelineMeta {
300        self.meta
301    }
302
303    #[must_use]
304    pub fn direction(&self) -> Direction {
305        self.meta.direction
306    }
307
308    /// The chunk this call was given. Empty during [`Plugin::on_eof`].
309    #[must_use]
310    pub fn input(&self) -> &[u8] {
311        self.input
312    }
313
314    /// Forward the input unchanged, without copying it.
315    pub fn pass_through(&mut self) {
316        match self.emission.emit {
317            Emit::Pending => self.emission.emit = Emit::Passthrough,
318            Emit::Passthrough => {}
319            // Something was emitted already, so passthrough has to materialise.
320            Emit::Buffered => {
321                let input = self.input;
322                self.emission.out.extend_from_slice(input);
323            }
324        }
325    }
326
327    /// Emit `bytes` downstream. Performs a copy. Use
328    /// [`pass_through`](Self::pass_through) when the bytes are the input.
329    ///
330    /// Appends: calling this twice emits both, in order, as one unit. Call
331    /// [`boundary`](Self::boundary) between them to emit two.
332    pub fn forward(&mut self, bytes: &[u8]) {
333        if self.emission.emit == Emit::Passthrough {
334            let input = self.input;
335            self.emission.out.extend_from_slice(input);
336        }
337
338        self.emission.emit = Emit::Buffered;
339        self.emission.out.extend_from_slice(bytes);
340    }
341
342    /// Explicitly swallow the chunk. Emitting nothing does the same thing; this
343    /// exists so a filter can state the intent.
344    pub fn drop_chunk(&mut self) {
345        if self.emission.emit == Emit::Passthrough {
346            self.emission.emit = Emit::Pending;
347        }
348    }
349
350    /// End the current unit. What was forwarded since the last boundary is
351    /// delivered on its own: one write at a byte sink, one message at a
352    /// datagram sink, one parcel across a detached boundary, and one
353    /// [`on_bytes`](Plugin::on_bytes) call at every stage below.
354    ///
355    /// Only worth calling when those splits are the point, as with a stage
356    /// cutting a stream into fixed-size records. Framing is not free: each
357    /// stage below is then called once per unit rather than once per chunk, so
358    /// a stage that emits many small units is asking the rest of the segment
359    /// to run many times. A stage that only rewrites bytes should leave the
360    /// framing it was given alone and say nothing.
361    ///
362    /// The trailing unit does not need one: whatever is forwarded after the
363    /// last boundary is closed automatically, so no bytes can be lost by
364    /// forgetting.
365    ///
366    /// Ignored when nothing has been forwarded since the last call, so a stage
367    /// cannot emit an empty unit by accident.
368    pub fn boundary(&mut self) {
369        if self.emission.emit == Emit::Passthrough {
370            let input = self.input;
371            self.emission.out.extend_from_slice(input);
372            self.emission.emit = Emit::Buffered;
373        }
374
375        self.emission.close();
376    }
377
378    /// Restart this stage's tick schedule: the next
379    /// [`on_tick`](Plugin::on_tick) falls a full interval from now rather than
380    /// wherever the existing cadence happens to land.
381    ///
382    /// A stage cannot read a clock, so it cannot measure how long it has been
383    /// holding something. What it can do is say when the waiting started, and
384    /// this is how. A stage that begins accumulating calls this, and its next
385    /// tick then means "an interval since you asked" rather than "an interval
386    /// since some earlier moment you know nothing about".
387    ///
388    /// Without it, [`tick_interval`](Plugin::tick_interval) is a cadence
389    /// rather than a delay: a tick that came due while bytes were flowing
390    /// fires at the next opportunity, which can be immediately after the bytes
391    /// it is about arrived.
392    ///
393    /// Cheap to call, and harmless to call often: it sets a flag the host
394    /// reads once at the end of the call. Ignored for a stage that asked for
395    /// no ticks.
396    pub fn rearm(&mut self) {
397        self.emission.rearm = true;
398    }
399
400    /// Stage bytes for a side channel obtained from [`BuildCtx::open_channel`].
401    pub fn side_write(&mut self, channel: ChannelId, bytes: &[u8]) {
402        self.sink.write(channel, bytes);
403    }
404
405    pub fn log(&mut self, level: LogLevel, message: &str) {
406        self.sink.log(level, self.stage, message);
407    }
408
409    /// Ask the host to wait `delay` before reading upstream again.
410    ///
411    /// Applied after this call returns and after whatever was emitted has been
412    /// written, so the bytes in hand are never held hostage by the wait. On a
413    /// socket this is real backpressure: the read stops, the receive buffer
414    /// fills, the window closes and the peer slows down. Nothing is buffered
415    /// on this side.
416    pub fn pace(&mut self, delay: Duration) {
417        self.sink.pace(delay);
418    }
419
420    /// Ask the host to stop reading upstream, as if it had reached end of
421    /// stream. `reason` is logged against this stage.
422    pub fn halt(&mut self, reason: &str) {
423        self.sink.halt(self.stage, reason);
424    }
425}
426
427/// Handed to a [`PluginFactory`] while constructing one instance.
428pub struct BuildCtx<'a> {
429    name: &'a str,
430    config: &'a Map<String, Value>,
431    meta: &'a PipelineMeta,
432    stage: StageInfo<'a>,
433    host: &'a mut dyn HostBuilder,
434}
435
436impl<'a> BuildCtx<'a> {
437    pub fn new(
438        name: &'a str,
439        config: &'a Map<String, Value>,
440        meta: &'a PipelineMeta,
441        stage: StageInfo<'a>,
442        host: &'a mut dyn HostBuilder,
443    ) -> Self {
444        Self {
445            name,
446            config,
447            meta,
448            stage,
449            host,
450        }
451    }
452
453    /// Where this instance sits in the pipeline. Cache anything derived from
454    /// it as the position cannot change after construction.
455    #[must_use]
456    pub fn stage(&self) -> StageInfo<'a> {
457        self.stage
458    }
459
460    #[must_use]
461    pub fn name(&self) -> &str {
462        self.name
463    }
464
465    #[must_use]
466    pub fn meta(&self) -> &PipelineMeta {
467        self.meta
468    }
469
470    #[must_use]
471    pub fn direction(&self) -> Direction {
472        self.meta.direction
473    }
474
475    #[must_use]
476    pub fn raw_config(&self) -> &Map<String, Value> {
477        self.config
478    }
479
480    /// Deserialize the entry's options into the plugin's own config type.
481    ///
482    /// Through [`Forgiving`], so option keys and enum values are matched the
483    /// way every other identifier in tocat is: case-insensitively, with dashes
484    /// and underscores treated as noise. A plugin declares its config exactly
485    /// as it would otherwise, including `deny_unknown_fields`, and needs to
486    /// know nothing about this.
487    pub fn config<T: DeserializeOwned>(&self) -> Result<T> {
488        T::deserialize(Forgiving(Value::Object(self.config.clone())))
489            .map_err(|e| PluginError::config(self.name, e))
490    }
491
492    /// Reserve a side channel. Equal targets share one handle process-wide.
493    pub fn open_channel(&mut self, target: ChannelTarget) -> Result<ChannelId> {
494        self.host.open_channel(target)
495    }
496}
497
498/// What a factory produced.
499///
500/// Most stages are [`Plugin`]s the host calls per chunk. A few cannot be: a
501/// subprocess decides nothing synchronously, may emit nothing for the chunk it
502/// was given, and may emit bytes belonging to three chunks ago. Rather than
503/// bend [`Plugin`] into something a subprocess could satisfy (and lose the
504/// property that makes it portable to WASM) such a stage is *described* here
505/// and *run* by the host.
506///
507/// A WASM guest can only ever produce [`Stage::Filter`]; spawning is a host
508/// capability by construction.
509pub enum Stage {
510    Filter(Box<dyn Plugin>),
511    External(ExternalStage),
512}
513
514impl Stage {
515    /// Build a filter stage from a plugin
516    pub fn filter(plugin: impl Plugin + 'static) -> Self {
517        Self::Filter(Box::new(plugin))
518    }
519}
520
521impl From<Box<dyn Plugin>> for Stage {
522    fn from(plugin: Box<dyn Plugin>) -> Self {
523        Self::Filter(plugin)
524    }
525}
526
527/// A subprocess to run as a stage, with the relay's bytes on its stdin and its
528/// stdout continuing downstream.
529///
530/// Always its own segment: it cannot share a task with inline stages, and
531/// `detach = false` on one is a contradiction.
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub struct ExternalStage {
534    /// Program and arguments, or a single shell command when `shell` is set.
535    pub argv: Vec<String>,
536    pub shell: bool,
537    pub stderr: StderrMode,
538    /// Display name, for logs and for attributing the child's stderr.
539    pub name: String,
540}
541
542/// What to do with a child's stderr.
543#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
544#[serde(rename_all = "kebab-case")]
545pub enum StderrMode {
546    /// Forward to the relay's own stderr. Interleaves with dumps and logs.
547    Inherit,
548    /// Capture and re-emit as warnings tagged with the stage name.
549    #[default]
550    Log,
551    Null,
552}
553
554/// One stage of a pipeline. Instances are per-direction and per-connection.
555///
556/// Synchronous on purpose: it is the only shape that maps onto a WASM guest
557/// call, and it keeps the per-chunk cost at a function call rather than a
558/// future poll. Anything that must await belongs on the effect side, where the
559/// host performs it off the critical path.
560pub trait Plugin: Send {
561    /// The name of the plugin
562    fn name(&self) -> &str;
563
564    /// A chunk arrived from upstream. `input` is the same slice as
565    /// [`Ctx::input`]; it is passed separately because it is the hot argument.
566    ///
567    /// One call is one unit. Where a stage above declared framing with
568    /// [`Ctx::boundary`] that is one call per unit rather than one per chunk,
569    /// so a stage never has to unpick two of them from a single slice.
570    fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()>;
571
572    /// Upstream reached EOF. Last chance to emit buffered bytes.
573    fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
574        let _ = ctx;
575        Ok(())
576    }
577
578    /// How often this stage wants [`on_tick`](Plugin::on_tick) called, or
579    /// `None` (the default) for never.
580    ///
581    /// Read once, at the end of construction, so it must not depend on
582    /// anything that changes later. A stage whose interval is configurable
583    /// reads its config in [`PluginFactory::build`] and answers from that.
584    ///
585    /// The host owns the clock. A guest cannot read one (a WASM module has no
586    /// way to reach the host's time) which is why this is a period the stage
587    /// *asks for* rather than a timestamp it checks. The cost falls on the
588    /// relay: one timer per direction per connection for any pipeline
589    /// containing a ticking stage, so a stage asking for milliseconds is
590    /// asking every forked connection to wake up that often.
591    fn tick_interval(&self) -> Option<Duration> {
592        None
593    }
594
595    /// The stage's schedule came due.
596    ///
597    /// Called from the same task, and under the same rules, as
598    /// [`on_bytes`](Plugin::on_bytes), it just arrives without any. This is
599    /// how a stage does anything that time rather than traffic should drive:
600    /// report a measurement, release bytes it has been holding back, emit a
601    /// keepalive. Without it a stalled stream and a finished one are
602    /// indistinguishable from inside a plugin.
603    ///
604    /// [`Ctx::input`] is empty, so there is nothing to pass through; anything
605    /// emitted here is emitted with [`Ctx::forward`] and continues downstream
606    /// through the stages *below* this one, in the same way
607    /// [`on_eof`](Plugin::on_eof) cascades. Emitting nothing is the common
608    /// case and costs nothing.
609    ///
610    /// A stage that emits from here is fabricating a message boundary on a
611    /// datagram path (the bytes belong to no datagram the peer sent) so it
612    /// should report [`Boundaries::Fuse`] from
613    /// [`boundaries`](Plugin::boundaries). A stage that only observes need
614    /// not.
615    ///
616    /// What is emitted here is one unit unless [`Ctx::boundary`] says
617    /// otherwise, exactly as in [`on_bytes`](Plugin::on_bytes).
618    ///
619    /// Ticks run for the life of the pipeline and stop at end of stream, so
620    /// they arrive whether or not anything is moving, which is the point, and
621    /// is what a keepalive needs. A stage that has nothing to say until the
622    /// first chunk has arrived is expected to keep that state itself.
623    fn on_tick(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
624        let _ = ctx;
625        Ok(())
626    }
627
628    /// What this stage does to the message boundaries passing through it.
629    ///
630    /// On a byte stream a chunk is an arbitrary slice: a stage may buffer,
631    /// split or coalesce freely, and the host is free to do the same. On a
632    /// datagram path the chunk *is* the message: one `on_bytes` call per
633    /// datagram, and whatever it emits is sent as exactly one datagram. A
634    /// stage that buffers across calls, or emits two messages' worth from one,
635    /// silently corrupts the protocol unless it says so here.
636    ///
637    /// The four answers, in the order a stage usually wants them:
638    ///
639    /// - [`Preserve`](Boundaries::Preserve): one unit in, one unit out. Every
640    ///   observer, and every codec that rewrites a message in place.
641    /// - [`Fuse`](Boundaries::Fuse): the units are gone below this stage.
642    ///   Anything that buffers across calls, splits, coalesces, or emits from a
643    ///   tick.
644    /// - [`Seal`](Boundaries::Seal): as `Preserve`, and the boundary is also
645    ///   written into the payload, so it outlives a stage below that fuses.
646    ///   `frame` and nothing else.
647    /// - [`Split`](Boundaries::Split): the units below are read out of the
648    ///   bytes rather than inherited, so the ones from above do not survive.
649    ///   `unframe` and nothing else.
650    ///
651    /// Defaults to `Fuse` because that is the answer that claims nothing,
652    /// which is the safe one for a stage that has not thought about it,
653    /// including any plugin loaded from outside this binary.
654    ///
655    /// Declaring the truth matters more than declaring safety. `block` fuses
656    /// and says so, and it is still the right stage to reach for when one
657    /// datagram per 1400 bytes is exactly what was wanted: the host warns and
658    /// relays anyway.
659    fn boundaries(&self) -> Boundaries {
660        Boundaries::Fuse
661    }
662
663    /// What this stage needs of the path it was placed on.
664    ///
665    /// Unlike [`boundaries`](Plugin::boundaries), which the host only warns
666    /// about, an unmet requirement is a build error: a stage saying this
667    /// cannot do its job at all where it was put.
668    ///
669    /// [`Upstream`](Needs::Upstream) means every call must carry one whole
670    /// message, so boundaries have to arrive from a datagram endpoint or from
671    /// an `unframe`. [`Downstream`](Needs::Downstream) means the units this
672    /// stage emits have to reach a datagram endpoint or a `frame`, or what it
673    /// wrote cannot be read back. The two are separate because the stages
674    /// that want them want opposite ones: a stage that seals a message and
675    /// appends a tag makes its own boundaries and needs them to survive
676    /// downwards, while the stage that verifies and strips that tag needs
677    /// whole messages from above and does not care what happens below it.
678    ///
679    /// Read once, after `build`, alongside `boundaries`. Neither is consulted
680    /// on the per-chunk path.
681    fn needs(&self) -> Needs {
682        Needs::Nothing
683    }
684}
685
686/// Constructs [`Plugin`] instances from a declared entry.
687pub trait PluginFactory: Send + Sync + 'static {
688    fn name(&self) -> &str;
689
690    fn description(&self) -> &str {
691        ""
692    }
693
694    /// Default placement. Overridable per entry with `detach = true|false`.
695    fn execution(&self) -> Execution {
696        Execution::Inline
697    }
698
699    fn build(&self, ctx: &mut BuildCtx<'_>) -> Result<Stage>;
700}