1use serde::{Deserialize, Serialize};
2use sha2::{Digest, Sha256};
3
4use crate::{ModuleRef, ProcessRef};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub(crate) struct LashlangExecutionContext {
8 pub(crate) module_ref: ModuleRef,
9 pub(crate) entry: LashlangExecutionEntry,
10}
11
12impl LashlangExecutionContext {
13 pub(crate) fn main(module_ref: ModuleRef) -> Self {
14 Self {
15 module_ref,
16 entry: LashlangExecutionEntry::Main,
17 }
18 }
19
20 pub(crate) fn process(
21 module_ref: ModuleRef,
22 process_ref: ProcessRef,
23 process_name: impl Into<String>,
24 ) -> Self {
25 Self {
26 module_ref,
27 entry: LashlangExecutionEntry::Process {
28 process_ref,
29 process_name: process_name.into(),
30 },
31 }
32 }
33
34 pub(crate) fn builder(&self) -> LashlangExecutionSiteBuilder<'_> {
35 LashlangExecutionSiteBuilder { context: self }
36 }
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub(crate) enum LashlangExecutionEntry {
41 Main,
42 Process {
43 process_ref: ProcessRef,
44 process_name: String,
45 },
46}
47
48impl LashlangExecutionEntry {
49 fn stable_key(&self) -> String {
50 match self {
51 Self::Main => "main".to_string(),
52 Self::Process { process_ref, .. } => process_ref_key(process_ref),
53 }
54 }
55
56 fn workflow_owner(&self) -> String {
57 match self {
58 Self::Main => "main".to_string(),
59 Self::Process { process_name, .. } => format!("process:{process_name}"),
60 }
61 }
62}
63
64#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub(crate) struct LashlangAstPath(Vec<u32>);
66
67impl LashlangAstPath {
68 pub(crate) fn from_indices(indices: &[u32]) -> Self {
69 Self(indices.to_vec())
70 }
71
72 fn stable_text(&self) -> String {
73 if self.0.is_empty() {
74 return "root".to_string();
75 }
76 self.0
77 .iter()
78 .map(u32::to_string)
79 .collect::<Vec<_>>()
80 .join(".")
81 }
82
83 fn indices(&self) -> &[u32] {
84 &self.0
85 }
86}
87
88#[derive(Clone, Debug)]
89pub(crate) struct LashlangExecutionSiteBuilder<'context> {
90 context: &'context LashlangExecutionContext,
91}
92
93impl LashlangExecutionSiteBuilder<'_> {
94 pub(crate) fn node_site(
95 &self,
96 path: &LashlangAstPath,
97 kind: impl Into<String>,
98 label: impl Into<String>,
99 ) -> LashlangExecutionSite {
100 let kind = kind.into();
101 let label = label.into();
102 LashlangExecutionSite {
103 node_id: self.node_id(path, &kind),
104 node_kind: kind.clone(),
105 label: label.clone(),
106 branch: None,
107 workflow_site: WorkflowExecutionSite::new(
108 self.context.entry.workflow_owner(),
109 path.indices(),
110 kind,
111 label,
112 ),
113 }
114 }
115
116 pub(crate) fn branch_site(&self, path: &LashlangAstPath) -> LashlangExecutionSite {
117 LashlangExecutionSite {
118 node_id: self.node_id(path, "branch"),
119 node_kind: "branch".to_string(),
120 label: "if".to_string(),
121 branch: Some(LashlangBranchSite {
122 then_edge_id: self.branch_edge_id(path, ProcessBranchSelection::Then),
123 else_edge_id: self.branch_edge_id(path, ProcessBranchSelection::Else),
124 }),
125 workflow_site: WorkflowExecutionSite::new(
126 self.context.entry.workflow_owner(),
127 path.indices(),
128 "branch",
129 "if",
130 ),
131 }
132 }
133
134 pub(crate) fn branch_edge_id(
135 &self,
136 path: &LashlangAstPath,
137 selection: ProcessBranchSelection,
138 ) -> String {
139 let label = match selection {
140 ProcessBranchSelection::Then => "then",
141 ProcessBranchSelection::Else => "else",
142 };
143 stable_id(
144 "edge",
145 &[
146 self.context.module_ref.to_string(),
147 self.context.entry.stable_key(),
148 path.stable_text(),
149 "branch".to_string(),
150 label.to_string(),
151 ],
152 )
153 }
154
155 fn node_id(&self, path: &LashlangAstPath, kind: impl AsRef<str>) -> String {
156 stable_id(
157 kind.as_ref(),
158 &[
159 self.context.module_ref.to_string(),
160 self.context.entry.stable_key(),
161 path.stable_text(),
162 kind.as_ref().to_string(),
163 ],
164 )
165 }
166}
167
168#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
170pub struct WorkflowExecutionSite {
171 pub owner: String,
172 #[serde(default)]
173 pub path: Vec<u32>,
174 pub kind: String,
175 pub label: String,
176}
177
178impl WorkflowExecutionSite {
179 pub fn new(
180 owner: impl Into<String>,
181 path: impl AsRef<[u32]>,
182 kind: impl Into<String>,
183 label: impl Into<String>,
184 ) -> Self {
185 Self {
186 owner: owner.into(),
187 path: path.as_ref().to_vec(),
188 kind: kind.into(),
189 label: label.into(),
190 }
191 }
192
193 pub(crate) fn same_location(&self, other: &Self) -> bool {
194 self.owner == other.owner && self.path == other.path
195 }
196}
197
198fn stable_id(prefix: &str, parts: &[String]) -> String {
199 let mut hasher = Sha256::new();
200 for part in parts {
201 hasher.update(part.as_bytes());
202 hasher.update([0]);
203 }
204 let hash = format!("{:x}", hasher.finalize());
205 format!("{prefix}:{}", &hash[..24])
206}
207
208pub fn process_ref_key(process_ref: &ProcessRef) -> String {
209 format!("{}:{}", process_ref.component, process_ref.pos)
210}
211
212#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
213pub struct LashlangExecutionSite {
214 pub node_id: String,
215 pub node_kind: String,
216 pub label: String,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub branch: Option<LashlangBranchSite>,
219 pub workflow_site: WorkflowExecutionSite,
220}
221
222#[derive(Clone, Debug, PartialEq, Eq)]
223pub struct LashlangExecutionCallSite {
224 pub site: LashlangExecutionSite,
225 pub occurrence: u64,
226}
227
228#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
229pub struct LashlangBranchSite {
230 pub then_edge_id: String,
231 pub else_edge_id: String,
232}
233
234#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
235#[serde(rename_all = "snake_case")]
236pub enum ProcessBranchSelection {
237 Then,
238 Else,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct LashlangExecutionChild {
243 pub process_id: String,
244 pub module_ref: ModuleRef,
245 pub process_ref: ProcessRef,
246 pub process_name: String,
247}
248
249#[derive(Clone, Debug, PartialEq, Eq)]
250pub enum LashlangExecutionObservation {
251 NodeStarted {
252 site: LashlangExecutionSite,
253 occurrence: u64,
254 },
255 NodeCompleted {
256 site: LashlangExecutionSite,
257 occurrence: u64,
258 },
259 NodeFailed {
260 site: LashlangExecutionSite,
261 occurrence: u64,
262 error: String,
263 },
264 BranchSelected {
265 site: LashlangExecutionSite,
266 occurrence: u64,
267 edge_id: String,
268 selected: ProcessBranchSelection,
269 },
270 ChildStarted {
271 site: LashlangExecutionSite,
272 occurrence: u64,
273 child: LashlangExecutionChild,
274 },
275}