Skip to main content

tatara_process/
err_ctx.rs

1//! Substrate primitive over `Result<T, E>` for any `E: `[`std::fmt::
2//! Display`] — the ONE substrate owner of the generic `.map_err(|e|
3//! anyhow::anyhow!("<ctx>: {e}"))` display-prefix wrap-shape.
4//!
5//! Every one of the sibling per-error-type peers ([`crate::kube_error::
6//! KubeResultExt`], [`crate::anyhow_flatten::FlattenCtxExt`],
7//! [`crate::hostname::HostnameResultExt`]) delegates its body onto
8//! this trait's `err_ctx` / `err_ctx_with` — the specialized peers
9//! stay for the naming discipline (each name is deliberately DISTINCT
10//! from [`anyhow::Context::context`] to prevent silent resolution to
11//! the chain-wrap semantics that would drop the source error's
12//! `Display` output from every `tracing::error!(error = %e, ...)`
13//! log line), but the byte-shape body itself lives at ONE substrate
14//! owner here. Pre-lift each peer restated the SAME
15//! `.map_err(|e| anyhow::anyhow!("{ctx}: {e}"))` closure by hand,
16//! pinned equal only by peer-side byte-shape tests; post-delegation
17//! byte-shape agreement holds by CONSTRUCTION and the cross-peer
18//! agreement tests in this module ([`tests::err_ctx_agrees_with_kube_ctx_on_kube_error_result`],
19//! [`tests::err_ctx_agrees_with_flatten_ctx_on_anyhow_result`],
20//! [`tests::err_ctx_agrees_with_hostname_ctx_on_hostname_error_result`],
21//! plus the `_with` peers) route the delegation invariant through
22//! this substrate.
23//!
24//! Peer of the type-specific flatten-wrap trait trio already in this
25//! crate on the same display-prefix wrap axis, partitioning the space
26//! by SPECIFICITY:
27//!
28//! * [`crate::kube_error::KubeResultExt::kube_ctx`] — the specialized
29//!   peer for `Result<T, kube::Error>`, kept because
30//!   [`kube::Error`]'s `Display` composes the request URI + status
31//!   line in a shape every reconciler-side callsite already greps on.
32//! * [`crate::hostname::HostnameResultExt::hostname_ctx`] — the
33//!   specialized peer for `Result<T, `[`crate::hostname::HostnameError`]`>`,
34//!   kept because the [`thiserror`]-derived `Display` output matches
35//!   the pre-lift hand-authored render_routing log stream verbatim.
36//! * [`crate::anyhow_flatten::FlattenCtxExt::flatten_ctx`] — the
37//!   specialized peer for `anyhow::Result<T>`, kept because it
38//!   collides with [`anyhow::Context::context`]'s naming so the
39//!   distinct-method-name discipline (flatten-prefix vs. chain-wrap
40//!   semantics) is load-bearing at every phase-machine callsite.
41//! * [`ErrCtxExt::err_ctx`] (this trait) — the shared substrate body
42//!   the three specialized peers delegate onto, AND the reachable
43//!   surface for any new consumer whose source error is a fresh
44//!   [`crate::tagged_union::declare_tagged_union_error`]-derived
45//!   variant (e.g. [`crate::export::ArtifactError`],
46//!   [`crate::intent::IntentError`],
47//!   [`crate::lifetime::LifetimeError`]). Such a consumer reaches the
48//!   display-prefix wrap-shape mechanically at THIS ONE substrate
49//!   owner instead of opening a fourth per-error-type peer trait for
50//!   every fresh [`thiserror`]-derived enum.
51//!
52//! Pre-lift the shape was hand-authored at TWO
53//! `tatara-export-worker/src/main.rs` sites past the ★★ PRIME-DIRECTIVE
54//! ≥ 2 duplication threshold, both restating the SAME closure —
55//! capture an [`crate::export::ArtifactError`] returned by the
56//! substrate primitive [`crate::export::ArtifactSource::variant`],
57//! prepend the identical static context slug `"source"`, delegate
58//! the tail to [`std::fmt::Display`] via the `{e}` slot — differing
59//! in NOTHING but their line numbers. Post-lift both callsites read
60//! `spec.source.variant().err_ctx("source")?` and the wrap-shape
61//! lives at ONE substrate owner here.
62//!
63//! ### Naming — `err_ctx`, not `context`
64//!
65//! Same discipline as the three specialized peers: the method name
66//! `err_ctx` is deliberately DISTINCT from [`anyhow::Context::context`]
67//! so a caller with [`anyhow::Context`] in scope can never resolve to
68//! the wrong method (which chain-wraps rather than display-prefix-
69//! flattens, and would silently drop the underlying error detail from
70//! every downstream `tracing::error!(error = %e, ...)` log line whose
71//! formatter interpolates `{e}` rather than the chain-walking `{e:#}`).
72//!
73//! ### Two flavors: `err_ctx` + `err_ctx_with`
74//!
75//! * [`ErrCtxExt::err_ctx`] takes a `&'static str` context — the
76//!   most common shape (`"source"` at the two export-worker
77//!   callsites). Static binding keeps the compile-time contract that
78//!   the context slug is a bare literal, no allocation, no dynamic
79//!   content leaking into an error stream downstream operators grep
80//!   on.
81//! * [`ErrCtxExt::err_ctx_with`] takes an owned [`String`] context —
82//!   the escape hatch for future consumers that compose the slug via
83//!   [`format!`] (e.g. a dynamic per-variant-name slug the way
84//!   [`crate::anyhow_flatten::FlattenCtxExt::flatten_ctx_with`]
85//!   already services for `phase_machine::evaluate_conditions`).
86//!
87//! ### `#[must_use]`
88//!
89//! Every consumer threads the `?` short-circuit onto its handler's
90//! `anyhow::Result<_>` return — dropping the wrap swallows the
91//! underlying failure entirely, which is never the intended
92//! semantic.
93//!
94//! Theory anchor: THEORY.md §VI.1 (generation over composition — the
95//! generic display-prefix wrap-shape recurred at two byte-identical
96//! hand-authored sites in `tatara-export-worker/src/main.rs` past
97//! the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
98//! ONE substrate owner here). THEORY.md §II.1 invariant 5
99//! (composition preserves proofs — a regression that drifts the
100//! display-prefix separator or the byte-shape surfaces here at the
101//! substrate pin rather than as silent operator-facing skew across
102//! every downstream `err_ctx` consumer).
103
104/// Substrate extension trait over `Result<T, E>` for any `E: `
105/// [`std::fmt::Display`] — the ONE substrate owner of the generic
106/// `.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))` display-prefix
107/// wrap-shape for consumers whose source error is a bare `Display`
108/// type NOT already covered by a per-error-type flatten-wrap peer.
109/// See the module docs for the specialized-peer partition + the
110/// naming rationale (why `err_ctx` and not `context`).
111pub trait ErrCtxExt<T>: Sized {
112    /// Wrap the source error (if any) with a static context prefix,
113    /// producing an [`anyhow::Result`] whose error `Display` reads
114    /// exactly `"<context>: <source display>"`.
115    #[must_use = "an error wrap that isn't threaded via `?` swallows the underlying failure"]
116    fn err_ctx(self, context: &'static str) -> anyhow::Result<T>;
117
118    /// Owned-string peer of [`Self::err_ctx`] — the escape hatch for
119    /// consumers that compose the context slug via [`format!`].
120    #[must_use = "an error wrap that isn't threaded via `?` swallows the underlying failure"]
121    fn err_ctx_with(self, context: String) -> anyhow::Result<T>;
122}
123
124impl<T, E> ErrCtxExt<T> for Result<T, E>
125where
126    E: std::fmt::Display,
127{
128    #[inline]
129    fn err_ctx(self, context: &'static str) -> anyhow::Result<T> {
130        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
131    }
132
133    #[inline]
134    fn err_ctx_with(self, context: String) -> anyhow::Result<T> {
135        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    // A representative bare-`Display` error type — matches the shape
144    // of [`crate::export::ArtifactError`] (the concrete
145    // [`thiserror`]-derived error whose two pre-lift callsites drove
146    // this lift) without pulling the whole export module into the
147    // test surface. A regression that promotes the trait bound to
148    // e.g. `E: std::error::Error` would surface HERE (this type
149    // doesn't impl `Error`) rather than as a silent narrowing of the
150    // substrate's admissibility surface.
151    #[derive(Debug)]
152    struct DisplayErr(&'static str);
153    impl std::fmt::Display for DisplayErr {
154        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155            f.write_str(self.0)
156        }
157    }
158
159    // ─── ErrCtxExt::err_ctx substrate pins ───────────────────────────
160    //
161    // Fail-before-pass-after granularity: the `ErrCtxExt::err_ctx`
162    // trait method did not exist before this commit, so each test
163    // below fails to compile pre-lift. Post-lift they collectively
164    // pin the display-prefix wrap-shape at ONE substrate owner — a
165    // regression that drifts the separator, swaps the two slots,
166    // narrows the trait bound (`E: Error` in place of `E: Display`,
167    // ruling out today's `ArtifactError` and similar bare-Display
168    // enums that don't derive `Error`), or promotes the pass-through
169    // arm to a synthesis surfaces HERE rather than as silent
170    // operator-facing skew across the two export-worker pre-lift
171    // consumers whose log output already encoded the flat
172    // `"source: <ArtifactError display>"` shape.
173
174    #[test]
175    fn err_ctx_static_str_context_matches_pre_lift_format_bytewise() {
176        // Byte-shape parity pin: the wrap output of
177        // `err_ctx("<slug>")` MUST be `Display`-identical to the
178        // pre-lift hand-authored `.map_err(|e| anyhow!("<slug>:
179        // {e}"))` chain. A regression that inserted a separator
180        // character (`"<slug>:: <err>"`), dropped the space after
181        // the colon, or swapped the two slots (`"<err>: <slug>"`)
182        // surfaces HERE rather than as silent drift at every
183        // downstream log-output consumer.
184        let raw: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
185        let via_trait = raw.err_ctx("source").unwrap_err();
186        assert_eq!(format!("{via_trait}"), "source: bad slot");
187    }
188
189    #[test]
190    fn err_ctx_ok_arm_is_a_pure_passthrough() {
191        // Ok-arm invariant: `err_ctx` on `Ok(t)` MUST return `Ok(t)`
192        // verbatim — no side-effect on the payload, no synthesis of
193        // a context-tagged error. Peer to the Err-arm byte-shape
194        // pin; a regression that promoted the Ok arm to ALWAYS
195        // produce a synthesis Error would silently break every
196        // successful downstream primitive call in the pre-lift
197        // consumer set.
198        let raw: Result<i32, DisplayErr> = Ok(42);
199        assert_eq!(raw.err_ctx("noop").unwrap(), 42);
200    }
201
202    #[test]
203    fn err_ctx_with_owned_string_matches_pre_lift_format_bytewise() {
204        // Owned-string peer's byte-shape pin — same discipline as
205        // the static-`&str` peer above. Future consumers that
206        // compose the context slug via `format!` route through this
207        // method and inherit the SAME display-prefix discipline as
208        // the static-slug peer, so mixing the two forms across a
209        // consumer's log stream never surfaces as a format-string
210        // skew.
211        let raw: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
212        let dynamic_slug = format!("evaluate {:?}", "SomeVariant");
213        let via_trait = raw.err_ctx_with(dynamic_slug.clone()).unwrap_err();
214        assert_eq!(format!("{via_trait}"), format!("{dynamic_slug}: bad slot"));
215    }
216
217    #[test]
218    fn err_ctx_with_ok_arm_is_a_pure_passthrough() {
219        // Ok-arm invariant on the owned-string peer — sibling to
220        // the static-slug pin above.
221        let raw: Result<&'static str, DisplayErr> = Ok("variant resolved");
222        assert_eq!(
223            raw.err_ctx_with("dynamic".to_string()).unwrap(),
224            "variant resolved"
225        );
226    }
227
228    #[test]
229    fn err_ctx_static_and_owned_peers_produce_identical_output_for_the_same_slug() {
230        // Cross-peer coherence pin: given the SAME context slug via
231        // both peers, the wrapped [`anyhow::Error`] MUST have
232        // byte-identical `Display` output.
233        let slug = "source";
234        let a: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
235        let b: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
236        assert_eq!(
237            format!("{}", a.err_ctx(slug).unwrap_err()),
238            format!("{}", b.err_ctx_with(slug.to_string()).unwrap_err()),
239            "static-str and owned-string peers must produce identical Display output"
240        );
241    }
242
243    #[test]
244    fn err_ctx_threads_the_underlying_display_verbatim() {
245        // Display-tail invariant: the wrapped [`anyhow::Error`]'s
246        // `Display` output MUST contain the source error's own
247        // `Display` output verbatim as the tail past `"<ctx>: "`.
248        let underlying_display = format!("{}", DisplayErr("bad slot"));
249        let raw: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
250        let wrapped = raw.err_ctx("source").unwrap_err();
251        let wrapped_display = format!("{wrapped}");
252        assert!(
253            wrapped_display.ends_with(&underlying_display),
254            "wrapped Display `{wrapped_display}` must end with underlying Display `{underlying_display}`"
255        );
256        assert!(
257            wrapped_display.starts_with("source: "),
258            "wrapped Display `{wrapped_display}` must start with `\"<ctx>: \"`"
259        );
260    }
261
262    #[test]
263    fn err_ctx_admits_thiserror_derived_tagged_union_error() {
264        // Substrate coverage pin: the trait's `E: Display` bound
265        // MUST admit the concrete error type both pre-lift
266        // export-worker callsites captured — a
267        // [`crate::tagged_union::declare_tagged_union_error`]-
268        // derived variant whose `Display` is [`thiserror`]-generated.
269        // This test exercises `ArtifactError` specifically (the two
270        // pre-lift consumers' source error) so a regression that
271        // dropped its `Display` impl, narrowed the trait bound, or
272        // otherwise made the primitive inapplicable to the very
273        // callsites it was opened for surfaces HERE.
274        use crate::export::{ArtifactError, ARTIFACT_KIND_LIST};
275        let raw: Result<(), ArtifactError> = Err(ArtifactError::Empty(ARTIFACT_KIND_LIST));
276        let wrapped = raw.err_ctx("source").unwrap_err();
277        let wrapped_display = format!("{wrapped}");
278        assert!(
279            wrapped_display.starts_with("source: "),
280            "wrapped Display `{wrapped_display}` must start with `\"source: \"`"
281        );
282        assert!(
283            wrapped_display.contains(ARTIFACT_KIND_LIST),
284            "wrapped Display `{wrapped_display}` must thread ArtifactError's Display body verbatim"
285        );
286    }
287
288    #[test]
289    fn err_ctx_agrees_with_flatten_ctx_on_anyhow_result() {
290        // Cross-substrate coherence pin: on the specific input shape
291        // `anyhow::Result<T>`, the generic `err_ctx` and the
292        // specialized peer
293        // [`crate::anyhow_flatten::FlattenCtxExt::flatten_ctx`] MUST
294        // produce byte-identical `Display` output. Post-delegation
295        // (both bodies routed onto this substrate owner) byte-shape
296        // agreement holds by CONSTRUCTION rather than by two
297        // independent hand-authored `.map_err(|e| anyhow!)` closures
298        // pinned equal by convention; a regression that re-open-coded
299        // the specialized peer's body surfaces HERE rather than as
300        // silent operator-facing skew between the consumers migrated
301        // onto the generic and consumers still routed through the
302        // specialized peer.
303        use crate::anyhow_flatten::FlattenCtxExt;
304        let a: anyhow::Result<()> = Err(anyhow::anyhow!("underlying failure"));
305        let b: anyhow::Result<()> = Err(anyhow::anyhow!("underlying failure"));
306        assert_eq!(
307            format!("{}", a.err_ctx("source").unwrap_err()),
308            format!("{}", b.flatten_ctx("source").unwrap_err()),
309            "generic err_ctx and specialized flatten_ctx must agree on anyhow::Result"
310        );
311    }
312
313    #[test]
314    fn err_ctx_with_agrees_with_flatten_ctx_with_on_dynamic_slug() {
315        // Owned-string peer coherence pin: sibling to the static-slug
316        // pin above, walking the `_with` peer instead. Pre-delegation
317        // the two owned-string bodies were byte-identical by
318        // convention (each restating the SAME
319        // `.map_err(|e| anyhow!("{ctx}: {e}"))` chain); post-
320        // delegation the specialized `flatten_ctx_with` routes onto
321        // this substrate's `err_ctx_with`, and byte-shape agreement
322        // holds by CONSTRUCTION. A regression that re-open-coded
323        // `flatten_ctx_with`'s body — dropping the delegation and
324        // restoring the pre-lift inline closure with a drifted
325        // separator or a swapped-slots typo — surfaces HERE rather
326        // than as silent skew between the four static-slug consumers
327        // and the one `format!`-slug consumer in the reconciler's
328        // phase-machine log stream.
329        use crate::anyhow_flatten::FlattenCtxExt;
330        let slug = format!("evaluate {:?}", "HelmReleaseReleased");
331        let a: anyhow::Result<()> = Err(anyhow::anyhow!("underlying failure"));
332        let b: anyhow::Result<()> = Err(anyhow::anyhow!("underlying failure"));
333        assert_eq!(
334            format!("{}", a.err_ctx_with(slug.clone()).unwrap_err()),
335            format!("{}", b.flatten_ctx_with(slug).unwrap_err()),
336            "generic err_ctx_with and specialized flatten_ctx_with must agree on anyhow::Result"
337        );
338    }
339
340    #[test]
341    fn err_ctx_agrees_with_kube_ctx_on_kube_error_result() {
342        // Cross-substrate coherence pin: on the specific input shape
343        // `Result<T, kube::Error>`, the generic `err_ctx` and the
344        // specialized peer
345        // [`crate::kube_error::KubeResultExt::kube_ctx`] MUST produce
346        // byte-identical `Display` output. Post-delegation (the
347        // specialized peer's body routes onto this substrate) byte-
348        // shape agreement holds by CONSTRUCTION — `kube::Error:
349        // Display` so the generic `err_ctx` impl applies to the same
350        // input type. A regression that re-open-coded `kube_ctx`'s
351        // body — dropping the delegation and restoring the pre-lift
352        // inline closure — surfaces HERE rather than as silent
353        // operator-facing skew between the 25+ K8s-round-trip
354        // consumers and the sibling peer families.
355        use crate::kube_error::KubeResultExt;
356        use kube::core::ErrorResponse;
357        let mk = || {
358            kube::Error::Api(ErrorResponse {
359                status: "Failure".into(),
360                message: "test code 404".into(),
361                reason: "NotFound".into(),
362                code: 404,
363            })
364        };
365        let a: Result<(), kube::Error> = Err(mk());
366        let b: Result<(), kube::Error> = Err(mk());
367        assert_eq!(
368            format!("{}", a.err_ctx("install finalizer").unwrap_err()),
369            format!("{}", b.kube_ctx("install finalizer").unwrap_err()),
370            "generic err_ctx and specialized kube_ctx must agree on Result<_, kube::Error>"
371        );
372    }
373
374    #[test]
375    fn err_ctx_with_agrees_with_kube_ctx_with_on_dynamic_slug() {
376        // Owned-string peer coherence pin on the Kube axis: sibling
377        // to the static-slug pin above, walking the `_with` peer
378        // instead. Post-delegation the specialized `kube_ctx_with`
379        // routes onto this substrate's `err_ctx_with`, and byte-
380        // shape agreement holds by CONSTRUCTION.
381        use crate::kube_error::KubeResultExt;
382        use kube::core::ErrorResponse;
383        let mk = || {
384            kube::Error::Api(ErrorResponse {
385                status: "Failure".into(),
386                message: "test code 409".into(),
387                reason: "AlreadyExists".into(),
388                code: 409,
389            })
390        };
391        let slug = format!("patch (releasing→{})", "Exiting");
392        let a: Result<(), kube::Error> = Err(mk());
393        let b: Result<(), kube::Error> = Err(mk());
394        assert_eq!(
395            format!("{}", a.err_ctx_with(slug.clone()).unwrap_err()),
396            format!("{}", b.kube_ctx_with(slug).unwrap_err()),
397            "generic err_ctx_with and specialized kube_ctx_with must agree on Result<_, kube::Error>"
398        );
399    }
400
401    #[test]
402    fn err_ctx_agrees_with_hostname_ctx_on_hostname_error_result() {
403        // Cross-substrate coherence pin: on the specific input shape
404        // `Result<T, HostnameError>`, the generic `err_ctx` and the
405        // specialized peer
406        // [`crate::hostname::HostnameResultExt::hostname_ctx`] MUST
407        // produce byte-identical `Display` output. Post-delegation
408        // the specialized peer's body routes onto this substrate;
409        // [`crate::hostname::HostnameError`] impls `Display` via
410        // `thiserror` so the generic `err_ctx` impl applies to the
411        // same input type. A regression that re-open-coded
412        // `hostname_ctx`'s body surfaces HERE rather than as silent
413        // operator-facing skew between `render_routing`'s
414        // hostname-format consumer and the sibling peer families.
415        use crate::hostname::{HostnameError, HostnameResultExt};
416        let mk = || HostnameError::ReservedApp("auth".to_string());
417        let a: Result<(), HostnameError> = Err(mk());
418        let b: Result<(), HostnameError> = Err(mk());
419        assert_eq!(
420            format!("{}", a.err_ctx("render routing").unwrap_err()),
421            format!("{}", b.hostname_ctx("render routing").unwrap_err()),
422            "generic err_ctx and specialized hostname_ctx must agree on Result<_, HostnameError>"
423        );
424    }
425}