Skip to content

Commit 46bd289

Browse files
committed
feat: added pre-exec step as flow compiler
1 parent 4648b55 commit 46bd289

4 files changed

Lines changed: 877 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//! Flow compiler for runtime execution plans.
2+
3+
use std::collections::HashMap;
4+
5+
use tucana::shared::{NodeFunction, node_value};
6+
7+
use crate::{
8+
runtime::engine::model::{
9+
CompiledArg, CompiledFlow, CompiledNode, CompiledParameter, NodeExecutionTarget,
10+
},
11+
types::errors::runtime_error::RuntimeError,
12+
};
13+
14+
#[derive(Debug)]
15+
pub enum CompileError {
16+
DuplicateNodeId {
17+
node_id: i64,
18+
},
19+
StartNodeMissing {
20+
node_id: i64,
21+
},
22+
NextNodeMissing {
23+
node_id: i64,
24+
next_node_id: i64,
25+
},
26+
ParameterValueMissing {
27+
node_id: i64,
28+
parameter_index: usize,
29+
},
30+
}
31+
32+
impl CompileError {
33+
pub fn as_runtime_error(&self) -> RuntimeError {
34+
match self {
35+
CompileError::DuplicateNodeId { node_id } => RuntimeError::new(
36+
"T-RT-000000",
37+
"FlowCompileError",
38+
format!("Duplicate node id in flow: {}", node_id),
39+
),
40+
CompileError::StartNodeMissing { node_id } => RuntimeError::new(
41+
"T-RT-000000",
42+
"FlowCompileError",
43+
format!("Start node not found in flow: {}", node_id),
44+
),
45+
CompileError::NextNodeMissing {
46+
node_id,
47+
next_node_id,
48+
} => RuntimeError::new(
49+
"T-RT-000000",
50+
"FlowCompileError",
51+
format!(
52+
"Node {} points to missing next node {}",
53+
node_id, next_node_id
54+
),
55+
),
56+
CompileError::ParameterValueMissing {
57+
node_id,
58+
parameter_index,
59+
} => RuntimeError::new(
60+
"T-RT-000000",
61+
"FlowCompileError",
62+
format!(
63+
"Node {} parameter {} does not contain a value",
64+
node_id, parameter_index
65+
),
66+
),
67+
}
68+
}
69+
}
70+
71+
pub fn compile_flow(
72+
start_node_id: i64,
73+
nodes: Vec<NodeFunction>,
74+
) -> Result<CompiledFlow, CompileError> {
75+
let mut node_idx_by_id = HashMap::with_capacity(nodes.len());
76+
for (idx, node) in nodes.iter().enumerate() {
77+
if node_idx_by_id.insert(node.database_id, idx).is_some() {
78+
return Err(CompileError::DuplicateNodeId {
79+
node_id: node.database_id,
80+
});
81+
}
82+
}
83+
84+
let start_idx = match node_idx_by_id.get(&start_node_id).copied() {
85+
Some(idx) => idx,
86+
None => {
87+
return Err(CompileError::StartNodeMissing {
88+
node_id: start_node_id,
89+
});
90+
}
91+
};
92+
93+
let mut compiled_nodes = Vec::with_capacity(nodes.len());
94+
for node in nodes {
95+
let next_idx = match node.next_node_id {
96+
Some(next_id) => match node_idx_by_id.get(&next_id).copied() {
97+
Some(idx) => Some(idx),
98+
None => {
99+
return Err(CompileError::NextNodeMissing {
100+
node_id: node.database_id,
101+
next_node_id: next_id,
102+
});
103+
}
104+
},
105+
None => None,
106+
};
107+
108+
let execution_target = execution_target_for(&node);
109+
110+
let mut parameters = Vec::with_capacity(node.parameters.len());
111+
for (parameter_index, parameter) in node.parameters.iter().enumerate() {
112+
let Some(node_value) = parameter.value.as_ref() else {
113+
return Err(CompileError::ParameterValueMissing {
114+
node_id: node.database_id,
115+
parameter_index,
116+
});
117+
};
118+
let Some(value) = node_value.value.as_ref() else {
119+
return Err(CompileError::ParameterValueMissing {
120+
node_id: node.database_id,
121+
parameter_index,
122+
});
123+
};
124+
125+
let arg = match value {
126+
node_value::Value::LiteralValue(v) => CompiledArg::Literal(v.clone()),
127+
node_value::Value::ReferenceValue(r) => CompiledArg::Reference(r.clone()),
128+
node_value::Value::NodeFunctionId(id) => CompiledArg::DeferredNode(*id),
129+
};
130+
131+
parameters.push(CompiledParameter {
132+
runtime_parameter_id: parameter.runtime_parameter_id.clone(),
133+
arg,
134+
});
135+
}
136+
137+
compiled_nodes.push(CompiledNode {
138+
id: node.database_id,
139+
handler_id: node.runtime_function_id,
140+
execution_target,
141+
next_idx,
142+
parameters,
143+
});
144+
}
145+
146+
Ok(CompiledFlow {
147+
start_idx,
148+
nodes: compiled_nodes,
149+
node_idx_by_id,
150+
})
151+
}
152+
153+
fn execution_target_for(node: &NodeFunction) -> NodeExecutionTarget {
154+
if node.definition_source.is_empty() || node.definition_source == "taurus" {
155+
NodeExecutionTarget::Local
156+
} else {
157+
NodeExecutionTarget::Remote {
158+
service: node.definition_source.clone(),
159+
}
160+
}
161+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! Respond emitter abstraction used by the engine.
2+
3+
use tucana::shared::Value;
4+
5+
/// Execution lifecycle event emitted by the runtime engine.
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7+
pub enum EmitType {
8+
/// Top-level flow execution has started.
9+
StartingExec,
10+
/// An intermediate `Signal::Respond` was emitted during execution.
11+
OngoingExec,
12+
/// Flow execution reached a non-failure terminal state.
13+
FinishedExec,
14+
/// Flow execution ended with a runtime failure.
15+
FailedExec,
16+
}
17+
18+
/// Callback interface for streaming execution lifecycle events.
19+
pub trait RespondEmitter {
20+
fn emit(&self, emit_type: EmitType, value: Value);
21+
}
22+
23+
impl<F> RespondEmitter for F
24+
where
25+
F: Fn(EmitType, Value) + ?Sized,
26+
{
27+
fn emit(&self, emit_type: EmitType, value: Value) {
28+
self(emit_type, value);
29+
}
30+
}

0 commit comments

Comments
 (0)