Skip to main content

tatara_process/
anyhow_flatten.rs

1//! Substrate primitive over `anyhow::Result<T>` — the ONE substrate
2//! owner of the `.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))` wrap-
3//! shape every reconciler consumer restates by hand at the
4//! anyhow-returning → anyhow-returning display-prefix boundary. Peer
5//! of [`crate::kube_error::KubeResultExt`] and
6//! [`crate::hostname::HostnameResultExt`] on the flatten-wrap axis; the
7//! three traits partition the display-prefix wrap space by underlying
8//! error type — [`kube::Error`] on the K8s peer, [`crate::hostname::
9//! HostnameError`] on the hostname peer, [`anyhow::Error`] on this
10//! peer (the "already an anyhow error, we just want a static or
11//! `format!`-composed slug in front of its `Display` output" case).
12//!
13//! Pre-lift the shape was hand-authored at FIVE `phase_machine.rs`
14//! sites in `tatara-reconciler` past the ★★ PRIME-DIRECTIVE ≥ 2
15//! duplication threshold, each restating the SAME closure — capture
16//! an [`anyhow::Error`] returned by a downstream primitive
17//! ([`crate::boundary::check_depends_on`],
18//! [`crate::render::render_routing`],
19//! [`crate::boundary::evaluate`],
20//! [`crate::render::render_export_jobs`],
21//! [`crate::ssapply::apply_owned`]), prepend a context slug identifying
22//! which primitive faulted, delegate the tail to [`anyhow::Error`]'s
23//! `Display` impl via the `{e}` slot — differing only in the context
24//! slug prefix each callsite stamped.
25//!
26//! Post-lift each callsite reads
27//! `<anyhow-returning-call>().await.flatten_ctx("<slug>")?` (or the
28//! owned-`String` peer for consumers that compose the slug via
29//! `format!`) and the wrap-shape lives at ONE substrate owner here.
30//! The composed [`anyhow::Error`]'s `Display` is byte-identical to
31//! the pre-lift chain (`format!("{ctx}: {e}")`, threading the source
32//! [`anyhow::Error`]'s own `Display` verbatim into the `{e}` slot),
33//! so operator-facing log output and any error-chain greps still
34//! match bytewise. A regression that drifts the separator, swaps
35//! the two slots, or wraps the source [`anyhow::Error`] with a
36//! chain-form `source` (which would change `Display` output on the
37//! `err` slot when downstream consumers format with `{e}` rather
38//! than `{e:#}`) surfaces at the tests below rather than as silent
39//! operator-facing drift across the five pre-lift consumers.
40//!
41//! ### Naming — `flatten_ctx`, not `anyhow::Context::context`
42//!
43//! Same discipline as [`crate::kube_error::KubeResultExt::kube_ctx`]
44//! and [`crate::hostname::HostnameResultExt::hostname_ctx`] — but with
45//! a more urgent motivation, because THIS trait operates on the SAME
46//! `anyhow::Result<T>` type [`anyhow::Context::context`] takes.
47//! `anyhow::Context::context` wraps the source in a chain (so
48//! `Display` emits ONLY the context slug and callers reach the source
49//! [`anyhow::Error`] via [`std::error::Error::source`] traversal, or
50//! by formatting with the alternate `{:#}` specifier that walks the
51//! chain), while this trait's `flatten_ctx` FLATTENS to a display-
52//! prefix shape (`"<ctx>: <anyhow::Error display>"`) — the pre-lift
53//! wire format every consumer's `tracing::error!(error = %e, ...)`
54//! log line already encoded. Sharing the name (`context`) would let
55//! a caller who has [`anyhow::Context`] in scope resolve to the
56//! WRONG method (a chain-wrap instead of the display-prefix flatten)
57//! and silently drop the underlying error detail from every
58//! reconciler-error tracing span whose formatter interpolates `{e}`.
59//!
60//! ### Two flavors: `flatten_ctx` + `flatten_ctx_with`
61//!
62//! * [`FlattenCtxExt::flatten_ctx`] takes a `&'static str` context —
63//!   the most common shape, matching every static-slug consumer
64//!   (`"depends_on check"`, `"render routing"`, `"render export
65//!   jobs"`, `"apply export job"`). Static binding keeps the
66//!   compile-time contract that the context slug is a bare literal,
67//!   no allocation, no dynamic content leaking into an error stream
68//!   downstream operators grep on.
69//! * [`FlattenCtxExt::flatten_ctx_with`] takes an owned [`String`]
70//!   context — the escape hatch for the one dynamic-slug consumer
71//!   (`format!("evaluate {:?}", c.kind)`, the `ConditionKind`
72//!   variant name only known at runtime), matching the pre-lift
73//!   shape where a `format!` composed the slug per-call.
74//!
75//! ### `#[must_use]`
76//!
77//! Every consumer threads the `?` short-circuit onto its handler's
78//! `Result<_, anyhow::Error>` return — dropping the wrap swallows
79//! the underlying failure entirely, which is never the intended
80//! semantic at any of the five pre-lift consumers.
81//!
82//! Theory anchor: THEORY.md §VI.1 (generation over composition — the
83//! anyhow-with-display-prefix wrap-shape recurred at five hand-
84//! authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
85//! trigger, and is lifted to ONE substrate owner here). THEORY.md
86//! §II.1 invariant 5 (composition preserves proofs — a regression
87//! that drifts the display-prefix separator or the byte-shape at
88//! ONE site surfaces here at the substrate pin rather than as
89//! silent operator-facing skew across every reconciler phase tick).
90
91/// Substrate extension trait over `anyhow::Result<T>` — the ONE
92/// substrate owner of the `.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))`
93/// display-prefix wrap-shape for consumers whose source error is
94/// already `anyhow::Error`. See the module docs for the full callsite
95/// audit + the naming rationale (why `flatten_ctx` and not
96/// `anyhow::Context::context`).
97pub trait FlattenCtxExt<T>: Sized {
98    /// Wrap the source [`anyhow::Error`] (if any) with a static
99    /// context prefix, producing an [`anyhow::Result`] whose error
100    /// `Display` reads exactly `"<context>: <source display>"`.
101    #[must_use = "an error wrap that isn't threaded via `?` swallows the underlying anyhow failure"]
102    fn flatten_ctx(self, context: &'static str) -> anyhow::Result<T>;
103
104    /// Owned-string peer of [`Self::flatten_ctx`] — the escape hatch
105    /// for consumers that compose the context slug via `format!`
106    /// (e.g. `format!("evaluate {:?}", c.kind)` where the tail is
107    /// only known at runtime).
108    #[must_use = "an error wrap that isn't threaded via `?` swallows the underlying anyhow failure"]
109    fn flatten_ctx_with(self, context: String) -> anyhow::Result<T>;
110}
111
112impl<T> FlattenCtxExt<T> for anyhow::Result<T> {
113    #[inline]
114    fn flatten_ctx(self, context: &'static str) -> anyhow::Result<T> {
115        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
116    }
117
118    #[inline]
119    fn flatten_ctx_with(self, context: String) -> anyhow::Result<T> {
120        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    fn sample_err() -> anyhow::Error {
129        anyhow::anyhow!("underlying primitive failed: bad slot 42")
130    }
131
132    // ─── FlattenCtxExt::flatten_ctx substrate pins ───────────────────
133    //
134    // Fail-before-pass-after granularity: the `FlattenCtxExt::flatten_ctx`
135    // trait method did not exist before this commit, so each test below
136    // fails to compile pre-lift. Post-lift they collectively pin the
137    // display-prefix wrap-shape at ONE substrate owner — a regression
138    // that drifts the separator, swaps the two slots, wraps the source
139    // anyhow error with a chain-form `source` (which would change
140    // `Display` output when downstream tracing formatters interpolate
141    // `{e}` rather than the chain-walking `{e:#}`), or promotes the
142    // pass-through arm to a synthesis (an empty `Ok(())`, a mutated
143    // context slug) surfaces HERE rather than as silent operator-facing
144    // skew across the five `phase_machine.rs` pre-lift consumers whose
145    // log output already encoded the flat `"<ctx>: <anyhow display>"`
146    // shape.
147
148    #[test]
149    fn flatten_ctx_static_str_context_matches_pre_lift_format_bytewise() {
150        // Byte-shape parity pin: the wrap output of `flatten_ctx
151        // ("<slug>")` MUST be `Display`-identical to the pre-lift
152        // hand-authored `.map_err(|e| anyhow!("<slug>: {e}"))` chain.
153        // A regression that inserted a separator character (`"<slug>::
154        // <anyhow>"`), dropped the space after the colon, or swapped
155        // the two slots (`"<anyhow>: <slug>"`) surfaces HERE rather
156        // than as silent drift at every downstream log-output consumer.
157        let raw: anyhow::Result<()> = Err(sample_err());
158        let via_trait = raw.flatten_ctx("depends_on check").unwrap_err();
159        let pre_lift = anyhow::anyhow!("depends_on check: {}", sample_err());
160        assert_eq!(
161            format!("{via_trait}"),
162            format!("{pre_lift}"),
163            "flatten_ctx wrap must be Display-identical to pre-lift anyhow! chain"
164        );
165    }
166
167    #[test]
168    fn flatten_ctx_ok_arm_is_a_pure_passthrough() {
169        // Ok-arm invariant: `flatten_ctx` on `Ok(t)` MUST return
170        // `Ok(t)` verbatim — no side-effect on the payload, no
171        // synthesis of a context-tagged error, no allocation. Peer to
172        // the Err-arm byte-shape pin; a regression that promoted the
173        // Ok arm to ALWAYS produce a synthesis Error would silently
174        // break every successful downstream primitive call in the
175        // pre-lift consumer set.
176        let raw: anyhow::Result<i32> = Ok(42);
177        assert_eq!(raw.flatten_ctx("noop").unwrap(), 42);
178    }
179
180    #[test]
181    fn flatten_ctx_with_owned_string_matches_pre_lift_format_bytewise() {
182        // Owned-string peer's byte-shape pin — same discipline as the
183        // static-`&str` peer above. Consumers that compose the context
184        // slug via `format!` (e.g. `format!("evaluate {:?}", c.kind)`)
185        // route through this method and inherit the SAME display-prefix
186        // discipline as the static-slug peer, so mixing the two forms
187        // across the reconciler's log stream never surfaces as a
188        // format-string skew.
189        let raw: anyhow::Result<()> = Err(sample_err());
190        let dynamic_slug = format!("evaluate {:?}", "HelmReleaseReleased");
191        let via_trait = raw.flatten_ctx_with(dynamic_slug.clone()).unwrap_err();
192        let pre_lift = anyhow::anyhow!("{}: {}", dynamic_slug, sample_err());
193        assert_eq!(
194            format!("{via_trait}"),
195            format!("{pre_lift}"),
196            "flatten_ctx_with wrap must be Display-identical to pre-lift anyhow! chain"
197        );
198    }
199
200    #[test]
201    fn flatten_ctx_with_ok_arm_is_a_pure_passthrough() {
202        // Ok-arm invariant on the owned-string peer — sibling to the
203        // static-slug pin above. A regression that promoted the Ok arm
204        // of the dynamic-slug peer to a synthesis while leaving the
205        // static-slug peer's Ok arm passthrough would surface HERE as
206        // an owned-string-peer-specific asymmetry rather than as silent
207        // drift at the one `phase_machine::evaluate_conditions` consumer.
208        let raw: anyhow::Result<&'static str> = Ok("condition satisfied");
209        assert_eq!(
210            raw.flatten_ctx_with("dynamic".to_string()).unwrap(),
211            "condition satisfied"
212        );
213    }
214
215    #[test]
216    fn flatten_ctx_static_and_owned_peers_produce_identical_output_for_the_same_slug() {
217        // Cross-peer coherence pin: given the SAME context slug via
218        // both peers (a `&'static str` passed to `flatten_ctx` and the
219        // owned `String` produced by `.to_string()` passed to
220        // `flatten_ctx_with`), the wrapped `anyhow::Error` MUST have
221        // byte-identical `Display` output. A regression that drifted
222        // one peer's format string away from the other would surface
223        // HERE rather than as silent operator-facing skew between the
224        // four static-slug consumers and the one `format!`-slug
225        // consumer in the same log stream.
226        let slug = "render routing";
227        let a: anyhow::Result<()> = Err(sample_err());
228        let b: anyhow::Result<()> = Err(sample_err());
229        assert_eq!(
230            format!("{}", a.flatten_ctx(slug).unwrap_err()),
231            format!("{}", b.flatten_ctx_with(slug.to_string()).unwrap_err()),
232            "static-str and owned-string peers must produce identical Display output"
233        );
234    }
235
236    #[test]
237    fn flatten_ctx_threads_the_underlying_anyhow_display_verbatim() {
238        // Display-tail invariant: the wrapped `anyhow::Error`'s
239        // `Display` output MUST contain the source `anyhow::Error`'s
240        // own `Display` output verbatim as the tail past `"<ctx>: "`.
241        // A regression that inserted a normalization (uppercase, JSON
242        // encoding, truncation) between the composed `{e}` slot and
243        // the underlying `Display` impl would surface HERE rather
244        // than as silent underlying-error-detail loss across the
245        // reconciler's error stream.
246        let underlying_display = format!("{}", sample_err());
247        let raw: anyhow::Result<()> = Err(sample_err());
248        let wrapped = raw.flatten_ctx("apply export job").unwrap_err();
249        let wrapped_display = format!("{wrapped}");
250        assert!(
251            wrapped_display.ends_with(&underlying_display),
252            "wrapped Display `{wrapped_display}` must end with underlying anyhow Display `{underlying_display}`"
253        );
254        assert!(
255            wrapped_display.starts_with("apply export job: "),
256            "wrapped Display `{wrapped_display}` must start with `\"<ctx>: \"`"
257        );
258    }
259}