1use super::compile::{CompileOptions, CompiledWorkflow, Compiler};
5use super::fingerprint::Fingerprint;
6use crate::base::{AuthoredPath, ProgramId, StepKind, TaskIx, WorkflowIx};
7use crate::def::Status;
8use crate::function::Registry;
9use std::collections::HashMap;
10use std::fmt;
11use std::sync::Arc;
12
13impl Compiler {
14 pub fn link(&self, workflows: impl IntoIterator<Item = CompiledWorkflow>) -> Program {
46 let mut workflows: Vec<CompiledWorkflow> = workflows.into_iter().collect();
47 for w in &workflows {
48 assert!(
49 w.registry.ptr_eq(&self.registry),
50 "cannot link workflow `{}`: it was compiled under a different registry from \
51 this compiler's",
52 w.id
53 );
54 }
55 workflows.sort_by_key(|w| w.priority);
56 let count =
57 u32::try_from(workflows.len()).expect("a program holds at most u32::MAX workflows");
58
59 let mut infos = Vec::with_capacity(workflows.len());
60 let mut workflow_ids = Vec::with_capacity(workflows.len());
61 let mut tasks = Vec::new();
62 let mut task_ids = Vec::new();
63 let mut task_base = Vec::with_capacity(workflows.len());
64 let mut all = Vec::new();
65 let mut by_channel: HashMap<String, Vec<WorkflowIx>> = HashMap::new();
66 for (ix, w) in (0..count).map(WorkflowIx::new).zip(&workflows) {
67 task_base
68 .push(u32::try_from(tasks.len()).expect("a program holds at most u32::MAX tasks"));
69 workflow_ids.push(Arc::from(w.id.as_str()));
70 infos.push(WorkflowInfo {
71 ix,
72 id: w.id.clone(),
73 channel: w.channel.clone(),
74 priority: w.priority,
75 status: w.status,
76 version: w.version,
77 fingerprint: w.fingerprint,
78 });
79 for meta in &w.tasks {
80 let task = TaskIx::new(
81 u32::try_from(tasks.len()).expect("a program holds at most u32::MAX tasks"),
82 );
83 task_ids.push(Arc::from(meta.id.as_str()));
84 tasks.push(TaskInfo {
85 ix: task,
86 id: meta.id.clone(),
87 function: meta.function.clone(),
88 workflow: ix,
89 kind: meta.kind,
90 path: meta.path.clone(),
91 });
92 }
93 if w.status == Status::Active {
94 all.push(ix);
95 by_channel.entry(w.channel.clone()).or_default().push(ix);
96 }
97 }
98
99 Program {
100 inner: Arc::new(Inner {
101 id: ProgramId::next(),
102 registry: self.registry.clone(),
103 options: self.options.clone(),
104 workflows,
105 infos,
106 workflow_ids,
107 tasks,
108 task_ids,
109 task_base,
110 all,
111 by_channel,
112 }),
113 }
114 }
115}
116
117struct Inner {
118 id: ProgramId,
119 registry: Registry,
120 options: CompileOptions,
122 workflows: Vec<CompiledWorkflow>,
123 infos: Vec<WorkflowInfo>,
124 workflow_ids: Vec<Arc<str>>,
126 tasks: Vec<TaskInfo>,
127 task_ids: Vec<Arc<str>>,
129 task_base: Vec<u32>,
130 all: Vec<WorkflowIx>,
132 by_channel: HashMap<String, Vec<WorkflowIx>>,
134}
135
136#[derive(Clone)]
141pub struct Program {
142 inner: Arc<Inner>,
143}
144
145impl Program {
146 pub fn id(&self) -> ProgramId {
148 self.inner.id
149 }
150
151 pub fn workflows(&self) -> &[WorkflowInfo] {
154 &self.inner.infos
155 }
156
157 pub fn tasks(&self) -> &[TaskInfo] {
159 &self.inner.tasks
160 }
161
162 pub fn task(&self, ix: TaskIx) -> &TaskInfo {
168 &self.inner.tasks[ix.index() as usize]
169 }
170
171 pub fn registry(&self) -> &Registry {
173 &self.inner.registry
174 }
175
176 pub(crate) fn compiled(&self, ix: WorkflowIx) -> &CompiledWorkflow {
178 &self.inner.workflows[ix.index() as usize]
179 }
180
181 pub(crate) fn active(&self, channel: Option<&str>) -> &[WorkflowIx] {
184 match channel {
185 None => &self.inner.all,
186 Some(c) => self.inner.by_channel.get(c).map_or(&[], Vec::as_slice),
187 }
188 }
189
190 pub(crate) fn task_base(&self, ix: WorkflowIx) -> u32 {
192 self.inner.task_base[ix.index() as usize]
193 }
194
195 pub(crate) fn ids(&self, ix: WorkflowIx) -> (&Arc<str>, &[Arc<str>]) {
198 let base = self.task_base(ix) as usize;
199 let count = self.compiled(ix).tasks.len();
200 (
201 &self.inner.workflow_ids[ix.index() as usize],
202 &self.inner.task_ids[base..base + count],
203 )
204 }
205
206 pub(crate) fn options(&self) -> &CompileOptions {
208 &self.inner.options
209 }
210}
211
212impl fmt::Debug for Program {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 f.debug_struct("Program")
216 .field("id", &self.inner.id)
217 .field(
218 "workflows",
219 &self.inner.infos.iter().map(|w| &w.id).collect::<Vec<_>>(),
220 )
221 .finish_non_exhaustive()
222 }
223}
224
225#[derive(Clone, Debug)]
227#[non_exhaustive]
228pub struct WorkflowInfo {
229 pub ix: WorkflowIx,
231 pub id: String,
233 pub channel: String,
235 pub priority: u32,
237 pub status: Status,
239 pub version: u32,
241 pub fingerprint: Fingerprint,
243}
244
245#[derive(Clone, Debug)]
247#[non_exhaustive]
248pub struct TaskInfo {
249 pub ix: TaskIx,
251 pub id: String,
253 pub function: String,
255 pub workflow: WorkflowIx,
257 pub kind: StepKind,
259 pub path: AuthoredPath,
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::def::{ParseOptions, parse};
267 use serde_json::{Value, json};
268
269 fn workflow(id: &str, channel: &str, status: &str, tasks: usize) -> Value {
270 let tasks: Vec<Value> = (0..tasks)
271 .map(|i| json!({"id": format!("t{i}"), "function": {"name": "log", "input": {"message": "m"}}}))
272 .collect();
273 json!({"id": id, "channel": channel, "status": status, "tasks": tasks})
274 }
275
276 fn link(defs: &[Value]) -> Program {
277 let c = Registry::default().compiler(CompileOptions::default());
278 c.link(defs.iter().map(|d| {
279 let def = parse(d, ParseOptions::default()).def.expect("parses");
280 c.compile_one(&def).expect("compiles").workflow
281 }))
282 }
283
284 fn ixs(list: &[WorkflowIx]) -> Vec<u32> {
285 list.iter().map(|ix| ix.index()).collect()
286 }
287
288 #[test]
289 fn active_lists_split_by_channel_and_status() {
290 let program = link(&[
291 workflow("a", "a", "active", 1),
292 workflow("b", "b", "active", 1),
293 workflow("p", "a", "paused", 1),
294 workflow("d", "default", "active", 1),
295 workflow("x", "default", "archived", 1),
296 workflow("a2", "a", "active", 1),
297 ]);
298 assert_eq!(program.workflows().len(), 6);
299 assert_eq!(ixs(program.active(Some("a"))), [0, 5]);
300 assert_eq!(ixs(program.active(Some("b"))), [1]);
301 assert_eq!(ixs(program.active(Some("default"))), [3]);
302 assert_eq!(ixs(program.active(None)), [0, 1, 3, 5]);
303 assert!(program.active(Some("zzz")).is_empty());
304 }
305
306 #[test]
307 fn tasks_of_inactive_workflows_still_have_indices() {
308 let program = link(&[
309 workflow("a", "x", "paused", 2),
310 workflow("b", "x", "active", 3),
311 workflow("e", "x", "active", 1),
312 ]);
313 let bases: Vec<u32> = (0..3)
314 .map(|i| program.task_base(WorkflowIx::new(i)))
315 .collect();
316 assert_eq!(bases, [0, 2, 5]);
317 assert_eq!(program.tasks().len(), 6);
318 let b = WorkflowIx::new(1);
319 assert_eq!(program.compiled(b).id(), "b");
320 assert_eq!(program.task(TaskIx::new(program.task_base(b))).id, "t0");
321 assert_eq!(program.task(TaskIx::new(program.task_base(b))).workflow, b);
322 }
323}