Skip to main content

nmbrs_runtime/wrappers/
mod.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Composable op dispenser wrappers.
5//!
6//! Each wrapper lives in its own submodule under this directory, and the
7//! **module file is named for the YAML field that triggers it** — `poll.rs`
8//! for `poll:`, `rate.rs` for `rate:`, and so on. The mod.rs holds shared
9//! traits/types plus the wrappers still awaiting extraction; submodules are
10//! re-exported here so the existing `crate::wrappers::<Name>` import paths
11//! keep working.
12//!
13//! A wrapper declares itself to the registry with a [`crate::wrapper_registry::WrapperName`] and the
14//! `owned_fields` that select it (see `crate::wrapper_registry`). At op
15//! construction the resolver composes the ones whose fields are present into a
16//! cascade around the adapter's dispenser, innermost-first, subject to each
17//! wrapper's `requires_inner` / `forbids_outer` constraints.
18//!
19//! # The wrappers
20//!
21//! Ordered innermost-to-outermost, which is also the order a cycle passes
22//! through them on its way to the adapter and back:
23//!
24//! | YAML | module | what it does |
25//! |------|--------|--------------|
26//! | `tries:` | [`tries`] | Attempt loop. The absolute innermost layer: wraps the raw adapter dispenser, owns the retry budget and the per-attempt panic catch. Absent `tries:`, it is not constructed at all and the op runs once. |
27//! | `traverse:` | [`traverse`] | Result traversal. Counts result elements and bytes, walks declared capture points, and writes extracted values onto the per-fiber op-template kernel. On by default and installed even when nothing requires it; `result:` / `metrics:` additionally declare it `requires_inner`, which the composition resolver enforces at init. Like `result:` the block CUSTOMISES rather than selects: `path:` re-roots the body with a base JSON Pointer so captures stop repeating an envelope prefix, and `on_missing: ignore\|warn\|error` decides what an unresolved capture means. |
28//! | `delay:` | [`delay`] | Sleeps before and/or after the inner op, reading the interval per cycle through the pull plan. `u64` is nanoseconds, `f64` is milliseconds. |
29//! | `poll:` | [`poll`] | Re-executes the inner op until its ROW COUNT — optionally projected through a JSON Pointer — lands in `[min_rows, max_rows]`, or the timeout fires. The await primitive for backend state such as a compaction drain. Note the asymmetry with PHASE-level `poll:` ([`nmbrs_workload::model::PhasePollSpec`]), whose `until:` is a polydat boolean expression re-evaluated per iteration; the op-level wrapper has no such condition form and can only test a row count. |
30//! | `if:` | `r#if` | Conditional execution. A falsy per-cycle value skips the op entirely — no inner execution, no adapter call — and counts a skip. |
31//! | `while:` | `r#while` | Loops the inner op while a per-cycle predicate holds. |
32//! | `result:` | [`result`] | Exposes op-result fields, plus the magic externs `body` / `count` / `ok`, as Polydat wires on the op-template kernel. Always installed; the optional `result:` block OVERRIDES which fields are exposed rather than enabling the wrapper — which is why it is not in the registry's `owned_fields`. |
33//! | `metrics:` | [`metrics`] | Records synthetic metrics. After the adapter returns, pulls each declared metric's value through the op-template kernel and writes it to the instrument. Also resolves `cell:` placement, materialising one dimensional cell per coordinate value. |
34//! | `rate:` | [`rate`] | Per-op rate limiter, independent of the activity-level limiter and of every other op's. Each instance owns its own limiter and acquires on every dispatch. |
35//! | `fields:` | [`fields`] | Prints the rendered op text per cycle — the "what did this op actually send" surface. |
36//! | `readout:` | [`readout`] | Opt-in per-op status visibility: reports an op-level lifecycle (start / complete / fail) so a long op appears as its own timed leaf rather than silence. |
37//! | `memo:` | [`memo`] | Publishes a short human-visible string to the activity's memo slot before and/or after the op — the `[[ … ]]` line. |
38//! | `gutter:` | [`gutter`] | Publishes the phase's contextual left-gutter cell from polydat templates. Distinct from `memo`, which owns the memo line. |
39//! | `errors:` | [`errors`] | Error routing. The outermost OP-level wrapper: it sees the one terminal outcome of the whole stack and applies the op's error policy (warn / count / stop). |
40//! | `dryrun:` | [`dryrun`] | Short-circuit: returns an empty result without calling the adapter. Its field is spelled like the others, but is normally INJECTED by the runner onto every op template from the CLI `dryrun=<mode>` param rather than written in a workload. It `forbids_outer` on `memo` and `gutter`, which is why those two hold explicit slots in the default order. |
41//! | `interval:` / `repeat:` | [`interval`] | The one PHASE-level wrapper. Re-runs a whole phase, dwelling `interval` between runs and bounded by `repeat`. Not an `OpDispenser` — it wraps the phase seam, so there is no dispenser type to re-export.
42//!
43//! Composition order is not alphabetical or declaration order: it comes from
44//! `wrapper_resolver::DEFAULT_ORDER` plus the constraint graph, and a workload
45//! may override it with `wrappers:` / `--wrap-default-order`. Two placements
46//! are load-bearing rather than cosmetic — `tries` is hand-placed innermost so
47//! the plan matches runtime truth, and `memo`/`gutter` must sort inside
48//! `dryrun` or resolution fails with `ForbiddenOuter`.
49
50// Per-wrapper modules. As each wrapper migrates out of this
51// file into its own module, add a `pub mod` line + re-export.
52// SRD-82/92 — the first PHASE-level wrapper. Unlike the rest it is not an
53// `OpDispenser`: a phase layer wraps the phase seam (`PhaseShell::run`), so
54// there is no dispenser type to re-export.
55// The one predicate mechanism, shared by `if:` / `while:` / `poll:`.
56pub mod condition;
57pub mod dryrun;
58pub mod interval;
59pub use dryrun::DryRunWrapper;
60pub mod memo;
61pub use memo::MemoDispenser;
62pub mod gutter;
63pub use gutter::GutterDispenser;
64pub mod delay;
65pub use delay::DelayDispenser;
66pub mod r#if;
67pub use r#if::ConditionalDispenser;
68pub mod r#while;
69pub use r#while::WhileWrapper;
70pub mod rate;
71pub use rate::OpRateWrapper;
72pub mod fields;
73pub use fields::FieldsDispenser;
74pub mod poll;
75pub use poll::{PollingDispenser, PollingMetrics};
76pub mod readout;
77pub use readout::ReadoutDispenser;
78pub mod result;
79pub use result::ResultDispenser;
80pub mod metrics;
81pub use metrics::MetricsDispenser;
82pub mod traverse;
83pub use traverse::{TraversalStats, TraversingDispenser};
84pub mod tries;
85pub use tries::TriesDispenser;
86pub mod errors;
87pub use errors::ErrorHandlerDispenser;
88
89// All wrappers now live in their own submodules:
90//   if / throttle.rs / poll / result.rs /
91//   metrics.rs / fields.rs / memo.rs / dryrun / traverse.