Skip to content

Commit a12c168

Browse files
committed
feat: made execution save all results
1 parent c1b2ff1 commit a12c168

3 files changed

Lines changed: 375 additions & 44 deletions

File tree

crates/taurus-core/src/runtime/engine.rs

Lines changed: 216 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ mod emitter;
88
mod executor;
99
mod model;
1010

11-
use tucana::shared::{ExecutionFlow, NodeFunction, Value};
11+
use tucana::shared::{ExecutionFlow, NodeExecutionResult, NodeFunction, Value};
1212

1313
use crate::handler::registry::FunctionStore;
1414
use crate::runtime::execution::value_store::ValueStore;
@@ -29,6 +29,14 @@ pub struct ExecutionEngine {
2929
handlers: FunctionStore,
3030
}
3131

32+
/// Full result of one engine execution, including per-node results for reporting.
33+
#[derive(Debug, Clone)]
34+
pub struct EngineExecutionReport {
35+
pub signal: Signal,
36+
pub exit_reason: ExitReason,
37+
pub node_execution_results: Vec<NodeExecutionResult>,
38+
}
39+
3240
impl Default for ExecutionEngine {
3341
fn default() -> Self {
3442
Self::new()
@@ -51,13 +59,14 @@ impl ExecutionEngine {
5159
respond_emitter: Option<&dyn RespondEmitter>,
5260
with_trace: bool,
5361
) -> (Signal, ExitReason) {
54-
self.execute_flow_with_execution_id(
62+
let report = self.execute_flow_with_execution_id_report(
5563
ExecutionId::new_v4(),
5664
flow,
5765
remote,
5866
respond_emitter,
5967
with_trace,
60-
)
68+
);
69+
(report.signal, report.exit_reason)
6170
}
6271

6372
/// Execute an `ExecutionFlow` with a caller-provided execution id.
@@ -69,7 +78,43 @@ impl ExecutionEngine {
6978
respond_emitter: Option<&dyn RespondEmitter>,
7079
with_trace: bool,
7180
) -> (Signal, ExitReason) {
72-
self.execute_graph_with_execution_id(
81+
let report = self.execute_flow_with_execution_id_report(
82+
execution_id,
83+
flow,
84+
remote,
85+
respond_emitter,
86+
with_trace,
87+
);
88+
(report.signal, report.exit_reason)
89+
}
90+
91+
/// Execute an `ExecutionFlow` and return the final signal plus per-node execution results.
92+
pub fn execute_flow_report(
93+
&self,
94+
flow: ExecutionFlow,
95+
remote: Option<&dyn RemoteRuntime>,
96+
respond_emitter: Option<&dyn RespondEmitter>,
97+
with_trace: bool,
98+
) -> EngineExecutionReport {
99+
self.execute_flow_with_execution_id_report(
100+
ExecutionId::new_v4(),
101+
flow,
102+
remote,
103+
respond_emitter,
104+
with_trace,
105+
)
106+
}
107+
108+
/// Execute an `ExecutionFlow` with a caller-provided execution id and return per-node results.
109+
pub fn execute_flow_with_execution_id_report(
110+
&self,
111+
execution_id: ExecutionId,
112+
flow: ExecutionFlow,
113+
remote: Option<&dyn RemoteRuntime>,
114+
respond_emitter: Option<&dyn RespondEmitter>,
115+
with_trace: bool,
116+
) -> EngineExecutionReport {
117+
self.execute_graph_with_execution_id_report(
73118
execution_id,
74119
flow.starting_node_id,
75120
flow.node_functions,
@@ -90,15 +135,16 @@ impl ExecutionEngine {
90135
respond_emitter: Option<&dyn RespondEmitter>,
91136
with_trace: bool,
92137
) -> (Signal, ExitReason) {
93-
self.execute_graph_with_execution_id(
138+
let report = self.execute_graph_with_execution_id_report(
94139
ExecutionId::new_v4(),
95140
start_node_id,
96141
node_functions,
97142
flow_input,
98143
remote,
99144
respond_emitter,
100145
with_trace,
101-
)
146+
);
147+
(report.signal, report.exit_reason)
102148
}
103149

104150
/// Execute a graph described by node list and start node with a caller-provided execution id.
@@ -112,6 +158,50 @@ impl ExecutionEngine {
112158
respond_emitter: Option<&dyn RespondEmitter>,
113159
with_trace: bool,
114160
) -> (Signal, ExitReason) {
161+
let report = self.execute_graph_with_execution_id_report(
162+
execution_id,
163+
start_node_id,
164+
node_functions,
165+
flow_input,
166+
remote,
167+
respond_emitter,
168+
with_trace,
169+
);
170+
(report.signal, report.exit_reason)
171+
}
172+
173+
/// Execute a graph and return the final signal plus per-node execution results.
174+
pub fn execute_graph_report(
175+
&self,
176+
start_node_id: i64,
177+
node_functions: Vec<NodeFunction>,
178+
flow_input: Option<Value>,
179+
remote: Option<&dyn RemoteRuntime>,
180+
respond_emitter: Option<&dyn RespondEmitter>,
181+
with_trace: bool,
182+
) -> EngineExecutionReport {
183+
self.execute_graph_with_execution_id_report(
184+
ExecutionId::new_v4(),
185+
start_node_id,
186+
node_functions,
187+
flow_input,
188+
remote,
189+
respond_emitter,
190+
with_trace,
191+
)
192+
}
193+
194+
/// Execute a graph with a caller-provided execution id and return per-node results.
195+
pub fn execute_graph_with_execution_id_report(
196+
&self,
197+
execution_id: ExecutionId,
198+
start_node_id: i64,
199+
node_functions: Vec<NodeFunction>,
200+
flow_input: Option<Value>,
201+
remote: Option<&dyn RemoteRuntime>,
202+
respond_emitter: Option<&dyn RespondEmitter>,
203+
with_trace: bool,
204+
) -> EngineExecutionReport {
115205
if let Some(emitter) = respond_emitter {
116206
emitter.emit(execution_id, EmitType::StartingExec, null_value());
117207
}
@@ -129,7 +219,11 @@ impl ExecutionEngine {
129219
emitter.emit(execution_id, EmitType::FailedExec, runtime_error.as_value());
130220
}
131221
let signal = Signal::Failure(runtime_error);
132-
return (signal, ExitReason::Failure);
222+
return EngineExecutionReport {
223+
signal,
224+
exit_reason: ExitReason::Failure,
225+
node_execution_results: Vec::new(),
226+
};
133227
}
134228
};
135229

@@ -160,7 +254,11 @@ impl ExecutionEngine {
160254
}
161255
}
162256
let exit_reason = signal.exit_reason();
163-
(signal, exit_reason)
257+
EngineExecutionReport {
258+
signal,
259+
exit_reason,
260+
node_execution_results: value_store.node_execution_results(),
261+
}
164262
}
165263
}
166264

@@ -171,8 +269,8 @@ mod tests {
171269
use std::cell::RefCell;
172270
use tucana::shared::{
173271
InputType, ListValue, NodeParameter, NodeValue, ReferenceValue, Struct, SubFlow,
174-
SubFlowSetting, Value, node_value, reference_value, sub_flow::ExecutionReference,
175-
value::Kind,
272+
SubFlowSetting, Value, node_execution_result, node_value, reference_value,
273+
sub_flow::ExecutionReference, value::Kind,
176274
};
177275

178276
fn literal_param(database_id: i64, runtime_parameter_id: &str, value: Value) -> NodeParameter {
@@ -719,6 +817,114 @@ mod tests {
719817
assert_eq!(expect_success(signal), int_value(42));
720818
}
721819

820+
#[test]
821+
fn execution_report_includes_literal_node_parameter_results() {
822+
let engine = ExecutionEngine::new();
823+
let add_node = node(
824+
1,
825+
"std::number::add",
826+
vec![
827+
literal_param(100, "lhs", int_value(1)),
828+
literal_param(101, "rhs", int_value(2)),
829+
],
830+
None,
831+
);
832+
833+
let report = engine.execute_graph_report(1, vec![add_node], None, None, None, false);
834+
835+
assert_eq!(report.exit_reason, ExitReason::Success);
836+
assert_eq!(report.node_execution_results.len(), 1);
837+
838+
let node_result = &report.node_execution_results[0];
839+
assert_eq!(node_result.node_id, 1);
840+
assert_eq!(node_result.parameter_results.len(), 2);
841+
assert_eq!(node_result.parameter_results[0].value, Some(int_value(1)));
842+
assert_eq!(node_result.parameter_results[1].value, Some(int_value(2)));
843+
844+
match node_result.result.as_ref() {
845+
Some(node_execution_result::Result::Success(value)) => {
846+
assert_eq!(value, &int_value(3));
847+
}
848+
other => panic!("expected node success result, got {:?}", other),
849+
}
850+
}
851+
852+
#[test]
853+
fn execution_report_includes_reference_node_parameter_results() {
854+
let engine = ExecutionEngine::new();
855+
let value_node = node(
856+
1,
857+
"std::control::value",
858+
vec![literal_param(100, "value", int_value(7))],
859+
Some(2),
860+
);
861+
let add_node = node(
862+
2,
863+
"std::number::add",
864+
vec![
865+
node_result_ref_param(200, "lhs", 1),
866+
literal_param(201, "rhs", int_value(5)),
867+
],
868+
None,
869+
);
870+
871+
let report =
872+
engine.execute_graph_report(1, vec![value_node, add_node], None, None, None, false);
873+
874+
assert_eq!(report.exit_reason, ExitReason::Success);
875+
assert_eq!(report.node_execution_results.len(), 2);
876+
877+
let node_result = &report.node_execution_results[1];
878+
assert_eq!(node_result.node_id, 2);
879+
assert_eq!(node_result.parameter_results.len(), 2);
880+
assert_eq!(node_result.parameter_results[0].value, Some(int_value(7)));
881+
assert_eq!(node_result.parameter_results[1].value, Some(int_value(5)));
882+
883+
match node_result.result.as_ref() {
884+
Some(node_execution_result::Result::Success(value)) => {
885+
assert_eq!(value, &int_value(12));
886+
}
887+
other => panic!("expected node success result, got {:?}", other),
888+
}
889+
}
890+
891+
#[test]
892+
fn execution_report_includes_respond_node_parameter_results() {
893+
let engine = ExecutionEngine::new();
894+
let respond_node = node(
895+
1,
896+
"rest::control::respond",
897+
vec![
898+
literal_param(100, "http_status_code", int_value(200)),
899+
literal_param(101, "headers", empty_struct_value()),
900+
literal_param(102, "payload", string_value("hello")),
901+
],
902+
None,
903+
);
904+
905+
let report = engine.execute_graph_report(1, vec![respond_node], None, None, None, false);
906+
907+
assert_eq!(report.exit_reason, ExitReason::Success);
908+
assert_eq!(report.node_execution_results.len(), 1);
909+
910+
let node_result = &report.node_execution_results[0];
911+
assert_eq!(node_result.node_id, 1);
912+
assert_eq!(node_result.parameter_results.len(), 3);
913+
assert_eq!(node_result.parameter_results[0].value, Some(int_value(200)));
914+
assert_eq!(
915+
node_result.parameter_results[1].value,
916+
Some(empty_struct_value())
917+
);
918+
assert_eq!(
919+
node_result.parameter_results[2].value,
920+
Some(string_value("hello"))
921+
);
922+
assert!(matches!(
923+
node_result.result,
924+
Some(node_execution_result::Result::Success(_))
925+
));
926+
}
927+
722928
#[test]
723929
fn emitter_emits_start_and_finish_for_successful_execution() {
724930
let engine = ExecutionEngine::new();

0 commit comments

Comments
 (0)