@@ -8,7 +8,7 @@ mod emitter;
88mod executor;
99mod model;
1010
11- use tucana:: shared:: { ExecutionFlow , NodeFunction , Value } ;
11+ use tucana:: shared:: { ExecutionFlow , NodeExecutionResult , NodeFunction , Value } ;
1212
1313use crate :: handler:: registry:: FunctionStore ;
1414use 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+
3240impl 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