harn_vm/observability/
execution_scope.rs1use std::cell::RefCell;
22use std::fmt;
23use std::str::FromStr;
24use std::sync::Arc;
25
26pub const EXECUTION_ID_PREFIX: &str = "hxe-";
28
29#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct ExecutionId(Arc<str>);
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
38#[error("invalid Harn execution identity")]
39pub struct InvalidExecutionId;
40
41impl ExecutionId {
42 pub fn mint() -> Self {
45 Self(Arc::from(format!(
46 "{EXECUTION_ID_PREFIX}{}",
47 uuid::Uuid::now_v7()
48 )))
49 }
50
51 pub fn parse(candidate: &str) -> Result<Self, InvalidExecutionId> {
54 let raw = candidate
55 .strip_prefix(EXECUTION_ID_PREFIX)
56 .ok_or(InvalidExecutionId)?;
57 let value = uuid::Uuid::parse_str(raw).map_err(|_| InvalidExecutionId)?;
58 if raw != value.hyphenated().to_string()
59 || value.get_version_num() != 7
60 || value.get_variant() != uuid::Variant::RFC4122
61 {
62 return Err(InvalidExecutionId);
63 }
64 Ok(Self(Arc::from(candidate)))
65 }
66
67 pub fn as_str(&self) -> &str {
68 &self.0
69 }
70}
71
72impl fmt::Debug for ExecutionId {
73 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74 formatter
75 .debug_tuple("ExecutionId")
76 .field(&self.as_str())
77 .finish()
78 }
79}
80
81impl fmt::Display for ExecutionId {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 formatter.write_str(self.as_str())
84 }
85}
86
87impl AsRef<str> for ExecutionId {
88 fn as_ref(&self) -> &str {
89 self.as_str()
90 }
91}
92
93impl std::ops::Deref for ExecutionId {
94 type Target = str;
95
96 fn deref(&self) -> &Self::Target {
97 self.as_str()
98 }
99}
100
101impl FromStr for ExecutionId {
102 type Err = InvalidExecutionId;
103
104 fn from_str(candidate: &str) -> Result<Self, Self::Err> {
105 Self::parse(candidate)
106 }
107}
108
109impl serde::Serialize for ExecutionId {
110 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111 where
112 S: serde::Serializer,
113 {
114 serializer.serialize_str(self.as_str())
115 }
116}
117
118impl<'de> serde::Deserialize<'de> for ExecutionId {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: serde::Deserializer<'de>,
122 {
123 let candidate = <String as serde::Deserialize>::deserialize(deserializer)?;
124 Self::parse(&candidate).map_err(serde::de::Error::custom)
125 }
126}
127
128thread_local! {
129 static ACTIVE_EXECUTION_SCOPE_STACK: RefCell<Vec<ExecutionId>> = const { RefCell::new(Vec::new()) };
130}
131
132pub fn mint_execution_scope() -> ExecutionId {
135 ExecutionId::mint()
136}
137
138#[must_use = "dropping the guard immediately pops the execution scope"]
142pub struct ExecutionScopeGuard {
143 _private: (),
144}
145
146impl Drop for ExecutionScopeGuard {
147 fn drop(&mut self) {
148 ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| {
149 stack.borrow_mut().pop();
150 });
151 }
152}
153
154pub fn enter_execution_scope(scope: ExecutionId) -> ExecutionScopeGuard {
157 ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| stack.borrow_mut().push(scope));
158 ExecutionScopeGuard { _private: () }
159}
160
161pub fn current_execution_scope() -> Option<ExecutionId> {
164 ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| stack.borrow().last().cloned())
165}
166
167pub(crate) fn swap_execution_scope_stack(replacement: Vec<ExecutionId>) -> Vec<ExecutionId> {
172 ACTIVE_EXECUTION_SCOPE_STACK
173 .with(|stack| std::mem::replace(&mut *stack.borrow_mut(), replacement))
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn current_returns_none_when_nothing_pushed() {
182 std::thread::spawn(|| {
184 assert_eq!(current_execution_scope(), None);
185 })
186 .join()
187 .unwrap();
188 }
189
190 #[test]
191 fn guard_pops_on_drop_and_inner_shadows_outer() {
192 std::thread::spawn(|| {
193 let outer = mint_execution_scope();
194 let inner = mint_execution_scope();
195 assert_ne!(outer, inner);
196 let _o = enter_execution_scope(outer.clone());
197 assert_eq!(current_execution_scope().as_deref(), Some(&*outer));
198 {
199 let _i = enter_execution_scope(inner.clone());
200 assert_eq!(current_execution_scope().as_deref(), Some(&*inner));
201 }
202 assert_eq!(current_execution_scope().as_deref(), Some(&*outer));
203 })
204 .join()
205 .unwrap();
206 }
207
208 #[test]
209 fn parse_and_serde_reject_noncanonical_or_non_v7_ids() {
210 let minted = ExecutionId::mint();
211 assert_eq!(ExecutionId::parse(minted.as_str()), Ok(minted));
212
213 let valid = "hxe-019c13e0-8080-7000-8000-000000000001";
214 assert_eq!(ExecutionId::parse(valid).unwrap().as_str(), valid);
215 assert_eq!(
216 serde_json::to_string(&ExecutionId::parse(valid).unwrap()).unwrap(),
217 format!("\"{valid}\"")
218 );
219 assert_eq!(
220 serde_json::from_str::<ExecutionId>(&format!("\"{valid}\""))
221 .unwrap()
222 .as_str(),
223 valid
224 );
225
226 for invalid in [
227 "cloud-run-id",
228 "hxe-019C13E0-8080-7000-8000-000000000001",
229 "hxe-019c13e0-8080-4000-8000-000000000001",
230 ] {
231 assert_eq!(ExecutionId::parse(invalid), Err(InvalidExecutionId));
232 assert!(serde_json::from_str::<ExecutionId>(&format!("\"{invalid}\"")).is_err());
233 }
234 }
235}