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 for
4//! consumers whose source error is a bare `Display` type NOT already
5//! covered by a per-error-type flatten-wrap peer.
6//!
7//! Peer of the type-specific flatten-wrap trait trio already in this
8//! crate on the same display-prefix wrap axis, partitioning the space
9//! by SPECIFICITY:
10//!
11//! * [`crate::kube_error::KubeResultExt::kube_ctx`] — the specialized
12//!   peer for `Result<T, kube::Error>`, kept because
13//!   [`kube::Error`]'s `Display` composes the request URI + status
14//!   line in a shape every reconciler-side callsite already greps on.
15//! * [`crate::hostname::HostnameResultExt::hostname_ctx`] — the
16//!   specialized peer for `Result<T, `[`crate::hostname::HostnameError`]`>`,
17//!   kept because the [`thiserror`]-derived `Display` output matches
18//!   the pre-lift hand-authored render_routing log stream verbatim.
19//! * [`crate::anyhow_flatten::FlattenCtxExt::flatten_ctx`] — the
20//!   specialized peer for `anyhow::Result<T>`, kept because it
21//!   collides with [`anyhow::Context::context`]'s naming so the
22//!   distinct-method-name discipline (flatten-prefix vs. chain-wrap
23//!   semantics) is load-bearing at every phase-machine callsite.
24//! * [`ErrCtxExt::err_ctx`] (this trait) — the generic fallback for
25//!   any `E: Display` NOT covered by the three specialized peers,
26//!   so a new consumer whose source error is a fresh
27//!   [`crate::tagged_union::declare_tagged_union_error`]-derived
28//!   variant (e.g. [`crate::export::ArtifactError`],
29//!   [`crate::intent::IntentError`],
30//!   [`crate::lifetime::LifetimeError`]) reaches the display-prefix
31//!   wrap-shape mechanically at ONE substrate owner instead of
32//!   opening a fourth per-error-type peer trait for every fresh
33//!   [`thiserror`]-derived enum.
34//!
35//! Pre-lift the shape was hand-authored at TWO
36//! `tatara-export-worker/src/main.rs` sites past the ★★ PRIME-DIRECTIVE
37//! ≥ 2 duplication threshold, both restating the SAME closure —
38//! capture an [`crate::export::ArtifactError`] returned by the
39//! substrate primitive [`crate::export::ArtifactSource::variant`],
40//! prepend the identical static context slug `"source"`, delegate
41//! the tail to [`std::fmt::Display`] via the `{e}` slot — differing
42//! in NOTHING but their line numbers. Post-lift both callsites read
43//! `spec.source.variant().err_ctx("source")?` and the wrap-shape
44//! lives at ONE substrate owner here.
45//!
46//! ### Naming — `err_ctx`, not `context`
47//!
48//! Same discipline as the three specialized peers: the method name
49//! `err_ctx` is deliberately DISTINCT from [`anyhow::Context::context`]
50//! so a caller with [`anyhow::Context`] in scope can never resolve to
51//! the wrong method (which chain-wraps rather than display-prefix-
52//! flattens, and would silently drop the underlying error detail from
53//! every downstream `tracing::error!(error = %e, ...)` log line whose
54//! formatter interpolates `{e}` rather than the chain-walking `{e:#}`).
55//!
56//! ### Two flavors: `err_ctx` + `err_ctx_with`
57//!
58//! * [`ErrCtxExt::err_ctx`] takes a `&'static str` context — the
59//!   most common shape (`"source"` at the two export-worker
60//!   callsites). Static binding keeps the compile-time contract that
61//!   the context slug is a bare literal, no allocation, no dynamic
62//!   content leaking into an error stream downstream operators grep
63//!   on.
64//! * [`ErrCtxExt::err_ctx_with`] takes an owned [`String`] context —
65//!   the escape hatch for future consumers that compose the slug via
66//!   [`format!`] (e.g. a dynamic per-variant-name slug the way
67//!   [`crate::anyhow_flatten::FlattenCtxExt::flatten_ctx_with`]
68//!   already services for `phase_machine::evaluate_conditions`).
69//!
70//! ### `#[must_use]`
71//!
72//! Every consumer threads the `?` short-circuit onto its handler's
73//! `anyhow::Result<_>` return — dropping the wrap swallows the
74//! underlying failure entirely, which is never the intended
75//! semantic.
76//!
77//! Theory anchor: THEORY.md §VI.1 (generation over composition — the
78//! generic display-prefix wrap-shape recurred at two byte-identical
79//! hand-authored sites in `tatara-export-worker/src/main.rs` past
80//! the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
81//! ONE substrate owner here). THEORY.md §II.1 invariant 5
82//! (composition preserves proofs — a regression that drifts the
83//! display-prefix separator or the byte-shape surfaces here at the
84//! substrate pin rather than as silent operator-facing skew across
85//! every downstream `err_ctx` consumer).
86
87/// Substrate extension trait over `Result<T, E>` for any `E: `
88/// [`std::fmt::Display`] — the ONE substrate owner of the generic
89/// `.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))` display-prefix
90/// wrap-shape for consumers whose source error is a bare `Display`
91/// type NOT already covered by a per-error-type flatten-wrap peer.
92/// See the module docs for the specialized-peer partition + the
93/// naming rationale (why `err_ctx` and not `context`).
94pub trait ErrCtxExt<T>: Sized {
95    /// Wrap the source error (if any) with a static context prefix,
96    /// producing an [`anyhow::Result`] whose error `Display` reads
97    /// exactly `"<context>: <source display>"`.
98    #[must_use = "an error wrap that isn't threaded via `?` swallows the underlying failure"]
99    fn err_ctx(self, context: &'static str) -> anyhow::Result<T>;
100
101    /// Owned-string peer of [`Self::err_ctx`] — the escape hatch for
102    /// consumers that compose the context slug via [`format!`].
103    #[must_use = "an error wrap that isn't threaded via `?` swallows the underlying failure"]
104    fn err_ctx_with(self, context: String) -> anyhow::Result<T>;
105}
106
107impl<T, E> ErrCtxExt<T> for Result<T, E>
108where
109    E: std::fmt::Display,
110{
111    #[inline]
112    fn err_ctx(self, context: &'static str) -> anyhow::Result<T> {
113        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
114    }
115
116    #[inline]
117    fn err_ctx_with(self, context: String) -> anyhow::Result<T> {
118        self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    // A representative bare-`Display` error type — matches the shape
127    // of [`crate::export::ArtifactError`] (the concrete
128    // [`thiserror`]-derived error whose two pre-lift callsites drove
129    // this lift) without pulling the whole export module into the
130    // test surface. A regression that promotes the trait bound to
131    // e.g. `E: std::error::Error` would surface HERE (this type
132    // doesn't impl `Error`) rather than as a silent narrowing of the
133    // substrate's admissibility surface.
134    #[derive(Debug)]
135    struct DisplayErr(&'static str);
136    impl std::fmt::Display for DisplayErr {
137        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138            f.write_str(self.0)
139        }
140    }
141
142    // ─── ErrCtxExt::err_ctx substrate pins ───────────────────────────
143    //
144    // Fail-before-pass-after granularity: the `ErrCtxExt::err_ctx`
145    // trait method did not exist before this commit, so each test
146    // below fails to compile pre-lift. Post-lift they collectively
147    // pin the display-prefix wrap-shape at ONE substrate owner — a
148    // regression that drifts the separator, swaps the two slots,
149    // narrows the trait bound (`E: Error` in place of `E: Display`,
150    // ruling out today's `ArtifactError` and similar bare-Display
151    // enums that don't derive `Error`), or promotes the pass-through
152    // arm to a synthesis surfaces HERE rather than as silent
153    // operator-facing skew across the two export-worker pre-lift
154    // consumers whose log output already encoded the flat
155    // `"source: <ArtifactError display>"` shape.
156
157    #[test]
158    fn err_ctx_static_str_context_matches_pre_lift_format_bytewise() {
159        // Byte-shape parity pin: the wrap output of
160        // `err_ctx("<slug>")` MUST be `Display`-identical to the
161        // pre-lift hand-authored `.map_err(|e| anyhow!("<slug>:
162        // {e}"))` chain. A regression that inserted a separator
163        // character (`"<slug>:: <err>"`), dropped the space after
164        // the colon, or swapped the two slots (`"<err>: <slug>"`)
165        // surfaces HERE rather than as silent drift at every
166        // downstream log-output consumer.
167        let raw: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
168        let via_trait = raw.err_ctx("source").unwrap_err();
169        assert_eq!(format!("{via_trait}"), "source: bad slot");
170    }
171
172    #[test]
173    fn err_ctx_ok_arm_is_a_pure_passthrough() {
174        // Ok-arm invariant: `err_ctx` on `Ok(t)` MUST return `Ok(t)`
175        // verbatim — no side-effect on the payload, no synthesis of
176        // a context-tagged error. Peer to the Err-arm byte-shape
177        // pin; a regression that promoted the Ok arm to ALWAYS
178        // produce a synthesis Error would silently break every
179        // successful downstream primitive call in the pre-lift
180        // consumer set.
181        let raw: Result<i32, DisplayErr> = Ok(42);
182        assert_eq!(raw.err_ctx("noop").unwrap(), 42);
183    }
184
185    #[test]
186    fn err_ctx_with_owned_string_matches_pre_lift_format_bytewise() {
187        // Owned-string peer's byte-shape pin — same discipline as
188        // the static-`&str` peer above. Future consumers that
189        // compose the context slug via `format!` route through this
190        // method and inherit the SAME display-prefix discipline as
191        // the static-slug peer, so mixing the two forms across a
192        // consumer's log stream never surfaces as a format-string
193        // skew.
194        let raw: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
195        let dynamic_slug = format!("evaluate {:?}", "SomeVariant");
196        let via_trait = raw.err_ctx_with(dynamic_slug.clone()).unwrap_err();
197        assert_eq!(format!("{via_trait}"), format!("{dynamic_slug}: bad slot"));
198    }
199
200    #[test]
201    fn err_ctx_with_ok_arm_is_a_pure_passthrough() {
202        // Ok-arm invariant on the owned-string peer — sibling to
203        // the static-slug pin above.
204        let raw: Result<&'static str, DisplayErr> = Ok("variant resolved");
205        assert_eq!(
206            raw.err_ctx_with("dynamic".to_string()).unwrap(),
207            "variant resolved"
208        );
209    }
210
211    #[test]
212    fn err_ctx_static_and_owned_peers_produce_identical_output_for_the_same_slug() {
213        // Cross-peer coherence pin: given the SAME context slug via
214        // both peers, the wrapped [`anyhow::Error`] MUST have
215        // byte-identical `Display` output.
216        let slug = "source";
217        let a: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
218        let b: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
219        assert_eq!(
220            format!("{}", a.err_ctx(slug).unwrap_err()),
221            format!("{}", b.err_ctx_with(slug.to_string()).unwrap_err()),
222            "static-str and owned-string peers must produce identical Display output"
223        );
224    }
225
226    #[test]
227    fn err_ctx_threads_the_underlying_display_verbatim() {
228        // Display-tail invariant: the wrapped [`anyhow::Error`]'s
229        // `Display` output MUST contain the source error's own
230        // `Display` output verbatim as the tail past `"<ctx>: "`.
231        let underlying_display = format!("{}", DisplayErr("bad slot"));
232        let raw: Result<(), DisplayErr> = Err(DisplayErr("bad slot"));
233        let wrapped = raw.err_ctx("source").unwrap_err();
234        let wrapped_display = format!("{wrapped}");
235        assert!(
236            wrapped_display.ends_with(&underlying_display),
237            "wrapped Display `{wrapped_display}` must end with underlying Display `{underlying_display}`"
238        );
239        assert!(
240            wrapped_display.starts_with("source: "),
241            "wrapped Display `{wrapped_display}` must start with `\"<ctx>: \"`"
242        );
243    }
244
245    #[test]
246    fn err_ctx_admits_thiserror_derived_tagged_union_error() {
247        // Substrate coverage pin: the trait's `E: Display` bound
248        // MUST admit the concrete error type both pre-lift
249        // export-worker callsites captured — a
250        // [`crate::tagged_union::declare_tagged_union_error`]-
251        // derived variant whose `Display` is [`thiserror`]-generated.
252        // This test exercises `ArtifactError` specifically (the two
253        // pre-lift consumers' source error) so a regression that
254        // dropped its `Display` impl, narrowed the trait bound, or
255        // otherwise made the primitive inapplicable to the very
256        // callsites it was opened for surfaces HERE.
257        use crate::export::{ArtifactError, ARTIFACT_KIND_LIST};
258        let raw: Result<(), ArtifactError> = Err(ArtifactError::Empty(ARTIFACT_KIND_LIST));
259        let wrapped = raw.err_ctx("source").unwrap_err();
260        let wrapped_display = format!("{wrapped}");
261        assert!(
262            wrapped_display.starts_with("source: "),
263            "wrapped Display `{wrapped_display}` must start with `\"source: \"`"
264        );
265        assert!(
266            wrapped_display.contains(ARTIFACT_KIND_LIST),
267            "wrapped Display `{wrapped_display}` must thread ArtifactError's Display body verbatim"
268        );
269    }
270
271    #[test]
272    fn err_ctx_agrees_with_flatten_ctx_on_anyhow_result() {
273        // Cross-substrate coherence pin: on the specific input shape
274        // `anyhow::Result<T>`, the generic `err_ctx` and the
275        // specialized peer
276        // [`crate::anyhow_flatten::FlattenCtxExt::flatten_ctx`] MUST
277        // produce byte-identical `Display` output. A regression that
278        // drifted either surface would surface HERE rather than as
279        // silent operator-facing skew between consumers migrated
280        // onto the generic and consumers still routed through the
281        // specialized peer.
282        use crate::anyhow_flatten::FlattenCtxExt;
283        let a: anyhow::Result<()> = Err(anyhow::anyhow!("underlying failure"));
284        let b: anyhow::Result<()> = Err(anyhow::anyhow!("underlying failure"));
285        assert_eq!(
286            format!("{}", a.err_ctx("source").unwrap_err()),
287            format!("{}", b.flatten_ctx("source").unwrap_err()),
288            "generic err_ctx and specialized flatten_ctx must agree on anyhow::Result"
289        );
290    }
291}