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}