Expand description
Substrate primitive over serde_json::Value — the ONE substrate
owner of the .as_object_mut().ok_or_else(|| anyhow::anyhow!( "<slot> is not an object")) guard-shape every JSON-mutating helper
restates by hand at the “walk this Value slot into its
serde_json::Map interior or fail loud” boundary.
Peer of the trait family that already lives in this crate on the wrap-shape axis:
crate::kube_error::KubeResultExt— thekube::Error → anyhowdisplay-prefix wrap.crate::hostname::HostnameResultExt— theHostnameError → anyhowdisplay-prefix wrap.crate::anyhow_flatten::FlattenCtxExt— theanyhow::Error → anyhowdisplay-prefix flatten.- This module — the
Option<&mut Map> → anyhow::Result<&mut Map>type-guard, partitioned from the three above by SOURCE (Nonefrom the slot-typecheck, not a lifted error type) but sharing the.map_err(|_| anyhow!("<slug>: …"))?display-prefix wire format.
Pre-lift the shape was hand-authored at THREE adjacent private
helpers in tatara-reconciler::ssapply past the ★★ PRIME-DIRECTIVE
≥ 2 duplication threshold:
metadata_object_mut(resource)— the root-guard step (resource.as_object_mut().ok_or_else(|| anyhow!("resource is not an object"))?) that opens the SSA-timeresource → &mut metadatawalk shared byinject_owner_referenceinject_annotations.
metadata_object_mut(resource)— the metadata-slot type-check step (metadata.as_object_mut().ok_or_else(|| anyhow!("metadata is not an object"))?) that closes the same walk — a resource whose author mistyped themetadataslot as an array / string surfaces as an error rather than as a silent.as_object_mut() → None → skipno-op.inject_annotations(resource, process)— the annotations-slot type-check step (annot.as_object_mut().ok_or_else(|| anyhow!( "annotations is not an object"))?) that opens the SSA-timemetadata → &mut annotationswalk before the ownership tag + observed-* primitive family drops its keys into the map.
All three restated the SAME 2-line shape verbatim: .as_object_mut()
on a serde_json::Value handle already known to be non-null, then
.ok_or_else(|| anyhow!("<slot-name> is not an object")) wrap
whose slot name matched the walk step’s semantic role ("resource"
/ "metadata" / "annotations"). THREE byte-for-byte identical
guard blocks past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold,
differing only in the &'static str slot name each callsite
stamped.
Post-lift each callsite reads
<value>.as_object_mut_or("<slot>")? and the guard-shape lives at
ONE substrate owner here. The composed anyhow::Error’s Display
is byte-identical to the pre-lift chain ("<slot> is not an object"), so operator-facing log output and any error-chain greps
still match bytewise. A regression that drifts the message (a
"<slot> is not a JSON object" synonym, a swapped <slot> slot,
a promotion to a chain-form source that only surfaces via the
alternate {e:#} formatter) surfaces at the tests below rather
than as silent operator-facing drift across the three pre-lift
consumers.
§Naming — as_object_mut_or, not as_object_mut
Same discipline as the three sibling traits above — the trait
method deliberately does NOT share a name with the inherent
serde_json::Value::as_object_mut method (which returns
Option<&mut Map>), because a name collision would let a caller
who has ValueObjectExt in scope resolve to the inherent method
by accident (inherent methods win over trait methods in method
resolution) and silently drop the type-guard wrap altogether. The
_or suffix names the intent: guard the Option → Result step
at the same call, matching the pre-lift .as_object_mut(). ok_or_else(...) chain.
§#[must_use]
Every consumer threads the ? short-circuit onto its handler’s
Result<_, anyhow::Error> return — dropping the guard swallows
the underlying type-mismatch entirely, which is never the intended
semantic at any of the three pre-lift consumers (each downstream
md.entry(...).or_insert_with(...) / annot.insert(...) mutation
depends on the returned &mut Map reference).
Theory anchor: THEORY.md §VI.1 (generation over composition — the
.as_object_mut().ok_or_else(|| anyhow!("<slot> is not an object")) guard-shape recurred at three hand-authored sites past
the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
ONE substrate owner here). THEORY.md §II.1 invariant 5 (composition
preserves proofs — a regression that drifts the guard message
wording at ONE site surfaces here at the substrate pin rather than
as silent operator-facing skew across every SSA-time
metadata_object_mut + inject_annotations mutation).
Traits§
- Json
MapObject Entry Ext - Substrate extension trait over
serde_json::Map<String, Value>— the ONE substrate owner of the.entry(<key>).or_insert_with(|| Value::Object(<empty>))seed-then-guard shape every JSON-mutating helper hand-authored at the “walk into this object slot on the parent map, seeding an empty object if the slot is absent, or fail loud if the slot exists but is a non-object” boundary. - Json
MapStr Ext - Substrate extension trait over
serde_json::Map<String, Value>— the ONE substrate owner of themap.insert(<key>.into(), Value::String(<val>.into()))string-slot insertion shape every JSON-mutating helper in the workspace hand-authored at each callsite. - Value
Object Ext - Substrate extension trait over
serde_json::Value— the ONE substrate owner of the.as_object_mut().ok_or_else(|| anyhow!( "<slot> is not an object"))guard-shape. See the module docs for the full callsite audit + the naming rationale (whyas_object_mut_orand notas_object_mut).