From 59fea7832325f8d1dd1085b5da9fd7d42d559f08 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:18:51 +0100 Subject: [PATCH 01/58] feat: reworked execution logic --- taurus/src/context/executor.rs | 425 ++++++++++++++++++++++++--------- 1 file changed, 316 insertions(+), 109 deletions(-) diff --git a/taurus/src/context/executor.rs b/taurus/src/context/executor.rs index 2622f8a..55279b8 100644 --- a/taurus/src/context/executor.rs +++ b/taurus/src/context/executor.rs @@ -1,8 +1,11 @@ use crate::context::argument::{Argument, ParameterNode}; use crate::context::context::{Context, ContextResult}; -use crate::context::registry::FunctionStore; +use crate::context::registry::{FunctionStore, HandlerFunctionEntry}; use crate::context::signal::Signal; +use crate::debug::trace::{ArgKind, ArgTrace, EdgeKind, Outcome, ReferenceKind}; +use crate::debug::tracer::{ExecutionTracer, Tracer}; use crate::error::RuntimeError; + use std::collections::HashMap; use tucana::shared::NodeFunction; @@ -13,135 +16,339 @@ pub struct Executor<'a> { impl<'a> Executor<'a> { pub fn new(functions: &'a FunctionStore, nodes: HashMap) -> Self { - Executor { functions, nodes } + Self { functions, nodes } + } + + /// This is now the ONLY execution entry point. + pub fn execute(&self, start_node_id: i64, ctx: &mut Context) -> Signal { + let mut tracer = Tracer::new(); + + let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); + + if let Some(run) = tracer.take_run() { + println!("{}", crate::debug::render::render_trace(&run)); + } + + signal } - pub fn execute(&self, starting_node_id: i64, ctx: &mut Context) -> Signal { - let mut current_node_id = starting_node_id; + // Main execution loop + fn execute_call( + &self, + start_node_id: i64, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + ) -> (Signal, u64) { + let mut current = start_node_id; + + let mut call_root_frame: Option = None; + let mut previous_frame: Option = None; loop { - let node = match self.nodes.get(¤t_node_id) { - None => { - return Signal::Failure(RuntimeError::simple( - "NodeNotFound", - format!( - "The node with the database id: {} was not found", - current_node_id - ), - )); + let (signal, frame_id) = self.execute_single_node(current, ctx, tracer); + + if call_root_frame.is_none() { + call_root_frame = Some(frame_id); + } + + // Link linear NEXT chain + if let Some(prev) = previous_frame { + tracer.link_child(prev, frame_id, EdgeKind::Next); + } + previous_frame = Some(frame_id); + + match signal { + Signal::Success(_) => { + let node = self.nodes.get(¤t).unwrap(); + + if let Some(next) = node.next_node_id { + current = next; + continue; + } + + return (signal, call_root_frame.unwrap()); } - Some(n) => n.clone(), - }; - - let entry = match self.functions.get(node.runtime_function_id.as_str()) { - None => { - return Signal::Failure(RuntimeError::simple( - "FunctionNotFound", - format!( - "The function {} (database id: {}) was not found", - node.runtime_function_id, node.database_id - ), - )); + + _ => return (signal, call_root_frame.unwrap()), + } + } + } + + // executes a single node + fn execute_single_node( + &self, + node_id: i64, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + ) -> (Signal, u64) { + let node = match self.nodes.get(&node_id) { + Some(n) => n.clone(), + None => { + let err = + RuntimeError::simple("NodeNotFound", format!("Node {} not found", node_id)); + return (Signal::Failure(err), 0); + } + }; + + let entry = match self.functions.get(node.runtime_function_id.as_str()) { + Some(e) => e, + None => { + let err = RuntimeError::simple( + "FunctionNotFound", + format!("Function {} not found", node.runtime_function_id), + ); + return (Signal::Failure(err), 0); + } + }; + + let frame_id = tracer.enter_node(node.database_id, node.runtime_function_id.as_str()); + + // ---- Build args + let mut args = match self.build_args(&node, ctx, tracer, frame_id) { + Ok(a) => a, + Err(e) => { + ctx.insert_error(node.database_id, e.clone()); + tracer.exit_node( + frame_id, + Outcome::Failure { + error_preview: format!("{:#?}", e), + }, + ); + return (Signal::Failure(e), frame_id); + } + }; + + // ---- Force eager args + if let Err((sig, outcome)) = + self.force_eager_args(&node, entry, &mut args, ctx, tracer, frame_id) + { + tracer.exit_node(frame_id, outcome); + return (sig, frame_id); + } + + // ---- Invoke handler + let result = self.invoke_handler(entry, &args, ctx, tracer); + + // ---- Commit result + let final_signal = self.commit_result(&node, result, ctx, tracer, frame_id); + + (final_signal, frame_id) + } + + fn build_args( + &self, + node: &NodeFunction, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + frame_id: u64, + ) -> Result, RuntimeError> { + let mut args = Vec::with_capacity(node.parameters.len()); + + for (i, param) in node.parameters.iter().enumerate() { + let node_value = param.value.as_ref().ok_or_else(|| { + RuntimeError::simple_str("NodeValueNotFound", "Missing param value") + })?; + + let value = node_value.value.as_ref().ok_or_else(|| { + RuntimeError::simple_str("NodeValueNotFound", "Missing inner value") + })?; + + match value { + tucana::shared::node_value::Value::LiteralValue(v) => { + tracer.record_arg( + frame_id, + ArgTrace { + index: i, + kind: ArgKind::Literal, + preview: format!("{:?}", v), + }, + ); + args.push(Argument::Eval(v.clone())); } - Some(f) => f, - }; - - let mut args: Vec = Vec::with_capacity(node.parameters.len()); - for parameter in &node.parameters { - let node_value = match ¶meter.value { - Some(v) => v, - None => { - return Signal::Failure(RuntimeError::simple_str( - "NodeValueNotFound", - "Missing parameter value: {}", - )); + + tucana::shared::node_value::Value::ReferenceValue(r) => match ctx.get(r.clone()) { + ContextResult::Success(v) => { + let reference = match r.target { + Some(ref_value) => match ref_value { + tucana::shared::reference_value::Target::FlowInput(_) => { + ReferenceKind::FlowInput + } + tucana::shared::reference_value::Target::NodeId(id) => { + ReferenceKind::Result { node_id: id } + } + tucana::shared::reference_value::Target::InputType(input_type) => { + ReferenceKind::InputType { + node_id: input_type.node_id, + input_index: input_type.input_index, + parameter_index: input_type.parameter_index, + } + } + }, + None => ReferenceKind::Empty, + }; + + tracer.record_arg( + frame_id, + ArgTrace { + index: i, + kind: ArgKind::Reference { + reference: reference, + hit: true, + }, + preview: format!("ctx.get({:?}) -> {:?}", r, v), + }, + ); + args.push(Argument::Eval(v)); } - }; - let value = match &node_value.value { - Some(v) => v, - None => { - return Signal::Failure(RuntimeError::simple_str( - "NodeValueNotFound", - "Missing inner value", + ContextResult::Error(e) => return Err(e), + ContextResult::NotFound => { + return Err(RuntimeError::simple_str( + "ReferenceValueNotFound", + "Referenced node not executed", )); } - }; + }, - match value { - tucana::shared::node_value::Value::LiteralValue(val) => { - args.push(Argument::Eval(val.clone())) - } - tucana::shared::node_value::Value::ReferenceValue(reference) => { - let value = ctx.get(reference.node_id); - match value { - ContextResult::Error(runtime_error) => { - return Signal::Failure(runtime_error); - } - ContextResult::Success(result) => { - args.push(Argument::Eval(result.clone())); - } - ContextResult::NotFound => { - return Signal::Failure(RuntimeError::simple_str( - "ReferenceValueNotFound", - "The given node has not been executed but referenced.", - )); - } - } - } - tucana::shared::node_value::Value::NodeFunctionId(id) => { - args.push(Argument::Thunk(*id)) - } + tucana::shared::node_value::Value::NodeFunctionId(id) => { + tracer.record_arg( + frame_id, + ArgTrace { + index: i, + kind: ArgKind::Thunk { + node_id: *id, + eager: false, + executed: false, + }, + preview: format!("thunk({})", id), + }, + ); + args.push(Argument::Thunk(*id)); } } + } + + Ok(args) + } + + fn force_eager_args( + &self, + _node: &NodeFunction, + entry: &HandlerFunctionEntry, + args: &mut [Argument], + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + parent_frame: u64, + ) -> Result<(), (Signal, Outcome)> { + for (i, arg) in args.iter_mut().enumerate() { + let mode = entry + .param_modes + .get(i) + .copied() + .unwrap_or(ParameterNode::Eager); + + if matches!(mode, ParameterNode::Eager) { + if let Argument::Thunk(id) = *arg { + let (child_sig, child_root) = self.execute_call(id, ctx, tracer); - for (i, a) in args.iter_mut().enumerate() { - let mode = entry - .param_modes - .get(i) - .copied() - .unwrap_or(ParameterNode::Eager); - if matches!(mode, ParameterNode::Eager) - && let Argument::Thunk(id) = *a - { - match self.execute(id, ctx) { + tracer.link_child( + parent_frame, + child_root, + EdgeKind::EagerCall { arg_index: i }, + ); + + match child_sig { Signal::Success(v) => { - log::debug!( - "Successfully executed node with database id {}, resulted in value: {:?}", - id, - a - ); - *a = Argument::Eval(v) + *arg = Argument::Eval(v); } - Signal::Failure(err) => { - log::error!("Failed to execute node with database id: {}", id); - return Signal::Failure(err); + s => { + return Err(( + s, + Outcome::Failure { + error_preview: "Eager child failed".into(), + }, + )); } - s @ (Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => return s, } } } + } - let mut run = |node_id: i64, ctx: &mut Context| self.execute(node_id, ctx); - let result = (entry.handler)(&args, ctx, &mut run); + Ok(()) + } - match result { - Signal::Success(value) => { - if let Some(next_node_id) = node.next_node_id { - current_node_id = next_node_id; - continue; - } else { - log::debug!( - "Successfully executed node with database id {}, resulted in value: {:?}", - current_node_id, - value - ); - return Signal::Success(value); - } - } - Signal::Failure(e) => return Signal::Failure(e), - Signal::Return(v) => return Signal::Return(v), - Signal::Respond(v) => return Signal::Respond(v), - Signal::Stop => return Signal::Stop, + fn invoke_handler( + &self, + entry: &HandlerFunctionEntry, + args: &[Argument], + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + ) -> Signal { + let mut run = |node_id: i64, ctx: &mut Context| { + let (sig, _) = self.execute_call(node_id, ctx, tracer); + sig + }; + + (entry.handler)(args, ctx, &mut run) + } + + fn commit_result( + &self, + node: &NodeFunction, + result: Signal, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + frame_id: u64, + ) -> Signal { + match result { + Signal::Success(v) => { + ctx.insert_success(node.database_id, v.clone()); + + tracer.exit_node( + frame_id, + Outcome::Success { + value_preview: format!("{:#?}", v), + }, + ); + + Signal::Success(v) + } + + Signal::Failure(e) => { + ctx.insert_error(node.database_id, e.clone()); + + tracer.exit_node( + frame_id, + Outcome::Failure { + error_preview: format!("{:#?}", e), + }, + ); + + Signal::Failure(e) + } + + Signal::Return(v) => { + tracer.exit_node( + frame_id, + Outcome::Return { + value_preview: format!("{:#?}", v), + }, + ); + Signal::Return(v) + } + + Signal::Respond(v) => { + tracer.exit_node( + frame_id, + Outcome::Respond { + value_preview: format!("{:#?}", v), + }, + ); + Signal::Respond(v) + } + + Signal::Stop => { + tracer.exit_node(frame_id, Outcome::Stop); + Signal::Stop } } } From f759cefb20958eff55569664d64ac7e114298aab Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:00 +0100 Subject: [PATCH 02/58] feat: new reference logic impl --- taurus/src/context/context.rs | 102 +++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/taurus/src/context/context.rs b/taurus/src/context/context.rs index 3b4cdef..f83a350 100644 --- a/taurus/src/context/context.rs +++ b/taurus/src/context/context.rs @@ -1,6 +1,6 @@ use crate::error::RuntimeError; use std::collections::HashMap; -use tucana::shared::Value; +use tucana::shared::{InputType, ReferenceValue, Value, value::Kind}; #[derive(Clone)] pub enum ContextResult { @@ -12,16 +12,114 @@ pub enum ContextResult { #[derive(Default)] pub struct Context { results: HashMap, + input_types: HashMap, + flow_input: Value, } impl Context { - pub fn get(&mut self, id: i64) -> ContextResult { + pub fn new(flow_input: Value) -> Self { + return Context { + results: HashMap::new(), + input_types: HashMap::new(), + flow_input, + }; + } + + pub fn get(&mut self, reference: ReferenceValue) -> ContextResult { + let target = match reference.target { + Some(t) => t, + None => return ContextResult::NotFound, + }; + + let res = match target { + tucana::shared::reference_value::Target::FlowInput(_) => self.get_flow_input(), + tucana::shared::reference_value::Target::NodeId(i) => self.get_result(i), + tucana::shared::reference_value::Target::InputType(input_type) => { + self.get_input_type(input_type) + } + }; + + if reference.paths.is_empty() { + return res; + } + + if let ContextResult::Success(value) = res { + let mut curr = value; + + for path in reference.paths { + if let Some(index) = path.array_index { + match curr.kind { + Some(ref kind) => { + if let Kind::ListValue(list) = &kind { + match list.values.get(index as usize) { + Some(x) => { + curr = x.clone(); + } + None => return ContextResult::NotFound, + } + } + } + None => return ContextResult::NotFound, + } + } + + if let Some(path) = path.path { + let splits = path.split("."); + + for part in splits { + match curr.kind { + Some(ref kind) => { + if let Kind::StructValue(struct_value) = &kind { + match struct_value.fields.get(part) { + Some(x) => { + curr = x.clone(); + } + None => return ContextResult::NotFound, + } + } + } + None => return ContextResult::NotFound, + } + } + } + } + + ContextResult::Success(curr) + } else { + res + } + } + + fn get_result(&mut self, id: i64) -> ContextResult { match self.results.get(&id) { None => ContextResult::NotFound, Some(result) => result.clone(), } } + fn get_flow_input(&mut self) -> ContextResult { + return ContextResult::Success(self.flow_input.clone()); + } + + fn get_input_type(&mut self, input_type: InputType) -> ContextResult { + match self.input_types.get(&input_type) { + Some(v) => ContextResult::Success(v.clone()), + None => ContextResult::NotFound, + } + } + + pub fn clear_input_type(&mut self, input_type: InputType) { + self.input_types.remove(&input_type); + } + + pub fn insert_input_type(&mut self, input_type: InputType, value: Value) { + self.input_types.insert(input_type, value); + } + + pub fn insert_flow_input(&mut self, value: Value) { + self.flow_input = value; + } + pub fn insert_success(&mut self, id: i64, value: Value) { self.results.insert(id, ContextResult::Success(value)); } From da492a9bcd99a43567a2aaa8de17ad351288f8d2 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:06 +0100 Subject: [PATCH 03/58] feat: added tracing --- taurus/src/debug/mod.rs | 3 + taurus/src/debug/render.rs | 156 +++++++++++++++++++++++++++++++++++++ taurus/src/debug/trace.rs | 75 ++++++++++++++++++ taurus/src/debug/tracer.rs | 97 +++++++++++++++++++++++ 4 files changed, 331 insertions(+) create mode 100644 taurus/src/debug/mod.rs create mode 100644 taurus/src/debug/render.rs create mode 100644 taurus/src/debug/trace.rs create mode 100644 taurus/src/debug/tracer.rs diff --git a/taurus/src/debug/mod.rs b/taurus/src/debug/mod.rs new file mode 100644 index 0000000..813a7e6 --- /dev/null +++ b/taurus/src/debug/mod.rs @@ -0,0 +1,3 @@ +pub mod trace; +pub mod tracer; +pub mod render; diff --git a/taurus/src/debug/render.rs b/taurus/src/debug/render.rs new file mode 100644 index 0000000..afd770c --- /dev/null +++ b/taurus/src/debug/render.rs @@ -0,0 +1,156 @@ +use crate::debug::trace::{ArgKind, EdgeKind, ExecFrame, Outcome, TraceRun}; +use std::collections::HashMap; + +fn short(s: &str, max: usize) -> String { + if s.len() <= max { + return s.to_string(); + } + format!("{}…", &s[..max]) +} + +fn ms(f: &ExecFrame) -> Option { + f.end.map(|e| e.duration_since(f.start).as_millis()) +} + +pub fn render_trace(run: &TraceRun) -> String { + let mut by_id: HashMap = HashMap::new(); + for f in &run.frames { + by_id.insert(f.frame_id, f); + } + + let mut out = String::new(); + render_frame(run.root, &by_id, "", true, &mut out); + out +} + +fn render_frame( + id: u64, + by_id: &HashMap, + prefix: &str, + is_last: bool, + out: &mut String, +) { + let f = by_id[&id]; + + let branch = if prefix.is_empty() { + "" // root + } else if is_last { + "└─ " + } else { + "├─ " + }; + + let dur = ms(f).map(|m| format!(" ({}ms)", m)).unwrap_or_default(); + + out.push_str(&format!( + "{prefix}{branch}#{fid} node={nid} fn={name}{dur}\n", + prefix = prefix, + branch = branch, + fid = f.frame_id, + nid = f.node_id, + name = f.function_name, + dur = dur + )); + + // args + for a in &f.args { + let pfx = if prefix.is_empty() { + "" + } else if is_last { + " " + } else { + "│ " + }; + + let kind = match &a.kind { + ArgKind::Literal => "lit", + ArgKind::Reference { hit, .. } => { + if *hit { + "ref✓" + } else { + "ref✗" + } + } + ArgKind::Thunk { + eager, executed, .. + } => match (*eager, *executed) { + (true, true) => "thunk eager✓", + (true, false) => "thunk eager", + (false, true) => "thunk lazy✓", + (false, false) => "thunk lazy", + }, + }; + + out.push_str(&format!( + "{prefix}{pfx} arg[{}] {:<12} {}\n", + a.index, + kind, + short(&a.preview, 1600), + prefix = prefix, + pfx = pfx + )); + } + + // outcome + let outcome_line = match &f.outcome { + Some(Outcome::Success { value_preview }) => format!("✅ {}", short(value_preview, 1800)), + Some(Outcome::Failure { error_preview }) => format!("❌ {}", short(error_preview, 1800)), + Some(Outcome::Return { value_preview }) => format!("↩️ {}", short(value_preview, 1800)), + Some(Outcome::Respond { value_preview }) => format!("💬 {}", short(value_preview, 1800)), + Some(Outcome::Stop) => "🛑 Stop".to_string(), + None => "…".to_string(), + }; + + let pfx = if prefix.is_empty() { + "" + } else if is_last { + " " + } else { + "│ " + }; + out.push_str(&format!( + "{prefix}{pfx} => {o}\n", + prefix = prefix, + pfx = pfx, + o = outcome_line + )); + + // children + let kids = &f.children; + if kids.is_empty() { + return; + } + + let new_prefix = if prefix.is_empty() { + String::new() + } else { + format!("{}{}", prefix, if is_last { " " } else { "│ " }) + }; + + for (idx, (edge, child_id)) in kids.iter().enumerate() { + let last = idx + 1 == kids.len(); + + // print edge label line (nice readability) + let edge_label = match edge { + EdgeKind::Next => "→ NEXT".to_string(), + EdgeKind::EagerCall { arg_index } => format!("↳ eager(arg#{})", arg_index), + }; + + let edge_pfx = if prefix.is_empty() { + "" + } else if is_last { + " " + } else { + "│ " + }; + + out.push_str(&format!( + "{prefix}{edge_pfx} {edge}\n", + prefix = prefix, + edge_pfx = edge_pfx, + edge = edge_label + )); + + render_frame(*child_id, by_id, &new_prefix, last, out); + } +} diff --git a/taurus/src/debug/trace.rs b/taurus/src/debug/trace.rs new file mode 100644 index 0000000..8eeee7a --- /dev/null +++ b/taurus/src/debug/trace.rs @@ -0,0 +1,75 @@ +// src/debug_trace/trace.rs +use std::time::Instant; + +#[derive(Debug, Clone)] +pub enum EdgeKind { + /// Linear control-flow via next_node_id + Next, + /// Eager evaluation of a thunk argument (child execution) + EagerCall { arg_index: usize }, +} + +#[derive(Debug, Clone)] +pub enum ArgKind { + Literal, + Reference { + reference: ReferenceKind, + hit: bool, + }, + Thunk { + node_id: i64, + eager: bool, + executed: bool, + }, +} + +#[derive(Debug, Clone)] +pub enum ReferenceKind { + Result { + node_id: i64, + }, + InputType { + node_id: i64, + input_index: i64, + parameter_index: i64, + }, + FlowInput, + Empty +} + +#[derive(Debug, Clone)] +pub struct ArgTrace { + pub index: usize, + pub kind: ArgKind, + pub preview: String, +} + +#[derive(Debug, Clone)] +pub enum Outcome { + Success { value_preview: String }, + Failure { error_preview: String }, + Return { value_preview: String }, + Respond { value_preview: String }, + Stop, +} + +#[derive(Debug, Clone)] +pub struct ExecFrame { + pub frame_id: u64, // unique execution instance id + pub node_id: i64, // database_id + pub function_name: String, // runtime_function_id + pub args: Vec, + pub outcome: Option, + + pub start: Instant, + pub end: Option, + + /// Child edges to other frames (CALLs and NEXT links) + pub children: Vec<(EdgeKind, u64)>, +} + +#[derive(Debug, Clone)] +pub struct TraceRun { + pub frames: Vec, + pub root: u64, +} diff --git a/taurus/src/debug/tracer.rs b/taurus/src/debug/tracer.rs new file mode 100644 index 0000000..ed452df --- /dev/null +++ b/taurus/src/debug/tracer.rs @@ -0,0 +1,97 @@ +use std::time::Instant; + +use crate::debug::trace::{ArgTrace, EdgeKind, ExecFrame, Outcome, TraceRun}; + +pub trait ExecutionTracer { + fn enter_node(&mut self, node_id: i64, function_name: &str) -> u64; + fn record_arg(&mut self, frame_id: u64, arg: ArgTrace); + fn link_child(&mut self, parent_frame: u64, child_frame: u64, edge: EdgeKind); + fn exit_node(&mut self, frame_id: u64, outcome: Outcome); +} + +pub struct Tracer { + next_id: u64, + pub run: Option, + stack: Vec, +} + +impl Tracer { + pub fn new() -> Self { + Self { + next_id: 1, + run: None, + stack: vec![], + } + } + + fn frames_mut(&mut self) -> &mut Vec { + &mut self.run.as_mut().unwrap().frames + } + + fn get_frame_mut(&mut self, frame_id: u64) -> &mut ExecFrame { + let idx = self + .frames_mut() + .iter() + .position(|f| f.frame_id == frame_id) + .expect("trace frame must exist"); + &mut self.frames_mut()[idx] + } + + pub fn take_run(self) -> Option { + self.run + } +} + +impl ExecutionTracer for Tracer { + fn enter_node(&mut self, node_id: i64, function_name: &str) -> u64 { + if self.run.is_none() { + self.run = Some(TraceRun { + frames: vec![], + root: 0, + }); + } + + let frame_id = self.next_id; + self.next_id += 1; + + let frame = ExecFrame { + frame_id, + node_id, + function_name: function_name.to_string(), + args: vec![], + outcome: None, + start: Instant::now(), + end: None, + children: vec![], + }; + + let run = self.run.as_mut().unwrap(); + if run.root == 0 { + run.root = frame_id; + } + run.frames.push(frame); + + self.stack.push(frame_id); + frame_id + } + + fn record_arg(&mut self, frame_id: u64, arg: ArgTrace) { + self.get_frame_mut(frame_id).args.push(arg); + } + + fn link_child(&mut self, parent_frame: u64, child_frame: u64, edge: EdgeKind) { + self.get_frame_mut(parent_frame) + .children + .push((edge, child_frame)); + } + + fn exit_node(&mut self, frame_id: u64, outcome: Outcome) { + let f = self.get_frame_mut(frame_id); + f.outcome = Some(outcome); + f.end = Some(Instant::now()); + + // Pop in LIFO order + let popped = self.stack.pop(); + debug_assert_eq!(popped, Some(frame_id)); + } +} From 6165a1450e197dd6d3199056fab7cd1b18e3d932 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:22 +0100 Subject: [PATCH 04/58] feat: wip new implementation of array functions --- taurus/src/implementation/array.rs | 396 +++++++++++++++++------------ 1 file changed, 233 insertions(+), 163 deletions(-) diff --git a/taurus/src/implementation/array.rs b/taurus/src/implementation/array.rs index 642d0c1..d247f06 100644 --- a/taurus/src/implementation/array.rs +++ b/taurus/src/implementation/array.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use tucana::shared::{ListValue, Value, value::Kind}; use crate::context::argument::Argument; +use crate::context::argument::ParameterNode::{Eager, Lazy}; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; @@ -12,14 +13,38 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ ("std::list::at", HandlerFn::eager(at, 2)), ("std::list::concat", HandlerFn::eager(concat, 2)), - ("std::list::filter", HandlerFn::eager(filter, 2)), - ("std::list::find", HandlerFn::eager(find, 2)), - ("std::list::find_last", HandlerFn::eager(find_last, 2)), - ("std::list::find_index", HandlerFn::eager(find_index, 2)), + //TODO + ( + "std::list::filter", + HandlerFn::into_function_entry(filter, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::find", + HandlerFn::into_function_entry(find, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::find_last", + HandlerFn::into_function_entry(find_last, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::find_index", + HandlerFn::into_function_entry(find_index, vec![Eager, Lazy]), + ), ("std::list::first", HandlerFn::eager(first, 1)), ("std::list::last", HandlerFn::eager(last, 1)), - ("std::list::for_each", HandlerFn::eager(for_each, 0)), - ("std::list::map", HandlerFn::eager(map, 2)), + //TODO + ( + "std::list::for_each", + HandlerFn::into_function_entry(for_each, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::map", + HandlerFn::into_function_entry(map, vec![Eager, Lazy]), + ), ("std::list::push", HandlerFn::eager(push, 2)), ("std::list::pop", HandlerFn::eager(pop, 1)), ("std::list::remove", HandlerFn::eager(remove, 2)), @@ -27,8 +52,16 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ("std::list::size", HandlerFn::eager(size, 1)), ("std::list::index_of", HandlerFn::eager(index_of, 2)), ("std::list::to_unique", HandlerFn::eager(to_unique, 1)), - ("std::list::sort", HandlerFn::eager(sort, 2)), - ("std::list::sort_reverse", HandlerFn::eager(sort_reverse, 2)), + //TODO + ( + "std::list::sort", + HandlerFn::into_function_entry(sort, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::sort_reverse", + HandlerFn::into_function_entry(sort_reverse, vec![Eager, Lazy]), + ), ("std::list::reverse", HandlerFn::eager(reverse, 1)), ("std::list::flat", HandlerFn::eager(flat, 1)), ("std::list::min", HandlerFn::eager(min, 1)), @@ -38,6 +71,23 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ] } +fn as_list(value: &Value, err: &'static str) -> Result { + match value.kind.clone().unwrap_or(Kind::NullValue(0)) { + Kind::ListValue(lv) => Ok(lv), + _ => Err(RuntimeError::simple_str("InvalidArgumentRuntimeError", err)), + } +} + +fn as_bool(value: &Value) -> Result { + match value.kind.clone().unwrap_or(Kind::NullValue(0)) { + Kind::BoolValue(b) => Ok(b), + _ => Err(RuntimeError::simple_str( + "InvalidArgumentRuntimeError", + "Expected boolean result from predicate", + )), + } +} + fn at( args: &[Argument], _ctx: &mut Context, @@ -98,192 +148,174 @@ fn concat( fn filter( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array".to_string(), + format!( + "filter expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans".to_string(), - )); + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + let mut out: Vec = Vec::new(); + + for item in array.values.iter().cloned() { + let pred_sig = run(*predicate_node, ctx); + + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => out.push(item), + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut i = 0usize; - let new_array = array - .values - .iter() - .filter(|_| { - let keep = *preds.get(i).unwrap_or(&false); - i += 1; - keep - }) - .cloned() - .collect::>(); - Signal::Success(Value { - kind: Some(Kind::ListValue(ListValue { values: new_array })), + kind: Some(Kind::ListValue(ListValue { values: out })), }) } fn find( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "find expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans", - )); + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + for item in array.values.iter().cloned() { + let pred_sig = run(*predicate_node, ctx); + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => return Signal::Success(item), + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut i = 0usize; - let item = array - .values - .iter() - .find(|&_| { - let keep = *preds.get(i).unwrap_or(&false); - i += 1; - keep - }) - .cloned(); - - match item { - Some(v) => Signal::Success(v), - None => Signal::Failure(RuntimeError::simple_str( - "NotFoundError", - "No item found that satisfies the predicate", - )), - } + Signal::Failure(RuntimeError::simple_str( + "NotFoundError", + "No item found that satisfies the predicate", + )) } - fn find_last( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "find_last expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans", - )); + + let mut array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; + array.values.reverse(); - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + for item in array.values.into_iter() { + let pred_sig = run(*predicate_node, ctx); + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => return Signal::Success(item), + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut i = 0usize; - let mut reversed = array.values.clone(); - reversed.reverse(); - - let item = reversed.into_iter().find(|_| { - let keep = *preds.get(i).unwrap_or(&false); - i += 1; - keep - }); - - match item { - Some(v) => Signal::Success(v), - None => Signal::Failure(RuntimeError::simple_str( - "NotFoundError", - "No item found that satisfies the predicate", - )), - } + Signal::Failure(RuntimeError::simple_str( + "NotFoundError", + "No item found that satisfies the predicate", + )) } fn find_index( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "find_index expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans", - )); + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + for (idx, _item) in array.values.iter().cloned().enumerate() { + let pred_sig = run(*predicate_node, ctx); + + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => { + return Signal::Success(Value { + kind: Some(Kind::NumberValue(idx as f64)), + }); + } + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut idx = 0usize; - let found = array.values.iter().find(|_| { - let keep = *preds.get(idx).unwrap_or(&false); - if !keep { - idx += 1; - } - keep - }); - - match found { - Some(_) => Signal::Success(Value { - kind: Some(Kind::NumberValue(idx as f64)), - }), - None => Signal::Failure(RuntimeError::simple_str( - "NotFoundError", - "No item found that satisfies the predicate", - )), - } + Signal::Failure(RuntimeError::simple_str( + "NotFoundError", + "No item found that satisfies the predicate", + )) } - fn first( args: &[Argument], _ctx: &mut Context, @@ -315,18 +347,38 @@ fn last( } } -/// for_each has no implementation -/// -/// Reason: -/// The definition itself takes in an array and a node -/// The node itself will be executed on the arrays elements -/// If the node is (CONSUMER) resolved it goes in this function --> therefor all code is already executed fn for_each( - _args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + args: &[Argument], + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - // Already executed by the engine (consumer); return Null + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), + )); + }; + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), + }; + + for _ in array.values.iter().cloned() { + let sig = run(*transform_node, ctx); + + match sig { + Signal::Success(_) => {} + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } + } + } + Signal::Success(Value { kind: Some(Kind::NullValue(0)), }) @@ -334,21 +386,39 @@ fn for_each( fn map( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - // (array, transformed_results[]) - args!(args => _array_v: Value, transform_v: Value); - let Kind::ListValue(transform_result) = - transform_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected transform result to be an array", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), )); }; + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), + }; + + let mut out: Vec = Vec::with_capacity(array.values.len()); + + for _ in array.values.iter().cloned() { + let sig = run(*transform_node, ctx); + match sig { + Signal::Success(v) => out.push(v), + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } + } + } + Signal::Success(Value { - kind: Some(Kind::ListValue(transform_result.clone())), + kind: Some(Kind::ListValue(ListValue { values: out })), }) } From eaa67c81b628aaf48aa9d02e596101bc0bf4b12c Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:34 +0100 Subject: [PATCH 05/58] dependencies: updated to tucana 0.0.53 --- Cargo.lock | 24 ++++++++++++++++++++++-- Cargo.toml | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb43f01..573e752 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,7 +259,7 @@ dependencies = [ "tokio", "tonic", "tonic-health", - "tucana", + "tucana 0.0.52", "walkdir", ] @@ -1734,7 +1734,7 @@ dependencies = [ "tokio", "tonic", "tonic-health", - "tucana", + "tucana 0.0.53", ] [[package]] @@ -2073,6 +2073,26 @@ dependencies = [ "tonic-prost-build", ] +[[package]] +name = "tucana" +version = "0.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca58639610a8fe299f3615f8774624b872833a53b589245c99d9df15f6fd8f7a" +dependencies = [ + "pbjson", + "pbjson-build", + "pbjson-types", + "prost", + "prost-build", + "prost-types", + "serde", + "serde_json", + "tonic", + "tonic-build", + "tonic-prost", + "tonic-prost-build", +] + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index fd619fa..4ee1577 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2024" [workspace.dependencies] code0-flow = { version = "0.0.25" } -tucana = { version = "0.0.52" } +tucana = { version = "0.0.53" } tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] } log = "0.4.27" futures-lite = "2.6.0" From e9ce54cc95663f45eedc520e23bc31b2b360a20d Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:43 +0100 Subject: [PATCH 06/58] feat: added new module --- taurus/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/taurus/src/main.rs b/taurus/src/main.rs index 4b1ecdc..8c791ec 100644 --- a/taurus/src/main.rs +++ b/taurus/src/main.rs @@ -1,5 +1,6 @@ mod config; pub mod context; +pub mod debug; pub mod error; pub mod implementation; @@ -7,6 +8,8 @@ use crate::config::Config; use crate::context::executor::Executor; use crate::context::registry::FunctionStore; use crate::context::signal::Signal; +use crate::debug::render::render_trace; +use crate::debug::tracer::Tracer; use crate::implementation::collect; use code0_flow::flow_service::FlowUpdateService; From 5925b4ca76434933b4e3540503032311682fda4f Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:09:48 +0100 Subject: [PATCH 07/58] feat: added current node id to context --- taurus/src/context/context.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/taurus/src/context/context.rs b/taurus/src/context/context.rs index f83a350..d66b765 100644 --- a/taurus/src/context/context.rs +++ b/taurus/src/context/context.rs @@ -14,6 +14,7 @@ pub struct Context { results: HashMap, input_types: HashMap, flow_input: Value, + current_node_id: i64, } impl Context { @@ -22,9 +23,18 @@ impl Context { results: HashMap::new(), input_types: HashMap::new(), flow_input, + current_node_id: 0, }; } + pub fn get_current_node_id(&mut self) -> i64 { + return self.current_node_id; + } + + pub fn set_current_node_id(&mut self, node_id: i64) { + self.current_node_id = node_id; + } + pub fn get(&mut self, reference: ReferenceValue) -> ContextResult { let target = match reference.target { Some(t) => t, From c4083e181b102d160cfbd30190a325511880cbc5 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:09:58 +0100 Subject: [PATCH 08/58] feat: set current node id in execution loop --- taurus/src/context/executor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/taurus/src/context/executor.rs b/taurus/src/context/executor.rs index 55279b8..8e8bbde 100644 --- a/taurus/src/context/executor.rs +++ b/taurus/src/context/executor.rs @@ -81,6 +81,7 @@ impl<'a> Executor<'a> { ctx: &mut Context, tracer: &mut dyn ExecutionTracer, ) -> (Signal, u64) { + ctx.set_current_node_id(node_id); let node = match self.nodes.get(&node_id) { Some(n) => n.clone(), None => { From 9e7556877ea0d870b1832cb3ab70ff0bf5be8203 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:10:13 +0100 Subject: [PATCH 09/58] feat: added new implementation for iterators needing input types --- taurus/src/implementation/array.rs | 270 ++++++++++++++++++++++------- 1 file changed, 208 insertions(+), 62 deletions(-) diff --git a/taurus/src/implementation/array.rs b/taurus/src/implementation/array.rs index d247f06..803313d 100644 --- a/taurus/src/implementation/array.rs +++ b/taurus/src/implementation/array.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; +use tucana::shared::InputType; use tucana::shared::{ListValue, Value, value::Kind}; use crate::context::argument::Argument; @@ -13,34 +14,28 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ ("std::list::at", HandlerFn::eager(at, 2)), ("std::list::concat", HandlerFn::eager(concat, 2)), - //TODO ( "std::list::filter", HandlerFn::into_function_entry(filter, vec![Eager, Lazy]), ), - //TODO ( "std::list::find", HandlerFn::into_function_entry(find, vec![Eager, Lazy]), ), - //TODO ( "std::list::find_last", HandlerFn::into_function_entry(find_last, vec![Eager, Lazy]), ), - //TODO ( "std::list::find_index", HandlerFn::into_function_entry(find_index, vec![Eager, Lazy]), ), ("std::list::first", HandlerFn::eager(first, 1)), ("std::list::last", HandlerFn::eager(last, 1)), - //TODO ( "std::list::for_each", HandlerFn::into_function_entry(for_each, vec![Eager, Lazy]), ), - //TODO ( "std::list::map", HandlerFn::into_function_entry(map, vec![Eager, Lazy]), @@ -52,12 +47,10 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ("std::list::size", HandlerFn::eager(size, 1)), ("std::list::index_of", HandlerFn::eager(index_of, 2)), ("std::list::to_unique", HandlerFn::eager(to_unique, 1)), - //TODO ( "std::list::sort", HandlerFn::into_function_entry(sort, vec![Eager, Lazy]), ), - //TODO ( "std::list::sort_reverse", HandlerFn::into_function_entry(sort_reverse, vec![Eager, Lazy]), @@ -167,13 +160,19 @@ fn filter( }; let mut out: Vec = Vec::new(); - - for item in array.values.iter().cloned() { + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { - Ok(true) => out.push(item), + Ok(true) => out.push(item.clone()), Ok(false) => {} Err(e) => return Signal::Failure(e), }, @@ -184,6 +183,7 @@ fn filter( } } + ctx.clear_input_type(input_type); Signal::Success(Value { kind: Some(Kind::ListValue(ListValue { values: out })), }) @@ -208,22 +208,37 @@ fn find( Ok(a) => a, Err(e) => return Signal::Failure(e), }; + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for item in array.values.iter().cloned() { + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { - Ok(true) => return Signal::Success(item), - Ok(false) => {} - Err(e) => return Signal::Failure(e), + Ok(true) => { + ctx.clear_input_type(input_type); + return Signal::Success(item.clone()); + } + Ok(false) => continue, + Err(e) => { + ctx.clear_input_type(input_type); + return Signal::Failure(e); + } }, other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } + ctx.clear_input_type(input_type); Signal::Failure(RuntimeError::simple_str( "NotFoundError", "No item found that satisfies the predicate", @@ -249,22 +264,36 @@ fn find_last( Err(e) => return Signal::Failure(e), }; array.values.reverse(); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; for item in array.values.into_iter() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { - Ok(true) => return Signal::Success(item), - Ok(false) => {} - Err(e) => return Signal::Failure(e), + Ok(true) => { + ctx.clear_input_type(input_type); + return Signal::Success(item); + } + Ok(false) => continue, + Err(e) => { + ctx.clear_input_type(input_type); + return Signal::Failure(e); + } }, other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } - + ctx.clear_input_type(input_type); Signal::Failure(RuntimeError::simple_str( "NotFoundError", "No item found that satisfies the predicate", @@ -290,27 +319,40 @@ fn find_index( Ok(a) => a, Err(e) => return Signal::Failure(e), }; + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for (idx, _item) in array.values.iter().cloned().enumerate() { + for (idx, item) in array.values.iter().enumerate() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { Ok(true) => { + ctx.clear_input_type(input_type); return Signal::Success(Value { kind: Some(Kind::NumberValue(idx as f64)), }); } - Ok(false) => {} - Err(e) => return Signal::Failure(e), + Ok(false) => continue, + Err(e) => { + ctx.clear_input_type(input_type); + return Signal::Failure(e); + } }, other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } + ctx.clear_input_type(input_type); Signal::Failure(RuntimeError::simple_str( "NotFoundError", "No item found that satisfies the predicate", @@ -366,8 +408,15 @@ fn for_each( Ok(a) => a, Err(e) => return Signal::Failure(e), }; + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for _ in array.values.iter().cloned() { + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let sig = run(*transform_node, ctx); match sig { @@ -379,6 +428,7 @@ fn for_each( } } + ctx.clear_input_type(input_type); Signal::Success(Value { kind: Some(Kind::NullValue(0)), }) @@ -405,18 +455,27 @@ fn map( }; let mut out: Vec = Vec::with_capacity(array.values.len()); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for _ in array.values.iter().cloned() { + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let sig = run(*transform_node, ctx); match sig { Signal::Success(v) => out.push(v), other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } + ctx.clear_input_type(input_type); Signal::Success(Value { kind: Some(Kind::ListValue(ListValue { values: out })), }) @@ -569,34 +628,77 @@ fn to_unique( fn sort( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - // array, resolved comparator yields -1/0/1 sequence - args!(args => array_v: Value, cmp_v: Value); - let Kind::ListValue(mut arr) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(cmp_vals) = cmp_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of numbers", - )); + + let mut array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut comps: Vec = Vec::new(); - for v in &cmp_vals.values { - if let Some(Kind::NumberValue(n)) = v.kind { - comps.push(n); + let mut out: Vec = Vec::new(); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; + + let input_type_next = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 1, + }; + + let mut signals = Vec::new(); + array.values.sort_by(|a, b| { + ctx.insert_input_type(input_type, a.clone()); + ctx.insert_input_type(input_type_next, b.clone()); + let sig = run(*transform_node, ctx); + signals.push(sig); + return Ordering::Equal; + }); + + for sig in signals { + match sig { + Signal::Success(v) => { + if let Value { + kind: Some(Kind::NumberValue(i)), + } = v + { + out.push(i); + } else { + ctx.clear_input_type(input_type); + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "expected return value of comparator to be a number but was {:?}", + v + ), + )); + } + } + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); + return other; + } } } let mut i = 0usize; - arr.values.sort_by(|_, _| { - let comp = *comps.get(i).unwrap_or(&0.0); + array.values.sort_by(|_, _| { + let comp = *out.get(i).unwrap_or(&0.0); i += 1; match comp { n if n < 0.0 => Ordering::Less, @@ -606,41 +708,85 @@ fn sort( }); Signal::Success(Value { - kind: Some(Kind::ListValue(arr)), + kind: Some(Kind::ListValue(array)), }) } fn sort_reverse( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, cmp_v: Value); - let Kind::ListValue(mut arr) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(cmp_vals) = cmp_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of numbers", - )); + + let mut array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut comps: Vec = Vec::new(); - for v in &cmp_vals.values { - if let Some(Kind::NumberValue(n)) = v.kind { - comps.push(n); + let mut out: Vec = Vec::new(); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; + + let input_type_next = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 1, + }; + + let mut signals = Vec::new(); + array.values.sort_by(|a, b| { + ctx.insert_input_type(input_type, a.clone()); + ctx.insert_input_type(input_type_next, b.clone()); + let sig = run(*transform_node, ctx); + signals.push(sig); + return Ordering::Equal; + }); + + for sig in signals { + match sig { + Signal::Success(v) => { + if let Value { + kind: Some(Kind::NumberValue(i)), + } = v + { + out.push(i); + } else { + ctx.clear_input_type(input_type); + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "expected return value of comparator to be a number but was {:?}", + v + ), + )); + } + } + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); + return other; + } } } - arr.values.reverse(); // keep behavior consistent with original + array.values.reverse(); // keep behavior consistent with original let mut i = 0usize; - arr.values.sort_by(|_, _| { - let comp = *comps.get(i).unwrap_or(&0.0); + array.values.sort_by(|_, _| { + let comp = *out.get(i).unwrap_or(&0.0); i += 1; match comp { n if n < 0.0 => Ordering::Less, @@ -650,7 +796,7 @@ fn sort_reverse( }); Signal::Success(Value { - kind: Some(Kind::ListValue(arr)), + kind: Some(Kind::ListValue(array)), }) } From 6628165f7c8855360bf2b72dc4fa4a663d87c849 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:13:04 +0100 Subject: [PATCH 10/58] dependencies: updated tucana and code0-flow --- Cargo.lock | 32 ++++++-------------------------- Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 573e752..c73f701 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "code0-flow" -version = "0.0.25" +version = "0.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e10d39ec0c673c18749c0ff66c033806b68d1a99ec49cf1f4657fcd7c9d4b5" +checksum = "c27759004bf304f21b1d65677fbafd0a3e97a1be471fbe7a3aac5556eed3bc44" dependencies = [ "async-nats", "async-trait", @@ -259,7 +259,7 @@ dependencies = [ "tokio", "tonic", "tonic-health", - "tucana 0.0.52", + "tucana", "walkdir", ] @@ -1734,7 +1734,7 @@ dependencies = [ "tokio", "tonic", "tonic-health", - "tucana 0.0.53", + "tucana", ] [[package]] @@ -2055,29 +2055,9 @@ dependencies = [ [[package]] name = "tucana" -version = "0.0.52" +version = "0.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e1cec14d8140417b330fffd1fb07f52cbd69063a4bd5688e63511b5bf8c8aa" -dependencies = [ - "pbjson", - "pbjson-build", - "pbjson-types", - "prost", - "prost-build", - "prost-types", - "serde", - "serde_json", - "tonic", - "tonic-build", - "tonic-prost", - "tonic-prost-build", -] - -[[package]] -name = "tucana" -version = "0.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca58639610a8fe299f3615f8774624b872833a53b589245c99d9df15f6fd8f7a" +checksum = "31e81f5598a25b6a8f1618fcb25d1a8b2ddbfa38c8a9dcba16a798efe425a36e" dependencies = [ "pbjson", "pbjson-build", diff --git a/Cargo.toml b/Cargo.toml index 4ee1577..a80b3e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ version = "0.1.0" edition = "2024" [workspace.dependencies] -code0-flow = { version = "0.0.25" } -tucana = { version = "0.0.53" } +code0-flow = { version = "0.0.26" } +tucana = { version = "0.0.54" } tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] } log = "0.4.27" futures-lite = "2.6.0" From 2eb78999d2295fb32cad703a421f4341aa41ae9b Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:13:39 +0100 Subject: [PATCH 11/58] ref: cargo fmt --- taurus/src/debug/mod.rs | 2 +- taurus/src/debug/trace.rs | 2 +- taurus/src/main.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/taurus/src/debug/mod.rs b/taurus/src/debug/mod.rs index 813a7e6..f30265c 100644 --- a/taurus/src/debug/mod.rs +++ b/taurus/src/debug/mod.rs @@ -1,3 +1,3 @@ +pub mod render; pub mod trace; pub mod tracer; -pub mod render; diff --git a/taurus/src/debug/trace.rs b/taurus/src/debug/trace.rs index 8eeee7a..d4f7c5d 100644 --- a/taurus/src/debug/trace.rs +++ b/taurus/src/debug/trace.rs @@ -34,7 +34,7 @@ pub enum ReferenceKind { parameter_index: i64, }, FlowInput, - Empty + Empty, } #[derive(Debug, Clone)] diff --git a/taurus/src/main.rs b/taurus/src/main.rs index 8c791ec..dcb03e7 100644 --- a/taurus/src/main.rs +++ b/taurus/src/main.rs @@ -8,8 +8,6 @@ use crate::config::Config; use crate::context::executor::Executor; use crate::context::registry::FunctionStore; use crate::context::signal::Signal; -use crate::debug::render::render_trace; -use crate::debug::tracer::Tracer; use crate::implementation::collect; use code0_flow::flow_service::FlowUpdateService; From 1747cf27549e2ae39ec7c61c8a930aada4189780 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:14:20 +0100 Subject: [PATCH 12/58] ref: cargo clippy --- taurus/src/context/context.rs | 8 ++++---- taurus/src/context/executor.rs | 7 +++---- taurus/src/debug/tracer.rs | 6 ++++++ taurus/src/implementation/array.rs | 24 ++++++++++++------------ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/taurus/src/context/context.rs b/taurus/src/context/context.rs index d66b765..650e66b 100644 --- a/taurus/src/context/context.rs +++ b/taurus/src/context/context.rs @@ -19,16 +19,16 @@ pub struct Context { impl Context { pub fn new(flow_input: Value) -> Self { - return Context { + Context { results: HashMap::new(), input_types: HashMap::new(), flow_input, current_node_id: 0, - }; + } } pub fn get_current_node_id(&mut self) -> i64 { - return self.current_node_id; + self.current_node_id } pub fn set_current_node_id(&mut self, node_id: i64) { @@ -108,7 +108,7 @@ impl Context { } fn get_flow_input(&mut self) -> ContextResult { - return ContextResult::Success(self.flow_input.clone()); + ContextResult::Success(self.flow_input.clone()) } fn get_input_type(&mut self, input_type: InputType) -> ContextResult { diff --git a/taurus/src/context/executor.rs b/taurus/src/context/executor.rs index 8e8bbde..f8fe93b 100644 --- a/taurus/src/context/executor.rs +++ b/taurus/src/context/executor.rs @@ -193,7 +193,7 @@ impl<'a> Executor<'a> { ArgTrace { index: i, kind: ArgKind::Reference { - reference: reference, + reference, hit: true, }, preview: format!("ctx.get({:?}) -> {:?}", r, v), @@ -247,8 +247,8 @@ impl<'a> Executor<'a> { .copied() .unwrap_or(ParameterNode::Eager); - if matches!(mode, ParameterNode::Eager) { - if let Argument::Thunk(id) = *arg { + if matches!(mode, ParameterNode::Eager) + && let Argument::Thunk(id) = *arg { let (child_sig, child_root) = self.execute_call(id, ctx, tracer); tracer.link_child( @@ -271,7 +271,6 @@ impl<'a> Executor<'a> { } } } - } } Ok(()) diff --git a/taurus/src/debug/tracer.rs b/taurus/src/debug/tracer.rs index ed452df..b0c26f6 100644 --- a/taurus/src/debug/tracer.rs +++ b/taurus/src/debug/tracer.rs @@ -15,6 +15,12 @@ pub struct Tracer { stack: Vec, } +impl Default for Tracer { + fn default() -> Self { + Self::new() + } +} + impl Tracer { pub fn new() -> Self { Self { diff --git a/taurus/src/implementation/array.rs b/taurus/src/implementation/array.rs index 803313d..2c68f81 100644 --- a/taurus/src/implementation/array.rs +++ b/taurus/src/implementation/array.rs @@ -162,7 +162,7 @@ fn filter( let mut out: Vec = Vec::new(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -210,7 +210,7 @@ fn find( }; let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -266,7 +266,7 @@ fn find_last( array.values.reverse(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -321,7 +321,7 @@ fn find_index( }; let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -410,7 +410,7 @@ fn for_each( }; let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -457,7 +457,7 @@ fn map( let mut out: Vec = Vec::with_capacity(array.values.len()); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -649,13 +649,13 @@ fn sort( let mut out: Vec = Vec::new(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; let input_type_next = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 1, }; @@ -666,7 +666,7 @@ fn sort( ctx.insert_input_type(input_type_next, b.clone()); let sig = run(*transform_node, ctx); signals.push(sig); - return Ordering::Equal; + Ordering::Equal }); for sig in signals { @@ -735,13 +735,13 @@ fn sort_reverse( let mut out: Vec = Vec::new(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; let input_type_next = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 1, }; @@ -752,7 +752,7 @@ fn sort_reverse( ctx.insert_input_type(input_type_next, b.clone()); let sig = run(*transform_node, ctx); signals.push(sig); - return Ordering::Equal; + Ordering::Equal }); for sig in signals { From 4d3a7bb6abaedc6f6a034e69bf9b3c3972565b33 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Mar 2026 13:31:37 +0100 Subject: [PATCH 13/58] ref: split taurus codebase into core, taurus (service), tests (test-suite) --- Cargo.lock | 15 +++++++++++++++ Cargo.toml | 5 ++++- crates/core/.gitignore | 1 + crates/core/Cargo.toml | 10 ++++++++++ {taurus => crates/core}/src/context/argument.rs | 2 +- {taurus => crates/core}/src/context/context.rs | 3 ++- {taurus => crates/core}/src/context/executor.rs | 2 +- {taurus => crates/core}/src/context/macros.rs | 4 ++-- {taurus => crates/core}/src/context/mod.rs | 0 {taurus => crates/core}/src/context/registry.rs | 0 {taurus => crates/core}/src/context/signal.rs | 3 ++- {taurus => crates/core}/src/debug/mod.rs | 0 {taurus => crates/core}/src/debug/render.rs | 0 {taurus => crates/core}/src/debug/trace.rs | 1 - {taurus => crates/core}/src/debug/tracer.rs | 0 crates/core/src/lib.rs | 3 +++ .../src => crates/core/src/runtime}/error/mod.rs | 0 .../core/src/runtime/functions}/array.rs | 3 ++- .../core/src/runtime/functions}/boolean.rs | 3 ++- .../core/src/runtime/functions}/control.rs | 2 +- .../core/src/runtime/functions}/http.rs | 14 +++++++------- .../core/src/runtime/functions}/mod.rs | 0 .../core/src/runtime/functions}/number.rs | 3 ++- .../core/src/runtime/functions}/object.rs | 0 .../core/src/runtime/functions}/text.rs | 3 ++- crates/core/src/runtime/mod.rs | 2 ++ {taurus => crates/taurus}/Cargo.toml | 5 +++-- {taurus => crates/taurus}/src/config/mod.rs | 0 {taurus => crates/taurus}/src/main.rs | 14 +++++--------- crates/tests/Cargo.toml | 6 ++++++ crates/tests/src/main.rs | 3 +++ 31 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 crates/core/.gitignore create mode 100644 crates/core/Cargo.toml rename {taurus => crates/core}/src/context/argument.rs (98%) rename {taurus => crates/core}/src/context/context.rs (99%) rename {taurus => crates/core}/src/context/executor.rs (99%) rename {taurus => crates/core}/src/context/macros.rs (94%) rename {taurus => crates/core}/src/context/mod.rs (100%) rename {taurus => crates/core}/src/context/registry.rs (100%) rename {taurus => crates/core}/src/context/signal.rs (96%) rename {taurus => crates/core}/src/debug/mod.rs (100%) rename {taurus => crates/core}/src/debug/render.rs (100%) rename {taurus => crates/core}/src/debug/trace.rs (98%) rename {taurus => crates/core}/src/debug/tracer.rs (100%) create mode 100644 crates/core/src/lib.rs rename {taurus/src => crates/core/src/runtime}/error/mod.rs (100%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/array.rs (99%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/boolean.rs (99%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/control.rs (98%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/http.rs (89%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/mod.rs (100%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/number.rs (99%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/object.rs (100%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/text.rs (99%) create mode 100644 crates/core/src/runtime/mod.rs rename {taurus => crates/taurus}/Cargo.toml (85%) rename {taurus => crates/taurus}/src/config/mod.rs (100%) rename {taurus => crates/taurus}/src/main.rs (96%) create mode 100644 crates/tests/Cargo.toml create mode 100644 crates/tests/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c73f701..d98115f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core" +version = "0.1.0" +dependencies = [ + "base64", + "log", + "rand 0.10.0", + "tucana", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1726,6 +1736,7 @@ dependencies = [ "async-nats", "base64", "code0-flow", + "core", "env_logger", "futures-lite", "log", @@ -1750,6 +1761,10 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tests" +version = "0.1.0" + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index a80b3e6..5ce7557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["taurus"] +members = [ "crates/core", "crates/taurus", "crates/tests" ] resolver = "3" [workspace.package] @@ -19,3 +19,6 @@ async-nats = "0.46.0" prost = "0.14.1" tonic-health = "0.14.1" tonic = "0.14.1" + +[workspace.dependencies.core] +path = "./crates/core" diff --git a/crates/core/.gitignore b/crates/core/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/core/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..48a3a1f --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "core" +version.workspace = true +edition.workspace = true + +[dependencies] +tucana = { workspace = true } +base64 = { workspace = true } +rand = { workspace = true } +log = { workspace = true } diff --git a/taurus/src/context/argument.rs b/crates/core/src/context/argument.rs similarity index 98% rename from taurus/src/context/argument.rs rename to crates/core/src/context/argument.rs index 1bbbe6a..a0a61af 100644 --- a/taurus/src/context/argument.rs +++ b/crates/core/src/context/argument.rs @@ -1,5 +1,5 @@ use crate::context::signal::Signal; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use std::convert::Infallible; use tucana::shared::value::Kind; use tucana::shared::{ListValue, Struct, Value}; diff --git a/taurus/src/context/context.rs b/crates/core/src/context/context.rs similarity index 99% rename from taurus/src/context/context.rs rename to crates/core/src/context/context.rs index 650e66b..f521d37 100644 --- a/taurus/src/context/context.rs +++ b/crates/core/src/context/context.rs @@ -1,7 +1,8 @@ -use crate::error::RuntimeError; use std::collections::HashMap; use tucana::shared::{InputType, ReferenceValue, Value, value::Kind}; +use crate::runtime::error::RuntimeError; + #[derive(Clone)] pub enum ContextResult { Error(RuntimeError), diff --git a/taurus/src/context/executor.rs b/crates/core/src/context/executor.rs similarity index 99% rename from taurus/src/context/executor.rs rename to crates/core/src/context/executor.rs index f8fe93b..04ed0a6 100644 --- a/taurus/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -4,7 +4,7 @@ use crate::context::registry::{FunctionStore, HandlerFunctionEntry}; use crate::context::signal::Signal; use crate::debug::trace::{ArgKind, ArgTrace, EdgeKind, Outcome, ReferenceKind}; use crate::debug::tracer::{ExecutionTracer, Tracer}; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use std::collections::HashMap; use tucana::shared::NodeFunction; diff --git a/taurus/src/context/macros.rs b/crates/core/src/context/macros.rs similarity index 94% rename from taurus/src/context/macros.rs rename to crates/core/src/context/macros.rs index ead9e8a..076db78 100644 --- a/taurus/src/context/macros.rs +++ b/crates/core/src/context/macros.rs @@ -6,7 +6,7 @@ macro_rules! args { let __expected: usize = 0usize $(+ { let _ = ::core::any::type_name::<$ty>(); 1usize })*; if $args_ident.len() != __expected { return $crate::context::signal::Signal::Failure( - $crate::error::RuntimeError::simple( + $crate::runtime::error::RuntimeError::simple( "InvalidArgumentRuntimeError", format!("Expected {__expected} args but received {}", $args_ident.len()), ) @@ -39,7 +39,7 @@ macro_rules! args { macro_rules! no_args { ($args_ident:ident) => { if !$args_ident.is_empty() { - return $crate::context::signal::Signal::Failure($crate::error::RuntimeError::simple( + return $crate::context::signal::Signal::Failure($crate::runtime::error::RuntimeError::simple( "InvalidArgumentRuntimeError", format!("Expected 0 args but received {}", $args_ident.len()), )); diff --git a/taurus/src/context/mod.rs b/crates/core/src/context/mod.rs similarity index 100% rename from taurus/src/context/mod.rs rename to crates/core/src/context/mod.rs diff --git a/taurus/src/context/registry.rs b/crates/core/src/context/registry.rs similarity index 100% rename from taurus/src/context/registry.rs rename to crates/core/src/context/registry.rs diff --git a/taurus/src/context/signal.rs b/crates/core/src/context/signal.rs similarity index 96% rename from taurus/src/context/signal.rs rename to crates/core/src/context/signal.rs index 0c40add..c51aa92 100644 --- a/taurus/src/context/signal.rs +++ b/crates/core/src/context/signal.rs @@ -1,6 +1,7 @@ -use crate::error::RuntimeError; use tucana::shared::Value; +use crate::runtime::error::RuntimeError; + #[derive(Debug)] pub enum Signal { // Will be signaled if a function has been executed successfully diff --git a/taurus/src/debug/mod.rs b/crates/core/src/debug/mod.rs similarity index 100% rename from taurus/src/debug/mod.rs rename to crates/core/src/debug/mod.rs diff --git a/taurus/src/debug/render.rs b/crates/core/src/debug/render.rs similarity index 100% rename from taurus/src/debug/render.rs rename to crates/core/src/debug/render.rs diff --git a/taurus/src/debug/trace.rs b/crates/core/src/debug/trace.rs similarity index 98% rename from taurus/src/debug/trace.rs rename to crates/core/src/debug/trace.rs index d4f7c5d..4a5d34b 100644 --- a/taurus/src/debug/trace.rs +++ b/crates/core/src/debug/trace.rs @@ -1,4 +1,3 @@ -// src/debug_trace/trace.rs use std::time::Instant; #[derive(Debug, Clone)] diff --git a/taurus/src/debug/tracer.rs b/crates/core/src/debug/tracer.rs similarity index 100% rename from taurus/src/debug/tracer.rs rename to crates/core/src/debug/tracer.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..c818ae7 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,3 @@ +pub mod context; +pub mod debug; +pub mod runtime; diff --git a/taurus/src/error/mod.rs b/crates/core/src/runtime/error/mod.rs similarity index 100% rename from taurus/src/error/mod.rs rename to crates/core/src/runtime/error/mod.rs diff --git a/taurus/src/implementation/array.rs b/crates/core/src/runtime/functions/array.rs similarity index 99% rename from taurus/src/implementation/array.rs rename to crates/core/src/runtime/functions/array.rs index 2c68f81..de149be 100644 --- a/taurus/src/implementation/array.rs +++ b/crates/core/src/runtime/functions/array.rs @@ -5,10 +5,11 @@ use tucana::shared::{ListValue, Value, value::Kind}; use crate::context::argument::Argument; use crate::context::argument::ParameterNode::{Eager, Lazy}; +use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ diff --git a/taurus/src/implementation/boolean.rs b/crates/core/src/runtime/functions/boolean.rs similarity index 99% rename from taurus/src/implementation/boolean.rs rename to crates/core/src/runtime/functions/boolean.rs index b7b5fb4..cb3e4de 100644 --- a/taurus/src/implementation/boolean.rs +++ b/crates/core/src/runtime/functions/boolean.rs @@ -1,8 +1,9 @@ use crate::context::argument::Argument; +use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; use tucana::shared::{Value, value::Kind}; pub fn collect_boolean_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { diff --git a/taurus/src/implementation/control.rs b/crates/core/src/runtime/functions/control.rs similarity index 98% rename from taurus/src/implementation/control.rs rename to crates/core/src/runtime/functions/control.rs index e552a7c..a5d766a 100644 --- a/taurus/src/implementation/control.rs +++ b/crates/core/src/runtime/functions/control.rs @@ -4,7 +4,7 @@ use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use tucana::shared::Value; use tucana::shared::value::Kind; diff --git a/taurus/src/implementation/http.rs b/crates/core/src/runtime/functions/http.rs similarity index 89% rename from taurus/src/implementation/http.rs rename to crates/core/src/runtime/functions/http.rs index d593ab5..4f524d8 100644 --- a/taurus/src/implementation/http.rs +++ b/crates/core/src/runtime/functions/http.rs @@ -3,7 +3,7 @@ use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use tucana::shared::value::Kind; use tucana::shared::{ListValue, Struct, Value}; @@ -47,10 +47,10 @@ fn respond( )); }; - let Some(Kind::ListValue(_headers_struct)) = &headers_val.kind else { + let Some(Kind::StructValue(_headers_struct)) = &headers_val.kind else { return Signal::Failure(RuntimeError::simple_str( "InvalidArgumentRuntimeError", - "Expected 'headers' to be ListValue", + "Expected 'headers' to be StructValue", )); }; @@ -78,7 +78,7 @@ fn create_request( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => http_method: String, headers: ListValue, http_url: String, payload: Value); + args!(args => http_method: String, headers: Struct, http_url: String, payload: Value); let mut fields = std::collections::HashMap::new(); fields.insert( @@ -98,7 +98,7 @@ fn create_request( fields.insert( "headers".to_string(), Value { - kind: Some(Kind::ListValue(headers.clone())), + kind: Some(Kind::StructValue(headers.clone())), }, ); fields.insert("body".to_string(), payload.clone()); @@ -113,7 +113,7 @@ fn create_response( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => http_status_code: String, headers: ListValue, payload: Value); + args!(args => http_status_code: String, headers: Struct, payload: Value); let mut fields = std::collections::HashMap::new(); let code = match http_status_code.as_str().parse::() { @@ -135,7 +135,7 @@ fn create_response( fields.insert( "headers".to_string(), Value { - kind: Some(Kind::ListValue(headers.clone())), + kind: Some(Kind::StructValue(headers.clone())), }, ); fields.insert("payload".to_string(), payload.clone()); diff --git a/taurus/src/implementation/mod.rs b/crates/core/src/runtime/functions/mod.rs similarity index 100% rename from taurus/src/implementation/mod.rs rename to crates/core/src/runtime/functions/mod.rs diff --git a/taurus/src/implementation/number.rs b/crates/core/src/runtime/functions/number.rs similarity index 99% rename from taurus/src/implementation/number.rs rename to crates/core/src/runtime/functions/number.rs index 19c53b8..cea4024 100644 --- a/taurus/src/implementation/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -6,7 +6,8 @@ use crate::context::argument::Argument; use crate::context::macros::{args, no_args}; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; +use crate::{context::context::Context}; pub fn collect_number_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ diff --git a/taurus/src/implementation/object.rs b/crates/core/src/runtime/functions/object.rs similarity index 100% rename from taurus/src/implementation/object.rs rename to crates/core/src/runtime/functions/object.rs diff --git a/taurus/src/implementation/text.rs b/crates/core/src/runtime/functions/text.rs similarity index 99% rename from taurus/src/implementation/text.rs rename to crates/core/src/runtime/functions/text.rs index f86a6a8..2877ab5 100644 --- a/taurus/src/implementation/text.rs +++ b/crates/core/src/runtime/functions/text.rs @@ -2,7 +2,8 @@ use crate::context::argument::Argument; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; +use crate::{context::context::Context}; use base64::Engine; use tucana::shared::{ListValue, Value, value::Kind}; diff --git a/crates/core/src/runtime/mod.rs b/crates/core/src/runtime/mod.rs new file mode 100644 index 0000000..81bfbbf --- /dev/null +++ b/crates/core/src/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod functions; diff --git a/taurus/Cargo.toml b/crates/taurus/Cargo.toml similarity index 85% rename from taurus/Cargo.toml rename to crates/taurus/Cargo.toml index 007f381..d2f7cf6 100644 --- a/taurus/Cargo.toml +++ b/crates/taurus/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "taurus" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true [dependencies] code0-flow = { workspace = true, features = ["flow_service"] } @@ -16,3 +16,4 @@ async-nats = { workspace = true } prost = { workspace = true } tonic-health = { workspace = true } tonic = { workspace = true } +core = { workspace = true } diff --git a/taurus/src/config/mod.rs b/crates/taurus/src/config/mod.rs similarity index 100% rename from taurus/src/config/mod.rs rename to crates/taurus/src/config/mod.rs diff --git a/taurus/src/main.rs b/crates/taurus/src/main.rs similarity index 96% rename from taurus/src/main.rs rename to crates/taurus/src/main.rs index dcb03e7..d3988b2 100644 --- a/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -1,22 +1,18 @@ mod config; -pub mod context; -pub mod debug; -pub mod error; -pub mod implementation; use crate::config::Config; -use crate::context::executor::Executor; -use crate::context::registry::FunctionStore; -use crate::context::signal::Signal; -use crate::implementation::collect; use code0_flow::flow_service::FlowUpdateService; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; -use context::context::Context; use futures_lite::StreamExt; use log::error; use prost::Message; +use core::context::context::Context; +use core::context::executor::Executor; +use core::context::registry::FunctionStore; +use core::context::signal::Signal; +use core::runtime::functions::collect; use std::collections::HashMap; use tokio::signal; use tonic_health::pb::health_server::HealthServer; diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml new file mode 100644 index 0000000..9e51e89 --- /dev/null +++ b/crates/tests/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tests" +version.workspace = true +edition.workspace = true + +[dependencies] diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/crates/tests/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 6b648fcc2f8c042671216c47dab852af7a5edf0e Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Mar 2026 13:57:42 +0100 Subject: [PATCH 14/58] fix: random_range if numbers are in wrong order it will return runtime error instead of panicing --- crates/core/src/runtime/functions/number.rs | 33 +++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/crates/core/src/runtime/functions/number.rs b/crates/core/src/runtime/functions/number.rs index cea4024..7b8d00f 100644 --- a/crates/core/src/runtime/functions/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -3,11 +3,11 @@ use std::f64; use tucana::shared::{Value, value::Kind}; use crate::context::argument::Argument; +use crate::context::context::Context; use crate::context::macros::{args, no_args}; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; -use crate::{context::context::Context}; pub fn collect_number_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ @@ -382,13 +382,17 @@ fn random( ) -> Signal { args!(args => min: f64, max: f64); - let min_i = min.ceil() as i64; - let max_i = max.floor() as i64; + if min > max { + return Signal::Failure(RuntimeError::simple_str( + "InvalidRange", + "First number can't be bigger then second when creating a range for std::math::random", + )); + } - let value = rand::random_range(min_i..=max_i) as i64; + let value = rand::random_range(min..=max); Signal::Success(Value { - kind: Some(Kind::NumberValue(value as f64)), + kind: Some(Kind::NumberValue(value)), }) } @@ -789,6 +793,25 @@ mod tests { assert!(r >= 1.0 && r < 10.0); } + + #[test] + fn test_random_range_numbers_equal() { + let mut ctx = Context::default(); + + let mut run = dummy_run; + let r = expect_num(random(&[a_num(1.0), a_num(1.0)], &mut ctx, &mut run)); + assert!(r == 1.0); + } + + #[test] + fn test_random_range_fist_bigger_then_second() { + let mut ctx = Context::default(); + + let mut run = dummy_run; + let res = random(&[a_num(10.0), a_num(1.0)], &mut ctx, &mut run); + assert!(matches!(res, Signal::Failure(_))); + } + #[test] fn test_trig_and_hyperbolic() { let mut ctx = Context::default(); From baacba0cbe340b5f8fb16c0f4999db88ef6d2b09 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 13 Mar 2026 11:57:27 +0100 Subject: [PATCH 15/58] feat: added wip flow test execution suite --- crates/core/src/context/registry.rs | 5 +- crates/taurus/src/main.rs | 3 +- crates/tests/Cargo.toml | 6 ++ crates/tests/src/main.rs | 142 +++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 4 deletions(-) diff --git a/crates/core/src/context/registry.rs b/crates/core/src/context/registry.rs index d402e44..633eac2 100644 --- a/crates/core/src/context/registry.rs +++ b/crates/core/src/context/registry.rs @@ -1,6 +1,7 @@ use crate::context::argument::{Argument, ParameterNode}; use crate::context::context::Context; use crate::context::signal::Signal; +use crate::runtime::functions::collect; use std::collections::HashMap; /// HandlerFm @@ -25,7 +26,9 @@ pub struct FunctionStore { impl Default for FunctionStore { fn default() -> Self { - Self::new() + let mut store = Self::new(); + store.populate(collect()); + store } } diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index d3988b2..745130d 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -40,8 +40,7 @@ async fn main() { load_env_file(); let config = Config::new(); - let mut store = FunctionStore::new(); - store.populate(collect()); + let store = FunctionStore::default(); let client = match async_nats::connect(config.nats_url.clone()).await { Ok(client) => { diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 9e51e89..8f819a3 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -4,3 +4,9 @@ version.workspace = true edition.workspace = true [dependencies] +tucana = { workspace = true } +core = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index e7a11a9..67da994 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -1,3 +1,143 @@ +use core::context::{context::Context, executor::Executor, registry::FunctionStore}; +use log::{error, info}; +use serde::Deserialize; +use std::collections::HashMap; + +use tucana::shared::NodeFunction; + +#[derive(Clone, Deserialize)] +struct Input { + input: Option, + expected_result: serde_json::Value, +} + +#[derive(Clone, Deserialize)] +struct Case { + name: String, + description: String, + inputs: Vec, + flow: TestableFlow, +} + +#[derive(Clone, Deserialize)] +struct TestableFlow { + pub starting_node_id: i64, + pub node_functions: Vec, +} + +#[derive(Clone, Deserialize)] +struct TestCases { + cases: Vec, +} + +fn print_success(case: &Case) { + info!("test {} ... ok", case.name); +} + +fn print_failure(case: &Case, input: &Input) { + error!("test {} ... FAILED", case.name); + error!(" input: {:?}", input.input); + error!(" expected: {:?}", input.expected_result); + error!(" message: {}", case.description); +} + +fn get_test_cases(path: &str) -> TestCases { + let mut items = Vec::new(); + let dir = match std::fs::read_dir(path) { + Ok(d) => d, + Err(err) => { + panic!("Cannot open path: {:?}", err) + } + }; + + for entry in dir { + let entry = match entry { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read entry: {:?}", err); + continue; + } + }; + let path = entry.path(); + + let content = match std::fs::read_to_string(&path) { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read file ({:?}): {:?}", path, err); + continue; + } + }; + items.push(match serde_json::from_str(&content) { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read json ({:?}): {:?}", path, err); + continue; + } + }); + } + + TestCases { cases: items } +} + +impl TestCases { + pub fn from_path(path: &str) -> Self { + get_test_cases(path) + } + + pub fn run_tests(&self) { + for case in self.cases.clone() { + match case.run() { + CaseResult::Success => print_success(&case), + CaseResult::Failure(input) => print_failure(&case, &input), + } + } + } +} + +enum CaseResult { + Success, + Failure(Input), +} + +impl Case { + fn run(&self) -> CaseResult { + let store = FunctionStore::default(); + + let node_functions: HashMap = self + .clone() + .flow + .node_functions + .into_iter() + .map(|node| (node.database_id, node)) + .collect(); + + for input in self.inputs.clone() { + let mut context = match input.clone().input { + Some(inp) => Context::new(inp), + None => Context::default(), + }; + + let res = Executor::new(&store, node_functions.clone()) + .execute(self.flow.starting_node_id, &mut context); + + match res { + core::context::signal::Signal::Failure(_) => { + return CaseResult::Failure(input); + } + _ => continue, + } + } + + CaseResult::Success + } +} + fn main() { - println!("Hello, world!"); + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Info) + .init(); + + + let cases = TestCases::from_path("./crates/tests/flows/"); + cases.run_tests(); } From 60facaed750c1f4f6bbaa884a3c843dcf53b65de Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 13 Mar 2026 11:57:35 +0100 Subject: [PATCH 16/58] dependencies: added serde and serde_json --- Cargo.lock | 26 +++++++++++++++++--------- Cargo.toml | 2 ++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d98115f..0d5c226 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1493,12 +1493,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -1578,15 +1572,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1764,6 +1758,14 @@ dependencies = [ [[package]] name = "tests" version = "0.1.0" +dependencies = [ + "core", + "env_logger", + "log", + "serde", + "serde_json", + "tucana", +] [[package]] name = "thiserror" @@ -2616,3 +2618,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 5ce7557..1fe6dd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ async-nats = "0.46.0" prost = "0.14.1" tonic-health = "0.14.1" tonic = "0.14.1" +serde_json = "1.0.149" +serde = "1.0.228" [workspace.dependencies.core] path = "./crates/core" From 470186cd4091aac1bc7d04d94aa4b183ec4dc462 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 13 Mar 2026 11:57:42 +0100 Subject: [PATCH 17/58] feat: added first test flow to suite --- crates/tests/flows/01_return_object.json | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 crates/tests/flows/01_return_object.json diff --git a/crates/tests/flows/01_return_object.json b/crates/tests/flows/01_return_object.json new file mode 100644 index 0000000..ade5ebd --- /dev/null +++ b/crates/tests/flows/01_return_object.json @@ -0,0 +1,78 @@ +{ + "name": "01_return_object", + "description": "This flow expects a simple http response object as the return value", + "inputs": [ + { + "input": null, + "expected_result": { + "http_status_code": 200, + "headers": [ + { + "Header": "X" + } + ], + "body": "Hello World" + } + } + ], + "flow": { + "starting_node_id": "1", + "node_functions": [ + { + "databaseId": "2", + "runtimeFunctionId": "rest::control::respond", + "parameters": [ + { + "databaseId": "4", + "runtimeParameterId": "http_response", + "value": { + "referenceValue": { + "nodeId": "1" + } + } + } + ] + }, + { + "databaseId": "1", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "1", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "2", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Header": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "3", + "runtimeParameterId": "payload", + "value": { + "literalValue": { + "stringValue": "Hello World" + } + } + } + ], + "nextNodeId": "2" + } + ] + } +} From b616f78f578c8c91d3e5eeb5620bb491c6e399b9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:09:25 +0100 Subject: [PATCH 18/58] feat: made tracer print optional --- crates/core/src/context/executor.rs | 50 +++++++++++++++-------------- crates/taurus/src/main.rs | 2 +- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 04ed0a6..bc180ff 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -20,15 +20,16 @@ impl<'a> Executor<'a> { } /// This is now the ONLY execution entry point. - pub fn execute(&self, start_node_id: i64, ctx: &mut Context) -> Signal { + pub fn execute(&self, start_node_id: i64, ctx: &mut Context, with_trace: bool) -> Signal { let mut tracer = Tracer::new(); let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); - if let Some(run) = tracer.take_run() { - println!("{}", crate::debug::render::render_trace(&run)); + if with_trace { + if let Some(run) = tracer.take_run() { + println!("{}", crate::debug::render::render_trace(&run)); + } } - signal } @@ -248,29 +249,30 @@ impl<'a> Executor<'a> { .unwrap_or(ParameterNode::Eager); if matches!(mode, ParameterNode::Eager) - && let Argument::Thunk(id) = *arg { - let (child_sig, child_root) = self.execute_call(id, ctx, tracer); - - tracer.link_child( - parent_frame, - child_root, - EdgeKind::EagerCall { arg_index: i }, - ); + && let Argument::Thunk(id) = *arg + { + let (child_sig, child_root) = self.execute_call(id, ctx, tracer); + + tracer.link_child( + parent_frame, + child_root, + EdgeKind::EagerCall { arg_index: i }, + ); - match child_sig { - Signal::Success(v) => { - *arg = Argument::Eval(v); - } - s => { - return Err(( - s, - Outcome::Failure { - error_preview: "Eager child failed".into(), - }, - )); - } + match child_sig { + Signal::Success(v) => { + *arg = Argument::Eval(v); + } + s => { + return Err(( + s, + Outcome::Failure { + error_preview: "Eager child failed".into(), + }, + )); } } + } } Ok(()) diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 745130d..9d285ba 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -28,7 +28,7 @@ fn handle_message(flow: ExecutionFlow, store: &FunctionStore) -> Signal { .map(|node| (node.database_id, node)) .collect(); - Executor::new(store, node_functions).execute(flow.starting_node_id, &mut context) + Executor::new(store, node_functions).execute(flow.starting_node_id, &mut context, true) } #[tokio::main] From ed4267407f77b4e8ef918d64d13b5ce2a06508ed Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:09:34 +0100 Subject: [PATCH 19/58] feat: exported RuntimeErrors name and message --- crates/core/src/runtime/error/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/src/runtime/error/mod.rs b/crates/core/src/runtime/error/mod.rs index 4dbf6e2..14895ba 100644 --- a/crates/core/src/runtime/error/mod.rs +++ b/crates/core/src/runtime/error/mod.rs @@ -8,8 +8,8 @@ use tucana::shared::{Struct, Value}; #[derive(Debug, Default, Clone)] pub struct RuntimeError { - name: String, - message: String, + pub name: String, + pub message: String, suggestion: Option, } From 720baf967208fc30bb3a04aafd15af1a4b9199e1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:09:46 +0100 Subject: [PATCH 20/58] feat: first correct flow test cases --- crates/tests/flows/01_return_object.json | 12 ++- crates/tests/flows/02_return_flow_input.json | 88 ++++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 crates/tests/flows/02_return_flow_input.json diff --git a/crates/tests/flows/01_return_object.json b/crates/tests/flows/01_return_object.json index ade5ebd..34c28c8 100644 --- a/crates/tests/flows/01_return_object.json +++ b/crates/tests/flows/01_return_object.json @@ -5,13 +5,11 @@ { "input": null, "expected_result": { - "http_status_code": 200, - "headers": [ - { - "Header": "X" - } - ], - "body": "Hello World" + "status_code": 200, + "headers": { + "Header": "X" + }, + "payload": "Hello World" } } ], diff --git a/crates/tests/flows/02_return_flow_input.json b/crates/tests/flows/02_return_flow_input.json new file mode 100644 index 0000000..ef8402d --- /dev/null +++ b/crates/tests/flows/02_return_flow_input.json @@ -0,0 +1,88 @@ +{ + "name": "02_return_object", + "description": "This flow expects the same output value as the input", + "inputs": [ + { + "input": null, + "expected_result": { + "status_code": 200, + "headers": { + "Authentication": "X" + }, + "payload": null + } + }, + { + "input": { + "name": "Joe Doe", + "age": 51, + "pets": [ + "dog", + "cat", + "bird" + ] + }, + "expected_result": { + "status_code": 200, + "headers": { + "Authentication": "X" + }, + "payload": { + "name": "Joe Doe", + "age": 51, + "pets": [ + "dog", + "cat", + "bird" + ] + } + } + } + ], + "flow": { + "flowId": "2", + "projectId": "1", + "startingNodeId": "3", + "nodeFunctions": [ + { + "databaseId": "3", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "5", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "6", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Authentication": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "7", + "runtimeParameterId": "payload", + "value": { + "referenceValue": { + "flowInput": {} + } + } + } + ] + } + ] + } +} From 8ad8b8328609d0d832aaef6162e441e91d91c889 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:01 +0100 Subject: [PATCH 21/58] feat: added readme --- crates/tests/README.md | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 crates/tests/README.md diff --git a/crates/tests/README.md b/crates/tests/README.md new file mode 100644 index 0000000..91f56b2 --- /dev/null +++ b/crates/tests/README.md @@ -0,0 +1,107 @@ +# Test Execution Suite + +This is the service for running a full execution for example flows. + +## How to run the execution suite? + +```console +cargo run --package tests +``` + +## How to add a flow to the suite? + +```json +{ + "name": "Descriptive snake case name", + "description": "Description on what logic should be tested", + "inputs": [ + { + "input": "Input Value/Flow Input (JSON)", + "expected_result": "Expected (JSON) Result of the flow", + } + ], + "flow": "The flow object (Exported from Aquila), parsed from Protobuf Message struct so it should be protobuf value not json value" +} + +``` + +An example + +```json +{ + "name": "01_return_object", + "description": "This flow expects a simple http response object as the return value", + "inputs": [ + { + "input": null, + "expected_result": { + "status_code": 200, + "headers": { + "Header": "X" + }, + "payload": "Hello World" + } + } + ], + "flow": { + "starting_node_id": "1", + "node_functions": [ + { + "databaseId": "2", + "runtimeFunctionId": "rest::control::respond", + "parameters": [ + { + "databaseId": "4", + "runtimeParameterId": "http_response", + "value": { + "referenceValue": { + "nodeId": "1" + } + } + } + ] + }, + { + "databaseId": "1", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "1", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "2", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Header": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "3", + "runtimeParameterId": "payload", + "value": { + "literalValue": { + "stringValue": "Hello World" + } + } + } + ], + "nextNodeId": "2" + } + ] + } +} +``` From a7bfd92b5cf1008aeafad86e5cc3c4539dc34d8b Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:15 +0100 Subject: [PATCH 22/58] feat: added comparison of results to test suite --- crates/tests/src/main.rs | 62 ++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 67da994..87ea2dd 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -1,13 +1,17 @@ use core::context::{context::Context, executor::Executor, registry::FunctionStore}; use log::{error, info}; use serde::Deserialize; +use serde_json::json; use std::collections::HashMap; -use tucana::shared::NodeFunction; +use tucana::shared::{ + NodeFunction, ValidationFlow, + helper::value::{from_json_value, to_json_value}, +}; #[derive(Clone, Deserialize)] struct Input { - input: Option, + input: Option, expected_result: serde_json::Value, } @@ -16,13 +20,7 @@ struct Case { name: String, description: String, inputs: Vec, - flow: TestableFlow, -} - -#[derive(Clone, Deserialize)] -struct TestableFlow { - pub starting_node_id: i64, - pub node_functions: Vec, + flow: ValidationFlow, } #[derive(Clone, Deserialize)] @@ -34,10 +32,11 @@ fn print_success(case: &Case) { info!("test {} ... ok", case.name); } -fn print_failure(case: &Case, input: &Input) { +fn print_failure(case: &Case, input: &Input, result: serde_json::Value) { error!("test {} ... FAILED", case.name); error!(" input: {:?}", input.input); error!(" expected: {:?}", input.expected_result); + error!(" real_value: {:?}", result); error!(" message: {}", case.description); } @@ -88,7 +87,7 @@ impl TestCases { for case in self.cases.clone() { match case.run() { CaseResult::Success => print_success(&case), - CaseResult::Failure(input) => print_failure(&case, &input), + CaseResult::Failure(input, result) => print_failure(&case, &input, result), } } } @@ -96,7 +95,7 @@ impl TestCases { enum CaseResult { Success, - Failure(Input), + Failure(Input, serde_json::Value), } impl Case { @@ -113,18 +112,46 @@ impl Case { for input in self.inputs.clone() { let mut context = match input.clone().input { - Some(inp) => Context::new(inp), + Some(inp) => Context::new(from_json_value(inp)), None => Context::default(), }; let res = Executor::new(&store, node_functions.clone()) - .execute(self.flow.starting_node_id, &mut context); + .execute(self.flow.starting_node_id, &mut context, false); match res { - core::context::signal::Signal::Failure(_) => { - return CaseResult::Failure(input); + core::context::signal::Signal::Failure(err) => { + let json = json!({ + "name": err.name, + "message": err.message, + }); + return CaseResult::Failure(input, json); + } + core::context::signal::Signal::Success(value) => { + let json = to_json_value(value); + if json == input.clone().expected_result { + return CaseResult::Success; + } else { + return CaseResult::Failure(input, json); + } } - _ => continue, + core::context::signal::Signal::Return(value) => { + let json = to_json_value(value); + if json == input.clone().expected_result { + return CaseResult::Success; + } else { + return CaseResult::Failure(input, json); + } + } + core::context::signal::Signal::Respond(value) => { + let json = to_json_value(value); + if json == input.clone().expected_result { + return CaseResult::Success; + } else { + return CaseResult::Failure(input, json); + } + } + core::context::signal::Signal::Stop => continue, } } @@ -137,7 +164,6 @@ fn main() { .filter_level(log::LevelFilter::Info) .init(); - let cases = TestCases::from_path("./crates/tests/flows/"); cases.run_tests(); } From 7d2c92e6619dd203283b5326302bd01c47add097 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:38 +0100 Subject: [PATCH 23/58] ref: cargo fix --- crates/core/src/runtime/functions/http.rs | 2 +- crates/taurus/src/main.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/core/src/runtime/functions/http.rs b/crates/core/src/runtime/functions/http.rs index 4f524d8..9fe305f 100644 --- a/crates/core/src/runtime/functions/http.rs +++ b/crates/core/src/runtime/functions/http.rs @@ -5,7 +5,7 @@ use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntr use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; use tucana::shared::value::Kind; -use tucana::shared::{ListValue, Struct, Value}; +use tucana::shared::{Struct, Value}; pub fn collect_http_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 9d285ba..29fa7fd 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -12,7 +12,6 @@ use core::context::context::Context; use core::context::executor::Executor; use core::context::registry::FunctionStore; use core::context::signal::Signal; -use core::runtime::functions::collect; use std::collections::HashMap; use tokio::signal; use tonic_health::pb::health_server::HealthServer; From 42351dac9f30ef75ac9fd4f998af55b448ac113f Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:56 +0100 Subject: [PATCH 24/58] ref: cargo clippy --- crates/core/src/context/executor.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index bc180ff..17a27b4 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -25,11 +25,10 @@ impl<'a> Executor<'a> { let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); - if with_trace { - if let Some(run) = tracer.take_run() { + if with_trace + && let Some(run) = tracer.take_run() { println!("{}", crate::debug::render::render_trace(&run)); } - } signal } From 6701195f188d23fc8821c77da29c2c4e1b03e81a Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:11:15 +0100 Subject: [PATCH 25/58] ref: cargo fmt --- crates/core/src/context/executor.rs | 7 +++---- crates/core/src/context/macros.rs | 10 ++++++---- crates/core/src/runtime/functions/number.rs | 1 - crates/core/src/runtime/functions/text.rs | 2 +- crates/taurus/src/main.rs | 6 +++--- crates/tests/src/main.rs | 7 +++++-- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 17a27b4..306d158 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -25,10 +25,9 @@ impl<'a> Executor<'a> { let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); - if with_trace - && let Some(run) = tracer.take_run() { - println!("{}", crate::debug::render::render_trace(&run)); - } + if with_trace && let Some(run) = tracer.take_run() { + println!("{}", crate::debug::render::render_trace(&run)); + } signal } diff --git a/crates/core/src/context/macros.rs b/crates/core/src/context/macros.rs index 076db78..ed4644f 100644 --- a/crates/core/src/context/macros.rs +++ b/crates/core/src/context/macros.rs @@ -39,10 +39,12 @@ macro_rules! args { macro_rules! no_args { ($args_ident:ident) => { if !$args_ident.is_empty() { - return $crate::context::signal::Signal::Failure($crate::runtime::error::RuntimeError::simple( - "InvalidArgumentRuntimeError", - format!("Expected 0 args but received {}", $args_ident.len()), - )); + return $crate::context::signal::Signal::Failure( + $crate::runtime::error::RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!("Expected 0 args but received {}", $args_ident.len()), + ), + ); } }; } diff --git a/crates/core/src/runtime/functions/number.rs b/crates/core/src/runtime/functions/number.rs index 7b8d00f..c60e023 100644 --- a/crates/core/src/runtime/functions/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -793,7 +793,6 @@ mod tests { assert!(r >= 1.0 && r < 10.0); } - #[test] fn test_random_range_numbers_equal() { let mut ctx = Context::default(); diff --git a/crates/core/src/runtime/functions/text.rs b/crates/core/src/runtime/functions/text.rs index 2877ab5..37b70f2 100644 --- a/crates/core/src/runtime/functions/text.rs +++ b/crates/core/src/runtime/functions/text.rs @@ -1,9 +1,9 @@ use crate::context::argument::Argument; +use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; -use crate::{context::context::Context}; use base64::Engine; use tucana::shared::{ListValue, Value, value::Kind}; diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 29fa7fd..67827c3 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -5,13 +5,13 @@ use code0_flow::flow_service::FlowUpdateService; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; -use futures_lite::StreamExt; -use log::error; -use prost::Message; use core::context::context::Context; use core::context::executor::Executor; use core::context::registry::FunctionStore; use core::context::signal::Signal; +use futures_lite::StreamExt; +use log::error; +use prost::Message; use std::collections::HashMap; use tokio::signal; use tonic_health::pb::health_server::HealthServer; diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 87ea2dd..9d8d430 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -116,8 +116,11 @@ impl Case { None => Context::default(), }; - let res = Executor::new(&store, node_functions.clone()) - .execute(self.flow.starting_node_id, &mut context, false); + let res = Executor::new(&store, node_functions.clone()).execute( + self.flow.starting_node_id, + &mut context, + false, + ); match res { core::context::signal::Signal::Failure(err) => { From 3619a47cc445ef02b4571d37821b3faf4238780a Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:18:51 +0100 Subject: [PATCH 26/58] feat: reworked execution logic --- taurus/src/context/executor.rs | 425 ++++++++++++++++++++++++--------- 1 file changed, 316 insertions(+), 109 deletions(-) diff --git a/taurus/src/context/executor.rs b/taurus/src/context/executor.rs index 2622f8a..55279b8 100644 --- a/taurus/src/context/executor.rs +++ b/taurus/src/context/executor.rs @@ -1,8 +1,11 @@ use crate::context::argument::{Argument, ParameterNode}; use crate::context::context::{Context, ContextResult}; -use crate::context::registry::FunctionStore; +use crate::context::registry::{FunctionStore, HandlerFunctionEntry}; use crate::context::signal::Signal; +use crate::debug::trace::{ArgKind, ArgTrace, EdgeKind, Outcome, ReferenceKind}; +use crate::debug::tracer::{ExecutionTracer, Tracer}; use crate::error::RuntimeError; + use std::collections::HashMap; use tucana::shared::NodeFunction; @@ -13,135 +16,339 @@ pub struct Executor<'a> { impl<'a> Executor<'a> { pub fn new(functions: &'a FunctionStore, nodes: HashMap) -> Self { - Executor { functions, nodes } + Self { functions, nodes } + } + + /// This is now the ONLY execution entry point. + pub fn execute(&self, start_node_id: i64, ctx: &mut Context) -> Signal { + let mut tracer = Tracer::new(); + + let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); + + if let Some(run) = tracer.take_run() { + println!("{}", crate::debug::render::render_trace(&run)); + } + + signal } - pub fn execute(&self, starting_node_id: i64, ctx: &mut Context) -> Signal { - let mut current_node_id = starting_node_id; + // Main execution loop + fn execute_call( + &self, + start_node_id: i64, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + ) -> (Signal, u64) { + let mut current = start_node_id; + + let mut call_root_frame: Option = None; + let mut previous_frame: Option = None; loop { - let node = match self.nodes.get(¤t_node_id) { - None => { - return Signal::Failure(RuntimeError::simple( - "NodeNotFound", - format!( - "The node with the database id: {} was not found", - current_node_id - ), - )); + let (signal, frame_id) = self.execute_single_node(current, ctx, tracer); + + if call_root_frame.is_none() { + call_root_frame = Some(frame_id); + } + + // Link linear NEXT chain + if let Some(prev) = previous_frame { + tracer.link_child(prev, frame_id, EdgeKind::Next); + } + previous_frame = Some(frame_id); + + match signal { + Signal::Success(_) => { + let node = self.nodes.get(¤t).unwrap(); + + if let Some(next) = node.next_node_id { + current = next; + continue; + } + + return (signal, call_root_frame.unwrap()); } - Some(n) => n.clone(), - }; - - let entry = match self.functions.get(node.runtime_function_id.as_str()) { - None => { - return Signal::Failure(RuntimeError::simple( - "FunctionNotFound", - format!( - "The function {} (database id: {}) was not found", - node.runtime_function_id, node.database_id - ), - )); + + _ => return (signal, call_root_frame.unwrap()), + } + } + } + + // executes a single node + fn execute_single_node( + &self, + node_id: i64, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + ) -> (Signal, u64) { + let node = match self.nodes.get(&node_id) { + Some(n) => n.clone(), + None => { + let err = + RuntimeError::simple("NodeNotFound", format!("Node {} not found", node_id)); + return (Signal::Failure(err), 0); + } + }; + + let entry = match self.functions.get(node.runtime_function_id.as_str()) { + Some(e) => e, + None => { + let err = RuntimeError::simple( + "FunctionNotFound", + format!("Function {} not found", node.runtime_function_id), + ); + return (Signal::Failure(err), 0); + } + }; + + let frame_id = tracer.enter_node(node.database_id, node.runtime_function_id.as_str()); + + // ---- Build args + let mut args = match self.build_args(&node, ctx, tracer, frame_id) { + Ok(a) => a, + Err(e) => { + ctx.insert_error(node.database_id, e.clone()); + tracer.exit_node( + frame_id, + Outcome::Failure { + error_preview: format!("{:#?}", e), + }, + ); + return (Signal::Failure(e), frame_id); + } + }; + + // ---- Force eager args + if let Err((sig, outcome)) = + self.force_eager_args(&node, entry, &mut args, ctx, tracer, frame_id) + { + tracer.exit_node(frame_id, outcome); + return (sig, frame_id); + } + + // ---- Invoke handler + let result = self.invoke_handler(entry, &args, ctx, tracer); + + // ---- Commit result + let final_signal = self.commit_result(&node, result, ctx, tracer, frame_id); + + (final_signal, frame_id) + } + + fn build_args( + &self, + node: &NodeFunction, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + frame_id: u64, + ) -> Result, RuntimeError> { + let mut args = Vec::with_capacity(node.parameters.len()); + + for (i, param) in node.parameters.iter().enumerate() { + let node_value = param.value.as_ref().ok_or_else(|| { + RuntimeError::simple_str("NodeValueNotFound", "Missing param value") + })?; + + let value = node_value.value.as_ref().ok_or_else(|| { + RuntimeError::simple_str("NodeValueNotFound", "Missing inner value") + })?; + + match value { + tucana::shared::node_value::Value::LiteralValue(v) => { + tracer.record_arg( + frame_id, + ArgTrace { + index: i, + kind: ArgKind::Literal, + preview: format!("{:?}", v), + }, + ); + args.push(Argument::Eval(v.clone())); } - Some(f) => f, - }; - - let mut args: Vec = Vec::with_capacity(node.parameters.len()); - for parameter in &node.parameters { - let node_value = match ¶meter.value { - Some(v) => v, - None => { - return Signal::Failure(RuntimeError::simple_str( - "NodeValueNotFound", - "Missing parameter value: {}", - )); + + tucana::shared::node_value::Value::ReferenceValue(r) => match ctx.get(r.clone()) { + ContextResult::Success(v) => { + let reference = match r.target { + Some(ref_value) => match ref_value { + tucana::shared::reference_value::Target::FlowInput(_) => { + ReferenceKind::FlowInput + } + tucana::shared::reference_value::Target::NodeId(id) => { + ReferenceKind::Result { node_id: id } + } + tucana::shared::reference_value::Target::InputType(input_type) => { + ReferenceKind::InputType { + node_id: input_type.node_id, + input_index: input_type.input_index, + parameter_index: input_type.parameter_index, + } + } + }, + None => ReferenceKind::Empty, + }; + + tracer.record_arg( + frame_id, + ArgTrace { + index: i, + kind: ArgKind::Reference { + reference: reference, + hit: true, + }, + preview: format!("ctx.get({:?}) -> {:?}", r, v), + }, + ); + args.push(Argument::Eval(v)); } - }; - let value = match &node_value.value { - Some(v) => v, - None => { - return Signal::Failure(RuntimeError::simple_str( - "NodeValueNotFound", - "Missing inner value", + ContextResult::Error(e) => return Err(e), + ContextResult::NotFound => { + return Err(RuntimeError::simple_str( + "ReferenceValueNotFound", + "Referenced node not executed", )); } - }; + }, - match value { - tucana::shared::node_value::Value::LiteralValue(val) => { - args.push(Argument::Eval(val.clone())) - } - tucana::shared::node_value::Value::ReferenceValue(reference) => { - let value = ctx.get(reference.node_id); - match value { - ContextResult::Error(runtime_error) => { - return Signal::Failure(runtime_error); - } - ContextResult::Success(result) => { - args.push(Argument::Eval(result.clone())); - } - ContextResult::NotFound => { - return Signal::Failure(RuntimeError::simple_str( - "ReferenceValueNotFound", - "The given node has not been executed but referenced.", - )); - } - } - } - tucana::shared::node_value::Value::NodeFunctionId(id) => { - args.push(Argument::Thunk(*id)) - } + tucana::shared::node_value::Value::NodeFunctionId(id) => { + tracer.record_arg( + frame_id, + ArgTrace { + index: i, + kind: ArgKind::Thunk { + node_id: *id, + eager: false, + executed: false, + }, + preview: format!("thunk({})", id), + }, + ); + args.push(Argument::Thunk(*id)); } } + } + + Ok(args) + } + + fn force_eager_args( + &self, + _node: &NodeFunction, + entry: &HandlerFunctionEntry, + args: &mut [Argument], + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + parent_frame: u64, + ) -> Result<(), (Signal, Outcome)> { + for (i, arg) in args.iter_mut().enumerate() { + let mode = entry + .param_modes + .get(i) + .copied() + .unwrap_or(ParameterNode::Eager); + + if matches!(mode, ParameterNode::Eager) { + if let Argument::Thunk(id) = *arg { + let (child_sig, child_root) = self.execute_call(id, ctx, tracer); - for (i, a) in args.iter_mut().enumerate() { - let mode = entry - .param_modes - .get(i) - .copied() - .unwrap_or(ParameterNode::Eager); - if matches!(mode, ParameterNode::Eager) - && let Argument::Thunk(id) = *a - { - match self.execute(id, ctx) { + tracer.link_child( + parent_frame, + child_root, + EdgeKind::EagerCall { arg_index: i }, + ); + + match child_sig { Signal::Success(v) => { - log::debug!( - "Successfully executed node with database id {}, resulted in value: {:?}", - id, - a - ); - *a = Argument::Eval(v) + *arg = Argument::Eval(v); } - Signal::Failure(err) => { - log::error!("Failed to execute node with database id: {}", id); - return Signal::Failure(err); + s => { + return Err(( + s, + Outcome::Failure { + error_preview: "Eager child failed".into(), + }, + )); } - s @ (Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => return s, } } } + } - let mut run = |node_id: i64, ctx: &mut Context| self.execute(node_id, ctx); - let result = (entry.handler)(&args, ctx, &mut run); + Ok(()) + } - match result { - Signal::Success(value) => { - if let Some(next_node_id) = node.next_node_id { - current_node_id = next_node_id; - continue; - } else { - log::debug!( - "Successfully executed node with database id {}, resulted in value: {:?}", - current_node_id, - value - ); - return Signal::Success(value); - } - } - Signal::Failure(e) => return Signal::Failure(e), - Signal::Return(v) => return Signal::Return(v), - Signal::Respond(v) => return Signal::Respond(v), - Signal::Stop => return Signal::Stop, + fn invoke_handler( + &self, + entry: &HandlerFunctionEntry, + args: &[Argument], + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + ) -> Signal { + let mut run = |node_id: i64, ctx: &mut Context| { + let (sig, _) = self.execute_call(node_id, ctx, tracer); + sig + }; + + (entry.handler)(args, ctx, &mut run) + } + + fn commit_result( + &self, + node: &NodeFunction, + result: Signal, + ctx: &mut Context, + tracer: &mut dyn ExecutionTracer, + frame_id: u64, + ) -> Signal { + match result { + Signal::Success(v) => { + ctx.insert_success(node.database_id, v.clone()); + + tracer.exit_node( + frame_id, + Outcome::Success { + value_preview: format!("{:#?}", v), + }, + ); + + Signal::Success(v) + } + + Signal::Failure(e) => { + ctx.insert_error(node.database_id, e.clone()); + + tracer.exit_node( + frame_id, + Outcome::Failure { + error_preview: format!("{:#?}", e), + }, + ); + + Signal::Failure(e) + } + + Signal::Return(v) => { + tracer.exit_node( + frame_id, + Outcome::Return { + value_preview: format!("{:#?}", v), + }, + ); + Signal::Return(v) + } + + Signal::Respond(v) => { + tracer.exit_node( + frame_id, + Outcome::Respond { + value_preview: format!("{:#?}", v), + }, + ); + Signal::Respond(v) + } + + Signal::Stop => { + tracer.exit_node(frame_id, Outcome::Stop); + Signal::Stop } } } From a8f11870d95d40b180ec96c1e552e342e1ebbc9d Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:00 +0100 Subject: [PATCH 27/58] feat: new reference logic impl --- taurus/src/context/context.rs | 102 +++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/taurus/src/context/context.rs b/taurus/src/context/context.rs index 3b4cdef..f83a350 100644 --- a/taurus/src/context/context.rs +++ b/taurus/src/context/context.rs @@ -1,6 +1,6 @@ use crate::error::RuntimeError; use std::collections::HashMap; -use tucana::shared::Value; +use tucana::shared::{InputType, ReferenceValue, Value, value::Kind}; #[derive(Clone)] pub enum ContextResult { @@ -12,16 +12,114 @@ pub enum ContextResult { #[derive(Default)] pub struct Context { results: HashMap, + input_types: HashMap, + flow_input: Value, } impl Context { - pub fn get(&mut self, id: i64) -> ContextResult { + pub fn new(flow_input: Value) -> Self { + return Context { + results: HashMap::new(), + input_types: HashMap::new(), + flow_input, + }; + } + + pub fn get(&mut self, reference: ReferenceValue) -> ContextResult { + let target = match reference.target { + Some(t) => t, + None => return ContextResult::NotFound, + }; + + let res = match target { + tucana::shared::reference_value::Target::FlowInput(_) => self.get_flow_input(), + tucana::shared::reference_value::Target::NodeId(i) => self.get_result(i), + tucana::shared::reference_value::Target::InputType(input_type) => { + self.get_input_type(input_type) + } + }; + + if reference.paths.is_empty() { + return res; + } + + if let ContextResult::Success(value) = res { + let mut curr = value; + + for path in reference.paths { + if let Some(index) = path.array_index { + match curr.kind { + Some(ref kind) => { + if let Kind::ListValue(list) = &kind { + match list.values.get(index as usize) { + Some(x) => { + curr = x.clone(); + } + None => return ContextResult::NotFound, + } + } + } + None => return ContextResult::NotFound, + } + } + + if let Some(path) = path.path { + let splits = path.split("."); + + for part in splits { + match curr.kind { + Some(ref kind) => { + if let Kind::StructValue(struct_value) = &kind { + match struct_value.fields.get(part) { + Some(x) => { + curr = x.clone(); + } + None => return ContextResult::NotFound, + } + } + } + None => return ContextResult::NotFound, + } + } + } + } + + ContextResult::Success(curr) + } else { + res + } + } + + fn get_result(&mut self, id: i64) -> ContextResult { match self.results.get(&id) { None => ContextResult::NotFound, Some(result) => result.clone(), } } + fn get_flow_input(&mut self) -> ContextResult { + return ContextResult::Success(self.flow_input.clone()); + } + + fn get_input_type(&mut self, input_type: InputType) -> ContextResult { + match self.input_types.get(&input_type) { + Some(v) => ContextResult::Success(v.clone()), + None => ContextResult::NotFound, + } + } + + pub fn clear_input_type(&mut self, input_type: InputType) { + self.input_types.remove(&input_type); + } + + pub fn insert_input_type(&mut self, input_type: InputType, value: Value) { + self.input_types.insert(input_type, value); + } + + pub fn insert_flow_input(&mut self, value: Value) { + self.flow_input = value; + } + pub fn insert_success(&mut self, id: i64, value: Value) { self.results.insert(id, ContextResult::Success(value)); } From 0783904559c141432c346b0c7e11d01690f08fb1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:06 +0100 Subject: [PATCH 28/58] feat: added tracing --- taurus/src/debug/mod.rs | 3 + taurus/src/debug/render.rs | 156 +++++++++++++++++++++++++++++++++++++ taurus/src/debug/trace.rs | 75 ++++++++++++++++++ taurus/src/debug/tracer.rs | 97 +++++++++++++++++++++++ 4 files changed, 331 insertions(+) create mode 100644 taurus/src/debug/mod.rs create mode 100644 taurus/src/debug/render.rs create mode 100644 taurus/src/debug/trace.rs create mode 100644 taurus/src/debug/tracer.rs diff --git a/taurus/src/debug/mod.rs b/taurus/src/debug/mod.rs new file mode 100644 index 0000000..813a7e6 --- /dev/null +++ b/taurus/src/debug/mod.rs @@ -0,0 +1,3 @@ +pub mod trace; +pub mod tracer; +pub mod render; diff --git a/taurus/src/debug/render.rs b/taurus/src/debug/render.rs new file mode 100644 index 0000000..afd770c --- /dev/null +++ b/taurus/src/debug/render.rs @@ -0,0 +1,156 @@ +use crate::debug::trace::{ArgKind, EdgeKind, ExecFrame, Outcome, TraceRun}; +use std::collections::HashMap; + +fn short(s: &str, max: usize) -> String { + if s.len() <= max { + return s.to_string(); + } + format!("{}…", &s[..max]) +} + +fn ms(f: &ExecFrame) -> Option { + f.end.map(|e| e.duration_since(f.start).as_millis()) +} + +pub fn render_trace(run: &TraceRun) -> String { + let mut by_id: HashMap = HashMap::new(); + for f in &run.frames { + by_id.insert(f.frame_id, f); + } + + let mut out = String::new(); + render_frame(run.root, &by_id, "", true, &mut out); + out +} + +fn render_frame( + id: u64, + by_id: &HashMap, + prefix: &str, + is_last: bool, + out: &mut String, +) { + let f = by_id[&id]; + + let branch = if prefix.is_empty() { + "" // root + } else if is_last { + "└─ " + } else { + "├─ " + }; + + let dur = ms(f).map(|m| format!(" ({}ms)", m)).unwrap_or_default(); + + out.push_str(&format!( + "{prefix}{branch}#{fid} node={nid} fn={name}{dur}\n", + prefix = prefix, + branch = branch, + fid = f.frame_id, + nid = f.node_id, + name = f.function_name, + dur = dur + )); + + // args + for a in &f.args { + let pfx = if prefix.is_empty() { + "" + } else if is_last { + " " + } else { + "│ " + }; + + let kind = match &a.kind { + ArgKind::Literal => "lit", + ArgKind::Reference { hit, .. } => { + if *hit { + "ref✓" + } else { + "ref✗" + } + } + ArgKind::Thunk { + eager, executed, .. + } => match (*eager, *executed) { + (true, true) => "thunk eager✓", + (true, false) => "thunk eager", + (false, true) => "thunk lazy✓", + (false, false) => "thunk lazy", + }, + }; + + out.push_str(&format!( + "{prefix}{pfx} arg[{}] {:<12} {}\n", + a.index, + kind, + short(&a.preview, 1600), + prefix = prefix, + pfx = pfx + )); + } + + // outcome + let outcome_line = match &f.outcome { + Some(Outcome::Success { value_preview }) => format!("✅ {}", short(value_preview, 1800)), + Some(Outcome::Failure { error_preview }) => format!("❌ {}", short(error_preview, 1800)), + Some(Outcome::Return { value_preview }) => format!("↩️ {}", short(value_preview, 1800)), + Some(Outcome::Respond { value_preview }) => format!("💬 {}", short(value_preview, 1800)), + Some(Outcome::Stop) => "🛑 Stop".to_string(), + None => "…".to_string(), + }; + + let pfx = if prefix.is_empty() { + "" + } else if is_last { + " " + } else { + "│ " + }; + out.push_str(&format!( + "{prefix}{pfx} => {o}\n", + prefix = prefix, + pfx = pfx, + o = outcome_line + )); + + // children + let kids = &f.children; + if kids.is_empty() { + return; + } + + let new_prefix = if prefix.is_empty() { + String::new() + } else { + format!("{}{}", prefix, if is_last { " " } else { "│ " }) + }; + + for (idx, (edge, child_id)) in kids.iter().enumerate() { + let last = idx + 1 == kids.len(); + + // print edge label line (nice readability) + let edge_label = match edge { + EdgeKind::Next => "→ NEXT".to_string(), + EdgeKind::EagerCall { arg_index } => format!("↳ eager(arg#{})", arg_index), + }; + + let edge_pfx = if prefix.is_empty() { + "" + } else if is_last { + " " + } else { + "│ " + }; + + out.push_str(&format!( + "{prefix}{edge_pfx} {edge}\n", + prefix = prefix, + edge_pfx = edge_pfx, + edge = edge_label + )); + + render_frame(*child_id, by_id, &new_prefix, last, out); + } +} diff --git a/taurus/src/debug/trace.rs b/taurus/src/debug/trace.rs new file mode 100644 index 0000000..8eeee7a --- /dev/null +++ b/taurus/src/debug/trace.rs @@ -0,0 +1,75 @@ +// src/debug_trace/trace.rs +use std::time::Instant; + +#[derive(Debug, Clone)] +pub enum EdgeKind { + /// Linear control-flow via next_node_id + Next, + /// Eager evaluation of a thunk argument (child execution) + EagerCall { arg_index: usize }, +} + +#[derive(Debug, Clone)] +pub enum ArgKind { + Literal, + Reference { + reference: ReferenceKind, + hit: bool, + }, + Thunk { + node_id: i64, + eager: bool, + executed: bool, + }, +} + +#[derive(Debug, Clone)] +pub enum ReferenceKind { + Result { + node_id: i64, + }, + InputType { + node_id: i64, + input_index: i64, + parameter_index: i64, + }, + FlowInput, + Empty +} + +#[derive(Debug, Clone)] +pub struct ArgTrace { + pub index: usize, + pub kind: ArgKind, + pub preview: String, +} + +#[derive(Debug, Clone)] +pub enum Outcome { + Success { value_preview: String }, + Failure { error_preview: String }, + Return { value_preview: String }, + Respond { value_preview: String }, + Stop, +} + +#[derive(Debug, Clone)] +pub struct ExecFrame { + pub frame_id: u64, // unique execution instance id + pub node_id: i64, // database_id + pub function_name: String, // runtime_function_id + pub args: Vec, + pub outcome: Option, + + pub start: Instant, + pub end: Option, + + /// Child edges to other frames (CALLs and NEXT links) + pub children: Vec<(EdgeKind, u64)>, +} + +#[derive(Debug, Clone)] +pub struct TraceRun { + pub frames: Vec, + pub root: u64, +} diff --git a/taurus/src/debug/tracer.rs b/taurus/src/debug/tracer.rs new file mode 100644 index 0000000..ed452df --- /dev/null +++ b/taurus/src/debug/tracer.rs @@ -0,0 +1,97 @@ +use std::time::Instant; + +use crate::debug::trace::{ArgTrace, EdgeKind, ExecFrame, Outcome, TraceRun}; + +pub trait ExecutionTracer { + fn enter_node(&mut self, node_id: i64, function_name: &str) -> u64; + fn record_arg(&mut self, frame_id: u64, arg: ArgTrace); + fn link_child(&mut self, parent_frame: u64, child_frame: u64, edge: EdgeKind); + fn exit_node(&mut self, frame_id: u64, outcome: Outcome); +} + +pub struct Tracer { + next_id: u64, + pub run: Option, + stack: Vec, +} + +impl Tracer { + pub fn new() -> Self { + Self { + next_id: 1, + run: None, + stack: vec![], + } + } + + fn frames_mut(&mut self) -> &mut Vec { + &mut self.run.as_mut().unwrap().frames + } + + fn get_frame_mut(&mut self, frame_id: u64) -> &mut ExecFrame { + let idx = self + .frames_mut() + .iter() + .position(|f| f.frame_id == frame_id) + .expect("trace frame must exist"); + &mut self.frames_mut()[idx] + } + + pub fn take_run(self) -> Option { + self.run + } +} + +impl ExecutionTracer for Tracer { + fn enter_node(&mut self, node_id: i64, function_name: &str) -> u64 { + if self.run.is_none() { + self.run = Some(TraceRun { + frames: vec![], + root: 0, + }); + } + + let frame_id = self.next_id; + self.next_id += 1; + + let frame = ExecFrame { + frame_id, + node_id, + function_name: function_name.to_string(), + args: vec![], + outcome: None, + start: Instant::now(), + end: None, + children: vec![], + }; + + let run = self.run.as_mut().unwrap(); + if run.root == 0 { + run.root = frame_id; + } + run.frames.push(frame); + + self.stack.push(frame_id); + frame_id + } + + fn record_arg(&mut self, frame_id: u64, arg: ArgTrace) { + self.get_frame_mut(frame_id).args.push(arg); + } + + fn link_child(&mut self, parent_frame: u64, child_frame: u64, edge: EdgeKind) { + self.get_frame_mut(parent_frame) + .children + .push((edge, child_frame)); + } + + fn exit_node(&mut self, frame_id: u64, outcome: Outcome) { + let f = self.get_frame_mut(frame_id); + f.outcome = Some(outcome); + f.end = Some(Instant::now()); + + // Pop in LIFO order + let popped = self.stack.pop(); + debug_assert_eq!(popped, Some(frame_id)); + } +} From d1b7d42cf31955df4a6b40101227172fda5b39fb Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:22 +0100 Subject: [PATCH 29/58] feat: wip new implementation of array functions --- taurus/src/implementation/array.rs | 396 +++++++++++++++++------------ 1 file changed, 233 insertions(+), 163 deletions(-) diff --git a/taurus/src/implementation/array.rs b/taurus/src/implementation/array.rs index 642d0c1..d247f06 100644 --- a/taurus/src/implementation/array.rs +++ b/taurus/src/implementation/array.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use tucana::shared::{ListValue, Value, value::Kind}; use crate::context::argument::Argument; +use crate::context::argument::ParameterNode::{Eager, Lazy}; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; @@ -12,14 +13,38 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ ("std::list::at", HandlerFn::eager(at, 2)), ("std::list::concat", HandlerFn::eager(concat, 2)), - ("std::list::filter", HandlerFn::eager(filter, 2)), - ("std::list::find", HandlerFn::eager(find, 2)), - ("std::list::find_last", HandlerFn::eager(find_last, 2)), - ("std::list::find_index", HandlerFn::eager(find_index, 2)), + //TODO + ( + "std::list::filter", + HandlerFn::into_function_entry(filter, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::find", + HandlerFn::into_function_entry(find, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::find_last", + HandlerFn::into_function_entry(find_last, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::find_index", + HandlerFn::into_function_entry(find_index, vec![Eager, Lazy]), + ), ("std::list::first", HandlerFn::eager(first, 1)), ("std::list::last", HandlerFn::eager(last, 1)), - ("std::list::for_each", HandlerFn::eager(for_each, 0)), - ("std::list::map", HandlerFn::eager(map, 2)), + //TODO + ( + "std::list::for_each", + HandlerFn::into_function_entry(for_each, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::map", + HandlerFn::into_function_entry(map, vec![Eager, Lazy]), + ), ("std::list::push", HandlerFn::eager(push, 2)), ("std::list::pop", HandlerFn::eager(pop, 1)), ("std::list::remove", HandlerFn::eager(remove, 2)), @@ -27,8 +52,16 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ("std::list::size", HandlerFn::eager(size, 1)), ("std::list::index_of", HandlerFn::eager(index_of, 2)), ("std::list::to_unique", HandlerFn::eager(to_unique, 1)), - ("std::list::sort", HandlerFn::eager(sort, 2)), - ("std::list::sort_reverse", HandlerFn::eager(sort_reverse, 2)), + //TODO + ( + "std::list::sort", + HandlerFn::into_function_entry(sort, vec![Eager, Lazy]), + ), + //TODO + ( + "std::list::sort_reverse", + HandlerFn::into_function_entry(sort_reverse, vec![Eager, Lazy]), + ), ("std::list::reverse", HandlerFn::eager(reverse, 1)), ("std::list::flat", HandlerFn::eager(flat, 1)), ("std::list::min", HandlerFn::eager(min, 1)), @@ -38,6 +71,23 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ] } +fn as_list(value: &Value, err: &'static str) -> Result { + match value.kind.clone().unwrap_or(Kind::NullValue(0)) { + Kind::ListValue(lv) => Ok(lv), + _ => Err(RuntimeError::simple_str("InvalidArgumentRuntimeError", err)), + } +} + +fn as_bool(value: &Value) -> Result { + match value.kind.clone().unwrap_or(Kind::NullValue(0)) { + Kind::BoolValue(b) => Ok(b), + _ => Err(RuntimeError::simple_str( + "InvalidArgumentRuntimeError", + "Expected boolean result from predicate", + )), + } +} + fn at( args: &[Argument], _ctx: &mut Context, @@ -98,192 +148,174 @@ fn concat( fn filter( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array".to_string(), + format!( + "filter expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans".to_string(), - )); + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + let mut out: Vec = Vec::new(); + + for item in array.values.iter().cloned() { + let pred_sig = run(*predicate_node, ctx); + + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => out.push(item), + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut i = 0usize; - let new_array = array - .values - .iter() - .filter(|_| { - let keep = *preds.get(i).unwrap_or(&false); - i += 1; - keep - }) - .cloned() - .collect::>(); - Signal::Success(Value { - kind: Some(Kind::ListValue(ListValue { values: new_array })), + kind: Some(Kind::ListValue(ListValue { values: out })), }) } fn find( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "find expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans", - )); + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + for item in array.values.iter().cloned() { + let pred_sig = run(*predicate_node, ctx); + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => return Signal::Success(item), + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut i = 0usize; - let item = array - .values - .iter() - .find(|&_| { - let keep = *preds.get(i).unwrap_or(&false); - i += 1; - keep - }) - .cloned(); - - match item { - Some(v) => Signal::Success(v), - None => Signal::Failure(RuntimeError::simple_str( - "NotFoundError", - "No item found that satisfies the predicate", - )), - } + Signal::Failure(RuntimeError::simple_str( + "NotFoundError", + "No item found that satisfies the predicate", + )) } - fn find_last( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "find_last expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans", - )); + + let mut array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; + array.values.reverse(); - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + for item in array.values.into_iter() { + let pred_sig = run(*predicate_node, ctx); + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => return Signal::Success(item), + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut i = 0usize; - let mut reversed = array.values.clone(); - reversed.reverse(); - - let item = reversed.into_iter().find(|_| { - let keep = *preds.get(i).unwrap_or(&false); - i += 1; - keep - }); - - match item { - Some(v) => Signal::Success(v), - None => Signal::Failure(RuntimeError::simple_str( - "NotFoundError", - "No item found that satisfies the predicate", - )), - } + Signal::Failure(RuntimeError::simple_str( + "NotFoundError", + "No item found that satisfies the predicate", + )) } fn find_index( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, predicate_v: Value); - let Kind::ListValue(array) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(predicate_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "find_index expects (array: eager, predicate: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(resolved_predicate) = - predicate_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of booleans", - )); + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut preds: Vec = Vec::with_capacity(resolved_predicate.values.len()); - for v in &resolved_predicate.values { - if let Some(Kind::BoolValue(b)) = v.kind { - preds.push(b); + for (idx, _item) in array.values.iter().cloned().enumerate() { + let pred_sig = run(*predicate_node, ctx); + + match pred_sig { + Signal::Success(v) => match as_bool(&v) { + Ok(true) => { + return Signal::Success(Value { + kind: Some(Kind::NumberValue(idx as f64)), + }); + } + Ok(false) => {} + Err(e) => return Signal::Failure(e), + }, + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } } } - let mut idx = 0usize; - let found = array.values.iter().find(|_| { - let keep = *preds.get(idx).unwrap_or(&false); - if !keep { - idx += 1; - } - keep - }); - - match found { - Some(_) => Signal::Success(Value { - kind: Some(Kind::NumberValue(idx as f64)), - }), - None => Signal::Failure(RuntimeError::simple_str( - "NotFoundError", - "No item found that satisfies the predicate", - )), - } + Signal::Failure(RuntimeError::simple_str( + "NotFoundError", + "No item found that satisfies the predicate", + )) } - fn first( args: &[Argument], _ctx: &mut Context, @@ -315,18 +347,38 @@ fn last( } } -/// for_each has no implementation -/// -/// Reason: -/// The definition itself takes in an array and a node -/// The node itself will be executed on the arrays elements -/// If the node is (CONSUMER) resolved it goes in this function --> therefor all code is already executed fn for_each( - _args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + args: &[Argument], + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - // Already executed by the engine (consumer); return Null + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), + )); + }; + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), + }; + + for _ in array.values.iter().cloned() { + let sig = run(*transform_node, ctx); + + match sig { + Signal::Success(_) => {} + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } + } + } + Signal::Success(Value { kind: Some(Kind::NullValue(0)), }) @@ -334,21 +386,39 @@ fn for_each( fn map( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - // (array, transformed_results[]) - args!(args => _array_v: Value, transform_v: Value); - let Kind::ListValue(transform_result) = - transform_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) - else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected transform result to be an array", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), )); }; + + let array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), + }; + + let mut out: Vec = Vec::with_capacity(array.values.len()); + + for _ in array.values.iter().cloned() { + let sig = run(*transform_node, ctx); + match sig { + Signal::Success(v) => out.push(v), + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + return other; + } + } + } + Signal::Success(Value { - kind: Some(Kind::ListValue(transform_result.clone())), + kind: Some(Kind::ListValue(ListValue { values: out })), }) } From 72d5cb4cef618c965f20cf3c4cb9e134c8d7968a Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:34 +0100 Subject: [PATCH 30/58] dependencies: updated to tucana 0.0.53 --- Cargo.lock | 524 ++++++++++++++++++++++------------------------------- Cargo.toml | 2 +- 2 files changed, 216 insertions(+), 310 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25b0683..6b2a4c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -43,29 +43,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "async-nats" @@ -129,9 +129,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -154,9 +154,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -178,15 +178,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -199,18 +199,18 @@ dependencies = [ [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.2.45" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "num-traits", ] @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "const-oid" @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -347,9 +347,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" @@ -364,9 +364,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -480,9 +480,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -513,24 +513,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -547,35 +547,34 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-sink", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -583,9 +582,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -594,21 +593,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", @@ -620,9 +607,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -648,9 +635,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -660,12 +647,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -706,9 +692,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -742,13 +728,12 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -809,9 +794,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -823,9 +808,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -871,12 +856,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -898,15 +883,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -917,9 +902,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -934,15 +919,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -964,9 +949,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -976,9 +961,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -1000,7 +985,7 @@ dependencies = [ "data-encoding", "ed25519", "ed25519-dalek", - "getrandom 0.2.16", + "getrandom 0.2.17", "log", "rand 0.8.5", "signatory", @@ -1017,9 +1002,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-traits" @@ -1032,9 +1017,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1108,28 +1093,29 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", + "hashbrown 0.15.5", "indexmap", ] [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -1138,9 +1124,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -1160,15 +1146,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] @@ -1209,9 +1195,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1228,15 +1214,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", "itertools", "log", "multimap", - "once_cell", "petgraph", "prettyplease", "prost", @@ -1263,18 +1248,18 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] [[package]] name = "pulldown-cmark" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" dependencies = [ "bitflags", "memchr", @@ -1283,27 +1268,27 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "21.1.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" dependencies = [ "pulldown-cmark", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" @@ -1323,7 +1308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ "chacha20", - "getrandom 0.4.1", + "getrandom 0.4.2", "rand_core 0.10.0", ] @@ -1343,7 +1328,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -1354,9 +1339,9 @@ checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1366,9 +1351,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1377,9 +1362,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -1389,7 +1374,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1406,9 +1391,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -1419,14 +1404,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -1455,9 +1440,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] @@ -1474,21 +1459,15 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -1500,9 +1479,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -1522,9 +1501,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -1568,15 +1547,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1618,10 +1597,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1649,9 +1629,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1661,12 +1641,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1693,9 +1673,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.109" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1734,17 +1714,17 @@ dependencies = [ "tokio", "tonic", "tonic-health", - "tucana 0.0.52", + "tucana 0.0.53", ] [[package]] name = "tempfile" -version = "3.23.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1772,30 +1752,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -1829,9 +1809,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -1850,9 +1830,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -1862,9 +1842,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -1925,9 +1905,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" +checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" dependencies = [ "prettyplease", "proc-macro2", @@ -1950,9 +1930,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", "prost", @@ -1961,9 +1941,9 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" +checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" dependencies = [ "prettyplease", "proc-macro2", @@ -1977,9 +1957,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -2008,9 +1988,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2019,9 +1999,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -2030,9 +2010,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -2055,9 +2035,9 @@ dependencies = [ [[package]] name = "tucana" -version = "0.0.52" +version = "0.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e1cec14d8140417b330fffd1fb07f52cbd69063a4bd5688e63511b5bf8c8aa" +checksum = "ca58639610a8fe299f3615f8774624b872833a53b589245c99d9df15f6fd8f7a" dependencies = [ "pbjson", "pbjson-build", @@ -2101,15 +2081,15 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -2125,9 +2105,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -2180,11 +2160,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen 0.46.0", + "wit-bindgen", ] [[package]] @@ -2193,7 +2173,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -2236,14 +2216,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.4", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -2269,16 +2249,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -2296,31 +2267,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -2329,102 +2283,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2544,18 +2444,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", @@ -2621,3 +2521,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 14f408e..57ecedb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2024" [workspace.dependencies] code0-flow = { version = "0.0.27" } -tucana = { version = "0.0.52" } +tucana = { version = "0.0.53" } tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] } log = "0.4.27" futures-lite = "2.6.0" From 816310a009fa603e0c4d041bb8f48211ba284da8 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 23 Feb 2026 13:19:43 +0100 Subject: [PATCH 31/58] feat: added new module --- taurus/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/taurus/src/main.rs b/taurus/src/main.rs index 4b1ecdc..8c791ec 100644 --- a/taurus/src/main.rs +++ b/taurus/src/main.rs @@ -1,5 +1,6 @@ mod config; pub mod context; +pub mod debug; pub mod error; pub mod implementation; @@ -7,6 +8,8 @@ use crate::config::Config; use crate::context::executor::Executor; use crate::context::registry::FunctionStore; use crate::context::signal::Signal; +use crate::debug::render::render_trace; +use crate::debug::tracer::Tracer; use crate::implementation::collect; use code0_flow::flow_service::FlowUpdateService; From 8a5ab498e79d44544f8fa09c364125c11c1a1023 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:09:48 +0100 Subject: [PATCH 32/58] feat: added current node id to context --- taurus/src/context/context.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/taurus/src/context/context.rs b/taurus/src/context/context.rs index f83a350..d66b765 100644 --- a/taurus/src/context/context.rs +++ b/taurus/src/context/context.rs @@ -14,6 +14,7 @@ pub struct Context { results: HashMap, input_types: HashMap, flow_input: Value, + current_node_id: i64, } impl Context { @@ -22,9 +23,18 @@ impl Context { results: HashMap::new(), input_types: HashMap::new(), flow_input, + current_node_id: 0, }; } + pub fn get_current_node_id(&mut self) -> i64 { + return self.current_node_id; + } + + pub fn set_current_node_id(&mut self, node_id: i64) { + self.current_node_id = node_id; + } + pub fn get(&mut self, reference: ReferenceValue) -> ContextResult { let target = match reference.target { Some(t) => t, From 51f26054b14b50c47812cc685dced78951707aae Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:09:58 +0100 Subject: [PATCH 33/58] feat: set current node id in execution loop --- taurus/src/context/executor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/taurus/src/context/executor.rs b/taurus/src/context/executor.rs index 55279b8..8e8bbde 100644 --- a/taurus/src/context/executor.rs +++ b/taurus/src/context/executor.rs @@ -81,6 +81,7 @@ impl<'a> Executor<'a> { ctx: &mut Context, tracer: &mut dyn ExecutionTracer, ) -> (Signal, u64) { + ctx.set_current_node_id(node_id); let node = match self.nodes.get(&node_id) { Some(n) => n.clone(), None => { From 3eebf06b10c5e1a33e2e78f9a8ccc4329cc392a2 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:10:13 +0100 Subject: [PATCH 34/58] feat: added new implementation for iterators needing input types --- taurus/src/implementation/array.rs | 270 ++++++++++++++++++++++------- 1 file changed, 208 insertions(+), 62 deletions(-) diff --git a/taurus/src/implementation/array.rs b/taurus/src/implementation/array.rs index d247f06..803313d 100644 --- a/taurus/src/implementation/array.rs +++ b/taurus/src/implementation/array.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; +use tucana::shared::InputType; use tucana::shared::{ListValue, Value, value::Kind}; use crate::context::argument::Argument; @@ -13,34 +14,28 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ ("std::list::at", HandlerFn::eager(at, 2)), ("std::list::concat", HandlerFn::eager(concat, 2)), - //TODO ( "std::list::filter", HandlerFn::into_function_entry(filter, vec![Eager, Lazy]), ), - //TODO ( "std::list::find", HandlerFn::into_function_entry(find, vec![Eager, Lazy]), ), - //TODO ( "std::list::find_last", HandlerFn::into_function_entry(find_last, vec![Eager, Lazy]), ), - //TODO ( "std::list::find_index", HandlerFn::into_function_entry(find_index, vec![Eager, Lazy]), ), ("std::list::first", HandlerFn::eager(first, 1)), ("std::list::last", HandlerFn::eager(last, 1)), - //TODO ( "std::list::for_each", HandlerFn::into_function_entry(for_each, vec![Eager, Lazy]), ), - //TODO ( "std::list::map", HandlerFn::into_function_entry(map, vec![Eager, Lazy]), @@ -52,12 +47,10 @@ pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ("std::list::size", HandlerFn::eager(size, 1)), ("std::list::index_of", HandlerFn::eager(index_of, 2)), ("std::list::to_unique", HandlerFn::eager(to_unique, 1)), - //TODO ( "std::list::sort", HandlerFn::into_function_entry(sort, vec![Eager, Lazy]), ), - //TODO ( "std::list::sort_reverse", HandlerFn::into_function_entry(sort_reverse, vec![Eager, Lazy]), @@ -167,13 +160,19 @@ fn filter( }; let mut out: Vec = Vec::new(); - - for item in array.values.iter().cloned() { + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { - Ok(true) => out.push(item), + Ok(true) => out.push(item.clone()), Ok(false) => {} Err(e) => return Signal::Failure(e), }, @@ -184,6 +183,7 @@ fn filter( } } + ctx.clear_input_type(input_type); Signal::Success(Value { kind: Some(Kind::ListValue(ListValue { values: out })), }) @@ -208,22 +208,37 @@ fn find( Ok(a) => a, Err(e) => return Signal::Failure(e), }; + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for item in array.values.iter().cloned() { + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { - Ok(true) => return Signal::Success(item), - Ok(false) => {} - Err(e) => return Signal::Failure(e), + Ok(true) => { + ctx.clear_input_type(input_type); + return Signal::Success(item.clone()); + } + Ok(false) => continue, + Err(e) => { + ctx.clear_input_type(input_type); + return Signal::Failure(e); + } }, other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } + ctx.clear_input_type(input_type); Signal::Failure(RuntimeError::simple_str( "NotFoundError", "No item found that satisfies the predicate", @@ -249,22 +264,36 @@ fn find_last( Err(e) => return Signal::Failure(e), }; array.values.reverse(); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; for item in array.values.into_iter() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { - Ok(true) => return Signal::Success(item), - Ok(false) => {} - Err(e) => return Signal::Failure(e), + Ok(true) => { + ctx.clear_input_type(input_type); + return Signal::Success(item); + } + Ok(false) => continue, + Err(e) => { + ctx.clear_input_type(input_type); + return Signal::Failure(e); + } }, other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } - + ctx.clear_input_type(input_type); Signal::Failure(RuntimeError::simple_str( "NotFoundError", "No item found that satisfies the predicate", @@ -290,27 +319,40 @@ fn find_index( Ok(a) => a, Err(e) => return Signal::Failure(e), }; + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for (idx, _item) in array.values.iter().cloned().enumerate() { + for (idx, item) in array.values.iter().enumerate() { + ctx.insert_input_type(input_type, item.clone()); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { Ok(true) => { + ctx.clear_input_type(input_type); return Signal::Success(Value { kind: Some(Kind::NumberValue(idx as f64)), }); } - Ok(false) => {} - Err(e) => return Signal::Failure(e), + Ok(false) => continue, + Err(e) => { + ctx.clear_input_type(input_type); + return Signal::Failure(e); + } }, other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } + ctx.clear_input_type(input_type); Signal::Failure(RuntimeError::simple_str( "NotFoundError", "No item found that satisfies the predicate", @@ -366,8 +408,15 @@ fn for_each( Ok(a) => a, Err(e) => return Signal::Failure(e), }; + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for _ in array.values.iter().cloned() { + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let sig = run(*transform_node, ctx); match sig { @@ -379,6 +428,7 @@ fn for_each( } } + ctx.clear_input_type(input_type); Signal::Success(Value { kind: Some(Kind::NullValue(0)), }) @@ -405,18 +455,27 @@ fn map( }; let mut out: Vec = Vec::with_capacity(array.values.len()); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; - for _ in array.values.iter().cloned() { + for item in array.values.iter() { + ctx.insert_input_type(input_type, item.clone()); let sig = run(*transform_node, ctx); match sig { Signal::Success(v) => out.push(v), other @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); return other; } } } + ctx.clear_input_type(input_type); Signal::Success(Value { kind: Some(Kind::ListValue(ListValue { values: out })), }) @@ -569,34 +628,77 @@ fn to_unique( fn sort( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - // array, resolved comparator yields -1/0/1 sequence - args!(args => array_v: Value, cmp_v: Value); - let Kind::ListValue(mut arr) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(cmp_vals) = cmp_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of numbers", - )); + + let mut array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut comps: Vec = Vec::new(); - for v in &cmp_vals.values { - if let Some(Kind::NumberValue(n)) = v.kind { - comps.push(n); + let mut out: Vec = Vec::new(); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; + + let input_type_next = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 1, + }; + + let mut signals = Vec::new(); + array.values.sort_by(|a, b| { + ctx.insert_input_type(input_type, a.clone()); + ctx.insert_input_type(input_type_next, b.clone()); + let sig = run(*transform_node, ctx); + signals.push(sig); + return Ordering::Equal; + }); + + for sig in signals { + match sig { + Signal::Success(v) => { + if let Value { + kind: Some(Kind::NumberValue(i)), + } = v + { + out.push(i); + } else { + ctx.clear_input_type(input_type); + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "expected return value of comparator to be a number but was {:?}", + v + ), + )); + } + } + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); + return other; + } } } let mut i = 0usize; - arr.values.sort_by(|_, _| { - let comp = *comps.get(i).unwrap_or(&0.0); + array.values.sort_by(|_, _| { + let comp = *out.get(i).unwrap_or(&0.0); i += 1; match comp { n if n < 0.0 => Ordering::Less, @@ -606,41 +708,85 @@ fn sort( }); Signal::Success(Value { - kind: Some(Kind::ListValue(arr)), + kind: Some(Kind::ListValue(array)), }) } fn sort_reverse( args: &[Argument], - _ctx: &mut Context, - _run: &mut dyn FnMut(i64, &mut Context) -> Signal, + ctx: &mut Context, + run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => array_v: Value, cmp_v: Value); - let Kind::ListValue(mut arr) = array_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( + let [Argument::Eval(array_v), Argument::Thunk(transform_node)] = args else { + return Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", - "Expected first argument to be an array", + format!( + "map expects (array: eager, transform: lazy thunk), got {:?}", + args + ), )); }; - let Kind::ListValue(cmp_vals) = cmp_v.kind.ok_or(()).unwrap_or(Kind::NullValue(0)) else { - return Signal::Failure(RuntimeError::simple_str( - "InvalidArgumentRuntimeError", - "Expected second argument to be an array of numbers", - )); + + let mut array = match as_list(array_v, "Expected first argument to be an array") { + Ok(a) => a, + Err(e) => return Signal::Failure(e), }; - let mut comps: Vec = Vec::new(); - for v in &cmp_vals.values { - if let Some(Kind::NumberValue(n)) = v.kind { - comps.push(n); + let mut out: Vec = Vec::new(); + let node_id = ctx.get_current_node_id(); + let input_type = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 0, + }; + + let input_type_next = InputType { + node_id: node_id, + parameter_index: 1, + input_index: 1, + }; + + let mut signals = Vec::new(); + array.values.sort_by(|a, b| { + ctx.insert_input_type(input_type, a.clone()); + ctx.insert_input_type(input_type_next, b.clone()); + let sig = run(*transform_node, ctx); + signals.push(sig); + return Ordering::Equal; + }); + + for sig in signals { + match sig { + Signal::Success(v) => { + if let Value { + kind: Some(Kind::NumberValue(i)), + } = v + { + out.push(i); + } else { + ctx.clear_input_type(input_type); + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "expected return value of comparator to be a number but was {:?}", + v + ), + )); + } + } + other + @ (Signal::Failure(_) | Signal::Return(_) | Signal::Respond(_) | Signal::Stop) => { + ctx.clear_input_type(input_type); + return other; + } } } - arr.values.reverse(); // keep behavior consistent with original + array.values.reverse(); // keep behavior consistent with original let mut i = 0usize; - arr.values.sort_by(|_, _| { - let comp = *comps.get(i).unwrap_or(&0.0); + array.values.sort_by(|_, _| { + let comp = *out.get(i).unwrap_or(&0.0); i += 1; match comp { n if n < 0.0 => Ordering::Less, @@ -650,7 +796,7 @@ fn sort_reverse( }); Signal::Success(Value { - kind: Some(Kind::ListValue(arr)), + kind: Some(Kind::ListValue(array)), }) } From 9b428cf86eeace3dc22b3e07c0dc9f05211b47db Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:13:04 +0100 Subject: [PATCH 35/58] dependencies: updated tucana and code0-flow --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b2a4c8..a488aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1714,7 +1714,7 @@ dependencies = [ "tokio", "tonic", "tonic-health", - "tucana 0.0.53", + "tucana 0.0.54", ] [[package]] @@ -2035,9 +2035,9 @@ dependencies = [ [[package]] name = "tucana" -version = "0.0.53" +version = "0.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca58639610a8fe299f3615f8774624b872833a53b589245c99d9df15f6fd8f7a" +checksum = "31e81f5598a25b6a8f1618fcb25d1a8b2ddbfa38c8a9dcba16a798efe425a36e" dependencies = [ "pbjson", "pbjson-build", diff --git a/Cargo.toml b/Cargo.toml index 57ecedb..37fc1c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2024" [workspace.dependencies] code0-flow = { version = "0.0.27" } -tucana = { version = "0.0.53" } +tucana = { version = "0.0.54" } tokio = { version = "1.44.1", features = ["rt-multi-thread", "signal"] } log = "0.4.27" futures-lite = "2.6.0" From e4d75f9635deaa840929226d60669a9cb143599e Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:13:39 +0100 Subject: [PATCH 36/58] ref: cargo fmt --- taurus/src/debug/mod.rs | 2 +- taurus/src/debug/trace.rs | 2 +- taurus/src/main.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/taurus/src/debug/mod.rs b/taurus/src/debug/mod.rs index 813a7e6..f30265c 100644 --- a/taurus/src/debug/mod.rs +++ b/taurus/src/debug/mod.rs @@ -1,3 +1,3 @@ +pub mod render; pub mod trace; pub mod tracer; -pub mod render; diff --git a/taurus/src/debug/trace.rs b/taurus/src/debug/trace.rs index 8eeee7a..d4f7c5d 100644 --- a/taurus/src/debug/trace.rs +++ b/taurus/src/debug/trace.rs @@ -34,7 +34,7 @@ pub enum ReferenceKind { parameter_index: i64, }, FlowInput, - Empty + Empty, } #[derive(Debug, Clone)] diff --git a/taurus/src/main.rs b/taurus/src/main.rs index 8c791ec..dcb03e7 100644 --- a/taurus/src/main.rs +++ b/taurus/src/main.rs @@ -8,8 +8,6 @@ use crate::config::Config; use crate::context::executor::Executor; use crate::context::registry::FunctionStore; use crate::context::signal::Signal; -use crate::debug::render::render_trace; -use crate::debug::tracer::Tracer; use crate::implementation::collect; use code0_flow::flow_service::FlowUpdateService; From 10da73e1915cd9e72340cf209deb0313c090a6ca Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 1 Mar 2026 15:14:20 +0100 Subject: [PATCH 37/58] ref: cargo clippy --- taurus/src/context/context.rs | 8 ++++---- taurus/src/context/executor.rs | 7 +++---- taurus/src/debug/tracer.rs | 6 ++++++ taurus/src/implementation/array.rs | 24 ++++++++++++------------ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/taurus/src/context/context.rs b/taurus/src/context/context.rs index d66b765..650e66b 100644 --- a/taurus/src/context/context.rs +++ b/taurus/src/context/context.rs @@ -19,16 +19,16 @@ pub struct Context { impl Context { pub fn new(flow_input: Value) -> Self { - return Context { + Context { results: HashMap::new(), input_types: HashMap::new(), flow_input, current_node_id: 0, - }; + } } pub fn get_current_node_id(&mut self) -> i64 { - return self.current_node_id; + self.current_node_id } pub fn set_current_node_id(&mut self, node_id: i64) { @@ -108,7 +108,7 @@ impl Context { } fn get_flow_input(&mut self) -> ContextResult { - return ContextResult::Success(self.flow_input.clone()); + ContextResult::Success(self.flow_input.clone()) } fn get_input_type(&mut self, input_type: InputType) -> ContextResult { diff --git a/taurus/src/context/executor.rs b/taurus/src/context/executor.rs index 8e8bbde..f8fe93b 100644 --- a/taurus/src/context/executor.rs +++ b/taurus/src/context/executor.rs @@ -193,7 +193,7 @@ impl<'a> Executor<'a> { ArgTrace { index: i, kind: ArgKind::Reference { - reference: reference, + reference, hit: true, }, preview: format!("ctx.get({:?}) -> {:?}", r, v), @@ -247,8 +247,8 @@ impl<'a> Executor<'a> { .copied() .unwrap_or(ParameterNode::Eager); - if matches!(mode, ParameterNode::Eager) { - if let Argument::Thunk(id) = *arg { + if matches!(mode, ParameterNode::Eager) + && let Argument::Thunk(id) = *arg { let (child_sig, child_root) = self.execute_call(id, ctx, tracer); tracer.link_child( @@ -271,7 +271,6 @@ impl<'a> Executor<'a> { } } } - } } Ok(()) diff --git a/taurus/src/debug/tracer.rs b/taurus/src/debug/tracer.rs index ed452df..b0c26f6 100644 --- a/taurus/src/debug/tracer.rs +++ b/taurus/src/debug/tracer.rs @@ -15,6 +15,12 @@ pub struct Tracer { stack: Vec, } +impl Default for Tracer { + fn default() -> Self { + Self::new() + } +} + impl Tracer { pub fn new() -> Self { Self { diff --git a/taurus/src/implementation/array.rs b/taurus/src/implementation/array.rs index 803313d..2c68f81 100644 --- a/taurus/src/implementation/array.rs +++ b/taurus/src/implementation/array.rs @@ -162,7 +162,7 @@ fn filter( let mut out: Vec = Vec::new(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -210,7 +210,7 @@ fn find( }; let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -266,7 +266,7 @@ fn find_last( array.values.reverse(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -321,7 +321,7 @@ fn find_index( }; let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -410,7 +410,7 @@ fn for_each( }; let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -457,7 +457,7 @@ fn map( let mut out: Vec = Vec::with_capacity(array.values.len()); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; @@ -649,13 +649,13 @@ fn sort( let mut out: Vec = Vec::new(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; let input_type_next = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 1, }; @@ -666,7 +666,7 @@ fn sort( ctx.insert_input_type(input_type_next, b.clone()); let sig = run(*transform_node, ctx); signals.push(sig); - return Ordering::Equal; + Ordering::Equal }); for sig in signals { @@ -735,13 +735,13 @@ fn sort_reverse( let mut out: Vec = Vec::new(); let node_id = ctx.get_current_node_id(); let input_type = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 0, }; let input_type_next = InputType { - node_id: node_id, + node_id, parameter_index: 1, input_index: 1, }; @@ -752,7 +752,7 @@ fn sort_reverse( ctx.insert_input_type(input_type_next, b.clone()); let sig = run(*transform_node, ctx); signals.push(sig); - return Ordering::Equal; + Ordering::Equal }); for sig in signals { From 03f7b563c70b42fa2ff1075641a268524a39ddb9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Mar 2026 13:31:37 +0100 Subject: [PATCH 38/58] ref: split taurus codebase into core, taurus (service), tests (test-suite) --- Cargo.lock | 15 +++++++++++++++ Cargo.toml | 5 ++++- crates/core/.gitignore | 1 + crates/core/Cargo.toml | 10 ++++++++++ {taurus => crates/core}/src/context/argument.rs | 2 +- {taurus => crates/core}/src/context/context.rs | 3 ++- {taurus => crates/core}/src/context/executor.rs | 2 +- {taurus => crates/core}/src/context/macros.rs | 4 ++-- {taurus => crates/core}/src/context/mod.rs | 0 {taurus => crates/core}/src/context/registry.rs | 0 {taurus => crates/core}/src/context/signal.rs | 3 ++- {taurus => crates/core}/src/debug/mod.rs | 0 {taurus => crates/core}/src/debug/render.rs | 0 {taurus => crates/core}/src/debug/trace.rs | 1 - {taurus => crates/core}/src/debug/tracer.rs | 0 crates/core/src/lib.rs | 3 +++ .../src => crates/core/src/runtime}/error/mod.rs | 0 .../core/src/runtime/functions}/array.rs | 3 ++- .../core/src/runtime/functions}/boolean.rs | 3 ++- .../core/src/runtime/functions}/control.rs | 2 +- .../core/src/runtime/functions}/http.rs | 14 +++++++------- .../core/src/runtime/functions}/mod.rs | 0 .../core/src/runtime/functions}/number.rs | 3 ++- .../core/src/runtime/functions}/object.rs | 0 .../core/src/runtime/functions}/text.rs | 3 ++- crates/core/src/runtime/mod.rs | 2 ++ {taurus => crates/taurus}/Cargo.toml | 5 +++-- {taurus => crates/taurus}/src/config/mod.rs | 0 {taurus => crates/taurus}/src/main.rs | 14 +++++--------- crates/tests/Cargo.toml | 6 ++++++ crates/tests/src/main.rs | 3 +++ 31 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 crates/core/.gitignore create mode 100644 crates/core/Cargo.toml rename {taurus => crates/core}/src/context/argument.rs (98%) rename {taurus => crates/core}/src/context/context.rs (99%) rename {taurus => crates/core}/src/context/executor.rs (99%) rename {taurus => crates/core}/src/context/macros.rs (94%) rename {taurus => crates/core}/src/context/mod.rs (100%) rename {taurus => crates/core}/src/context/registry.rs (100%) rename {taurus => crates/core}/src/context/signal.rs (96%) rename {taurus => crates/core}/src/debug/mod.rs (100%) rename {taurus => crates/core}/src/debug/render.rs (100%) rename {taurus => crates/core}/src/debug/trace.rs (98%) rename {taurus => crates/core}/src/debug/tracer.rs (100%) create mode 100644 crates/core/src/lib.rs rename {taurus/src => crates/core/src/runtime}/error/mod.rs (100%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/array.rs (99%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/boolean.rs (99%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/control.rs (98%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/http.rs (89%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/mod.rs (100%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/number.rs (99%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/object.rs (100%) rename {taurus/src/implementation => crates/core/src/runtime/functions}/text.rs (99%) create mode 100644 crates/core/src/runtime/mod.rs rename {taurus => crates/taurus}/Cargo.toml (85%) rename {taurus => crates/taurus}/src/config/mod.rs (100%) rename {taurus => crates/taurus}/src/main.rs (96%) create mode 100644 crates/tests/Cargo.toml create mode 100644 crates/tests/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index a488aa3..894d437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core" +version = "0.1.0" +dependencies = [ + "base64", + "log", + "rand 0.10.0", + "tucana", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1706,6 +1716,7 @@ dependencies = [ "async-nats", "base64", "code0-flow", + "core", "env_logger", "futures-lite", "log", @@ -1730,6 +1741,10 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tests" +version = "0.1.0" + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 37fc1c3..5f839b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["taurus"] +members = [ "crates/core", "crates/taurus", "crates/tests" ] resolver = "3" [workspace.package] @@ -19,3 +19,6 @@ async-nats = "0.46.0" prost = "0.14.1" tonic-health = "0.14.1" tonic = "0.14.1" + +[workspace.dependencies.core] +path = "./crates/core" diff --git a/crates/core/.gitignore b/crates/core/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/core/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..48a3a1f --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "core" +version.workspace = true +edition.workspace = true + +[dependencies] +tucana = { workspace = true } +base64 = { workspace = true } +rand = { workspace = true } +log = { workspace = true } diff --git a/taurus/src/context/argument.rs b/crates/core/src/context/argument.rs similarity index 98% rename from taurus/src/context/argument.rs rename to crates/core/src/context/argument.rs index 1bbbe6a..a0a61af 100644 --- a/taurus/src/context/argument.rs +++ b/crates/core/src/context/argument.rs @@ -1,5 +1,5 @@ use crate::context::signal::Signal; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use std::convert::Infallible; use tucana::shared::value::Kind; use tucana::shared::{ListValue, Struct, Value}; diff --git a/taurus/src/context/context.rs b/crates/core/src/context/context.rs similarity index 99% rename from taurus/src/context/context.rs rename to crates/core/src/context/context.rs index 650e66b..f521d37 100644 --- a/taurus/src/context/context.rs +++ b/crates/core/src/context/context.rs @@ -1,7 +1,8 @@ -use crate::error::RuntimeError; use std::collections::HashMap; use tucana::shared::{InputType, ReferenceValue, Value, value::Kind}; +use crate::runtime::error::RuntimeError; + #[derive(Clone)] pub enum ContextResult { Error(RuntimeError), diff --git a/taurus/src/context/executor.rs b/crates/core/src/context/executor.rs similarity index 99% rename from taurus/src/context/executor.rs rename to crates/core/src/context/executor.rs index f8fe93b..04ed0a6 100644 --- a/taurus/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -4,7 +4,7 @@ use crate::context::registry::{FunctionStore, HandlerFunctionEntry}; use crate::context::signal::Signal; use crate::debug::trace::{ArgKind, ArgTrace, EdgeKind, Outcome, ReferenceKind}; use crate::debug::tracer::{ExecutionTracer, Tracer}; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use std::collections::HashMap; use tucana::shared::NodeFunction; diff --git a/taurus/src/context/macros.rs b/crates/core/src/context/macros.rs similarity index 94% rename from taurus/src/context/macros.rs rename to crates/core/src/context/macros.rs index ead9e8a..076db78 100644 --- a/taurus/src/context/macros.rs +++ b/crates/core/src/context/macros.rs @@ -6,7 +6,7 @@ macro_rules! args { let __expected: usize = 0usize $(+ { let _ = ::core::any::type_name::<$ty>(); 1usize })*; if $args_ident.len() != __expected { return $crate::context::signal::Signal::Failure( - $crate::error::RuntimeError::simple( + $crate::runtime::error::RuntimeError::simple( "InvalidArgumentRuntimeError", format!("Expected {__expected} args but received {}", $args_ident.len()), ) @@ -39,7 +39,7 @@ macro_rules! args { macro_rules! no_args { ($args_ident:ident) => { if !$args_ident.is_empty() { - return $crate::context::signal::Signal::Failure($crate::error::RuntimeError::simple( + return $crate::context::signal::Signal::Failure($crate::runtime::error::RuntimeError::simple( "InvalidArgumentRuntimeError", format!("Expected 0 args but received {}", $args_ident.len()), )); diff --git a/taurus/src/context/mod.rs b/crates/core/src/context/mod.rs similarity index 100% rename from taurus/src/context/mod.rs rename to crates/core/src/context/mod.rs diff --git a/taurus/src/context/registry.rs b/crates/core/src/context/registry.rs similarity index 100% rename from taurus/src/context/registry.rs rename to crates/core/src/context/registry.rs diff --git a/taurus/src/context/signal.rs b/crates/core/src/context/signal.rs similarity index 96% rename from taurus/src/context/signal.rs rename to crates/core/src/context/signal.rs index 0c40add..c51aa92 100644 --- a/taurus/src/context/signal.rs +++ b/crates/core/src/context/signal.rs @@ -1,6 +1,7 @@ -use crate::error::RuntimeError; use tucana::shared::Value; +use crate::runtime::error::RuntimeError; + #[derive(Debug)] pub enum Signal { // Will be signaled if a function has been executed successfully diff --git a/taurus/src/debug/mod.rs b/crates/core/src/debug/mod.rs similarity index 100% rename from taurus/src/debug/mod.rs rename to crates/core/src/debug/mod.rs diff --git a/taurus/src/debug/render.rs b/crates/core/src/debug/render.rs similarity index 100% rename from taurus/src/debug/render.rs rename to crates/core/src/debug/render.rs diff --git a/taurus/src/debug/trace.rs b/crates/core/src/debug/trace.rs similarity index 98% rename from taurus/src/debug/trace.rs rename to crates/core/src/debug/trace.rs index d4f7c5d..4a5d34b 100644 --- a/taurus/src/debug/trace.rs +++ b/crates/core/src/debug/trace.rs @@ -1,4 +1,3 @@ -// src/debug_trace/trace.rs use std::time::Instant; #[derive(Debug, Clone)] diff --git a/taurus/src/debug/tracer.rs b/crates/core/src/debug/tracer.rs similarity index 100% rename from taurus/src/debug/tracer.rs rename to crates/core/src/debug/tracer.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..c818ae7 --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,3 @@ +pub mod context; +pub mod debug; +pub mod runtime; diff --git a/taurus/src/error/mod.rs b/crates/core/src/runtime/error/mod.rs similarity index 100% rename from taurus/src/error/mod.rs rename to crates/core/src/runtime/error/mod.rs diff --git a/taurus/src/implementation/array.rs b/crates/core/src/runtime/functions/array.rs similarity index 99% rename from taurus/src/implementation/array.rs rename to crates/core/src/runtime/functions/array.rs index 2c68f81..de149be 100644 --- a/taurus/src/implementation/array.rs +++ b/crates/core/src/runtime/functions/array.rs @@ -5,10 +5,11 @@ use tucana::shared::{ListValue, Value, value::Kind}; use crate::context::argument::Argument; use crate::context::argument::ParameterNode::{Eager, Lazy}; +use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ diff --git a/taurus/src/implementation/boolean.rs b/crates/core/src/runtime/functions/boolean.rs similarity index 99% rename from taurus/src/implementation/boolean.rs rename to crates/core/src/runtime/functions/boolean.rs index b7b5fb4..cb3e4de 100644 --- a/taurus/src/implementation/boolean.rs +++ b/crates/core/src/runtime/functions/boolean.rs @@ -1,8 +1,9 @@ use crate::context::argument::Argument; +use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; use tucana::shared::{Value, value::Kind}; pub fn collect_boolean_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { diff --git a/taurus/src/implementation/control.rs b/crates/core/src/runtime/functions/control.rs similarity index 98% rename from taurus/src/implementation/control.rs rename to crates/core/src/runtime/functions/control.rs index e552a7c..a5d766a 100644 --- a/taurus/src/implementation/control.rs +++ b/crates/core/src/runtime/functions/control.rs @@ -4,7 +4,7 @@ use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use tucana::shared::Value; use tucana::shared::value::Kind; diff --git a/taurus/src/implementation/http.rs b/crates/core/src/runtime/functions/http.rs similarity index 89% rename from taurus/src/implementation/http.rs rename to crates/core/src/runtime/functions/http.rs index d593ab5..4f524d8 100644 --- a/taurus/src/implementation/http.rs +++ b/crates/core/src/runtime/functions/http.rs @@ -3,7 +3,7 @@ use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::error::RuntimeError; +use crate::runtime::error::RuntimeError; use tucana::shared::value::Kind; use tucana::shared::{ListValue, Struct, Value}; @@ -47,10 +47,10 @@ fn respond( )); }; - let Some(Kind::ListValue(_headers_struct)) = &headers_val.kind else { + let Some(Kind::StructValue(_headers_struct)) = &headers_val.kind else { return Signal::Failure(RuntimeError::simple_str( "InvalidArgumentRuntimeError", - "Expected 'headers' to be ListValue", + "Expected 'headers' to be StructValue", )); }; @@ -78,7 +78,7 @@ fn create_request( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => http_method: String, headers: ListValue, http_url: String, payload: Value); + args!(args => http_method: String, headers: Struct, http_url: String, payload: Value); let mut fields = std::collections::HashMap::new(); fields.insert( @@ -98,7 +98,7 @@ fn create_request( fields.insert( "headers".to_string(), Value { - kind: Some(Kind::ListValue(headers.clone())), + kind: Some(Kind::StructValue(headers.clone())), }, ); fields.insert("body".to_string(), payload.clone()); @@ -113,7 +113,7 @@ fn create_response( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => http_status_code: String, headers: ListValue, payload: Value); + args!(args => http_status_code: String, headers: Struct, payload: Value); let mut fields = std::collections::HashMap::new(); let code = match http_status_code.as_str().parse::() { @@ -135,7 +135,7 @@ fn create_response( fields.insert( "headers".to_string(), Value { - kind: Some(Kind::ListValue(headers.clone())), + kind: Some(Kind::StructValue(headers.clone())), }, ); fields.insert("payload".to_string(), payload.clone()); diff --git a/taurus/src/implementation/mod.rs b/crates/core/src/runtime/functions/mod.rs similarity index 100% rename from taurus/src/implementation/mod.rs rename to crates/core/src/runtime/functions/mod.rs diff --git a/taurus/src/implementation/number.rs b/crates/core/src/runtime/functions/number.rs similarity index 99% rename from taurus/src/implementation/number.rs rename to crates/core/src/runtime/functions/number.rs index 19c53b8..cea4024 100644 --- a/taurus/src/implementation/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -6,7 +6,8 @@ use crate::context::argument::Argument; use crate::context::macros::{args, no_args}; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; +use crate::{context::context::Context}; pub fn collect_number_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ diff --git a/taurus/src/implementation/object.rs b/crates/core/src/runtime/functions/object.rs similarity index 100% rename from taurus/src/implementation/object.rs rename to crates/core/src/runtime/functions/object.rs diff --git a/taurus/src/implementation/text.rs b/crates/core/src/runtime/functions/text.rs similarity index 99% rename from taurus/src/implementation/text.rs rename to crates/core/src/runtime/functions/text.rs index f86a6a8..2877ab5 100644 --- a/taurus/src/implementation/text.rs +++ b/crates/core/src/runtime/functions/text.rs @@ -2,7 +2,8 @@ use crate::context::argument::Argument; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; -use crate::{context::context::Context, error::RuntimeError}; +use crate::runtime::error::RuntimeError; +use crate::{context::context::Context}; use base64::Engine; use tucana::shared::{ListValue, Value, value::Kind}; diff --git a/crates/core/src/runtime/mod.rs b/crates/core/src/runtime/mod.rs new file mode 100644 index 0000000..81bfbbf --- /dev/null +++ b/crates/core/src/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod functions; diff --git a/taurus/Cargo.toml b/crates/taurus/Cargo.toml similarity index 85% rename from taurus/Cargo.toml rename to crates/taurus/Cargo.toml index 007f381..d2f7cf6 100644 --- a/taurus/Cargo.toml +++ b/crates/taurus/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "taurus" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true [dependencies] code0-flow = { workspace = true, features = ["flow_service"] } @@ -16,3 +16,4 @@ async-nats = { workspace = true } prost = { workspace = true } tonic-health = { workspace = true } tonic = { workspace = true } +core = { workspace = true } diff --git a/taurus/src/config/mod.rs b/crates/taurus/src/config/mod.rs similarity index 100% rename from taurus/src/config/mod.rs rename to crates/taurus/src/config/mod.rs diff --git a/taurus/src/main.rs b/crates/taurus/src/main.rs similarity index 96% rename from taurus/src/main.rs rename to crates/taurus/src/main.rs index dcb03e7..d3988b2 100644 --- a/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -1,22 +1,18 @@ mod config; -pub mod context; -pub mod debug; -pub mod error; -pub mod implementation; use crate::config::Config; -use crate::context::executor::Executor; -use crate::context::registry::FunctionStore; -use crate::context::signal::Signal; -use crate::implementation::collect; use code0_flow::flow_service::FlowUpdateService; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; -use context::context::Context; use futures_lite::StreamExt; use log::error; use prost::Message; +use core::context::context::Context; +use core::context::executor::Executor; +use core::context::registry::FunctionStore; +use core::context::signal::Signal; +use core::runtime::functions::collect; use std::collections::HashMap; use tokio::signal; use tonic_health::pb::health_server::HealthServer; diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml new file mode 100644 index 0000000..9e51e89 --- /dev/null +++ b/crates/tests/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tests" +version.workspace = true +edition.workspace = true + +[dependencies] diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/crates/tests/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 96a2f3bf735ee6ac5760723d2859fe169847ef64 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Mar 2026 13:57:42 +0100 Subject: [PATCH 39/58] fix: random_range if numbers are in wrong order it will return runtime error instead of panicing --- crates/core/src/runtime/functions/number.rs | 33 +++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/crates/core/src/runtime/functions/number.rs b/crates/core/src/runtime/functions/number.rs index cea4024..7b8d00f 100644 --- a/crates/core/src/runtime/functions/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -3,11 +3,11 @@ use std::f64; use tucana::shared::{Value, value::Kind}; use crate::context::argument::Argument; +use crate::context::context::Context; use crate::context::macros::{args, no_args}; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; -use crate::{context::context::Context}; pub fn collect_number_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ @@ -382,13 +382,17 @@ fn random( ) -> Signal { args!(args => min: f64, max: f64); - let min_i = min.ceil() as i64; - let max_i = max.floor() as i64; + if min > max { + return Signal::Failure(RuntimeError::simple_str( + "InvalidRange", + "First number can't be bigger then second when creating a range for std::math::random", + )); + } - let value = rand::random_range(min_i..=max_i) as i64; + let value = rand::random_range(min..=max); Signal::Success(Value { - kind: Some(Kind::NumberValue(value as f64)), + kind: Some(Kind::NumberValue(value)), }) } @@ -789,6 +793,25 @@ mod tests { assert!(r >= 1.0 && r < 10.0); } + + #[test] + fn test_random_range_numbers_equal() { + let mut ctx = Context::default(); + + let mut run = dummy_run; + let r = expect_num(random(&[a_num(1.0), a_num(1.0)], &mut ctx, &mut run)); + assert!(r == 1.0); + } + + #[test] + fn test_random_range_fist_bigger_then_second() { + let mut ctx = Context::default(); + + let mut run = dummy_run; + let res = random(&[a_num(10.0), a_num(1.0)], &mut ctx, &mut run); + assert!(matches!(res, Signal::Failure(_))); + } + #[test] fn test_trig_and_hyperbolic() { let mut ctx = Context::default(); From a390f51091497476755612cd78e0b4be83b29663 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 13 Mar 2026 11:57:27 +0100 Subject: [PATCH 40/58] feat: added wip flow test execution suite --- crates/core/src/context/registry.rs | 5 +- crates/taurus/src/main.rs | 3 +- crates/tests/Cargo.toml | 6 ++ crates/tests/src/main.rs | 142 +++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 4 deletions(-) diff --git a/crates/core/src/context/registry.rs b/crates/core/src/context/registry.rs index d402e44..633eac2 100644 --- a/crates/core/src/context/registry.rs +++ b/crates/core/src/context/registry.rs @@ -1,6 +1,7 @@ use crate::context::argument::{Argument, ParameterNode}; use crate::context::context::Context; use crate::context::signal::Signal; +use crate::runtime::functions::collect; use std::collections::HashMap; /// HandlerFm @@ -25,7 +26,9 @@ pub struct FunctionStore { impl Default for FunctionStore { fn default() -> Self { - Self::new() + let mut store = Self::new(); + store.populate(collect()); + store } } diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index d3988b2..745130d 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -40,8 +40,7 @@ async fn main() { load_env_file(); let config = Config::new(); - let mut store = FunctionStore::new(); - store.populate(collect()); + let store = FunctionStore::default(); let client = match async_nats::connect(config.nats_url.clone()).await { Ok(client) => { diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 9e51e89..8f819a3 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -4,3 +4,9 @@ version.workspace = true edition.workspace = true [dependencies] +tucana = { workspace = true } +core = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index e7a11a9..67da994 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -1,3 +1,143 @@ +use core::context::{context::Context, executor::Executor, registry::FunctionStore}; +use log::{error, info}; +use serde::Deserialize; +use std::collections::HashMap; + +use tucana::shared::NodeFunction; + +#[derive(Clone, Deserialize)] +struct Input { + input: Option, + expected_result: serde_json::Value, +} + +#[derive(Clone, Deserialize)] +struct Case { + name: String, + description: String, + inputs: Vec, + flow: TestableFlow, +} + +#[derive(Clone, Deserialize)] +struct TestableFlow { + pub starting_node_id: i64, + pub node_functions: Vec, +} + +#[derive(Clone, Deserialize)] +struct TestCases { + cases: Vec, +} + +fn print_success(case: &Case) { + info!("test {} ... ok", case.name); +} + +fn print_failure(case: &Case, input: &Input) { + error!("test {} ... FAILED", case.name); + error!(" input: {:?}", input.input); + error!(" expected: {:?}", input.expected_result); + error!(" message: {}", case.description); +} + +fn get_test_cases(path: &str) -> TestCases { + let mut items = Vec::new(); + let dir = match std::fs::read_dir(path) { + Ok(d) => d, + Err(err) => { + panic!("Cannot open path: {:?}", err) + } + }; + + for entry in dir { + let entry = match entry { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read entry: {:?}", err); + continue; + } + }; + let path = entry.path(); + + let content = match std::fs::read_to_string(&path) { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read file ({:?}): {:?}", path, err); + continue; + } + }; + items.push(match serde_json::from_str(&content) { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read json ({:?}): {:?}", path, err); + continue; + } + }); + } + + TestCases { cases: items } +} + +impl TestCases { + pub fn from_path(path: &str) -> Self { + get_test_cases(path) + } + + pub fn run_tests(&self) { + for case in self.cases.clone() { + match case.run() { + CaseResult::Success => print_success(&case), + CaseResult::Failure(input) => print_failure(&case, &input), + } + } + } +} + +enum CaseResult { + Success, + Failure(Input), +} + +impl Case { + fn run(&self) -> CaseResult { + let store = FunctionStore::default(); + + let node_functions: HashMap = self + .clone() + .flow + .node_functions + .into_iter() + .map(|node| (node.database_id, node)) + .collect(); + + for input in self.inputs.clone() { + let mut context = match input.clone().input { + Some(inp) => Context::new(inp), + None => Context::default(), + }; + + let res = Executor::new(&store, node_functions.clone()) + .execute(self.flow.starting_node_id, &mut context); + + match res { + core::context::signal::Signal::Failure(_) => { + return CaseResult::Failure(input); + } + _ => continue, + } + } + + CaseResult::Success + } +} + fn main() { - println!("Hello, world!"); + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Info) + .init(); + + + let cases = TestCases::from_path("./crates/tests/flows/"); + cases.run_tests(); } From 0fcd17fe8befa603d8cd5c89cbe3a7126997f3fd Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 13 Mar 2026 11:57:35 +0100 Subject: [PATCH 41/58] dependencies: added serde and serde_json --- Cargo.lock | 8 ++++++++ Cargo.toml | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 894d437..8a83134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1744,6 +1744,14 @@ dependencies = [ [[package]] name = "tests" version = "0.1.0" +dependencies = [ + "core", + "env_logger", + "log", + "serde", + "serde_json", + "tucana", +] [[package]] name = "thiserror" diff --git a/Cargo.toml b/Cargo.toml index 5f839b9..fee99c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ async-nats = "0.46.0" prost = "0.14.1" tonic-health = "0.14.1" tonic = "0.14.1" +serde_json = "1.0.149" +serde = "1.0.228" [workspace.dependencies.core] path = "./crates/core" From 2f15a41ad143c9a9d170084c8d9a3f11392a8fa4 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 13 Mar 2026 11:57:42 +0100 Subject: [PATCH 42/58] feat: added first test flow to suite --- crates/tests/flows/01_return_object.json | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 crates/tests/flows/01_return_object.json diff --git a/crates/tests/flows/01_return_object.json b/crates/tests/flows/01_return_object.json new file mode 100644 index 0000000..ade5ebd --- /dev/null +++ b/crates/tests/flows/01_return_object.json @@ -0,0 +1,78 @@ +{ + "name": "01_return_object", + "description": "This flow expects a simple http response object as the return value", + "inputs": [ + { + "input": null, + "expected_result": { + "http_status_code": 200, + "headers": [ + { + "Header": "X" + } + ], + "body": "Hello World" + } + } + ], + "flow": { + "starting_node_id": "1", + "node_functions": [ + { + "databaseId": "2", + "runtimeFunctionId": "rest::control::respond", + "parameters": [ + { + "databaseId": "4", + "runtimeParameterId": "http_response", + "value": { + "referenceValue": { + "nodeId": "1" + } + } + } + ] + }, + { + "databaseId": "1", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "1", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "2", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Header": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "3", + "runtimeParameterId": "payload", + "value": { + "literalValue": { + "stringValue": "Hello World" + } + } + } + ], + "nextNodeId": "2" + } + ] + } +} From 7f035c4c1db3498271860a493194337987aab2d8 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:09:25 +0100 Subject: [PATCH 43/58] feat: made tracer print optional --- crates/core/src/context/executor.rs | 50 +++++++++++++++-------------- crates/taurus/src/main.rs | 2 +- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 04ed0a6..bc180ff 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -20,15 +20,16 @@ impl<'a> Executor<'a> { } /// This is now the ONLY execution entry point. - pub fn execute(&self, start_node_id: i64, ctx: &mut Context) -> Signal { + pub fn execute(&self, start_node_id: i64, ctx: &mut Context, with_trace: bool) -> Signal { let mut tracer = Tracer::new(); let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); - if let Some(run) = tracer.take_run() { - println!("{}", crate::debug::render::render_trace(&run)); + if with_trace { + if let Some(run) = tracer.take_run() { + println!("{}", crate::debug::render::render_trace(&run)); + } } - signal } @@ -248,29 +249,30 @@ impl<'a> Executor<'a> { .unwrap_or(ParameterNode::Eager); if matches!(mode, ParameterNode::Eager) - && let Argument::Thunk(id) = *arg { - let (child_sig, child_root) = self.execute_call(id, ctx, tracer); - - tracer.link_child( - parent_frame, - child_root, - EdgeKind::EagerCall { arg_index: i }, - ); + && let Argument::Thunk(id) = *arg + { + let (child_sig, child_root) = self.execute_call(id, ctx, tracer); + + tracer.link_child( + parent_frame, + child_root, + EdgeKind::EagerCall { arg_index: i }, + ); - match child_sig { - Signal::Success(v) => { - *arg = Argument::Eval(v); - } - s => { - return Err(( - s, - Outcome::Failure { - error_preview: "Eager child failed".into(), - }, - )); - } + match child_sig { + Signal::Success(v) => { + *arg = Argument::Eval(v); + } + s => { + return Err(( + s, + Outcome::Failure { + error_preview: "Eager child failed".into(), + }, + )); } } + } } Ok(()) diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 745130d..9d285ba 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -28,7 +28,7 @@ fn handle_message(flow: ExecutionFlow, store: &FunctionStore) -> Signal { .map(|node| (node.database_id, node)) .collect(); - Executor::new(store, node_functions).execute(flow.starting_node_id, &mut context) + Executor::new(store, node_functions).execute(flow.starting_node_id, &mut context, true) } #[tokio::main] From 08c24599b8d851c6b8c8ffaa4d1b1a5d16d34810 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:09:34 +0100 Subject: [PATCH 44/58] feat: exported RuntimeErrors name and message --- crates/core/src/runtime/error/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/src/runtime/error/mod.rs b/crates/core/src/runtime/error/mod.rs index 4dbf6e2..14895ba 100644 --- a/crates/core/src/runtime/error/mod.rs +++ b/crates/core/src/runtime/error/mod.rs @@ -8,8 +8,8 @@ use tucana::shared::{Struct, Value}; #[derive(Debug, Default, Clone)] pub struct RuntimeError { - name: String, - message: String, + pub name: String, + pub message: String, suggestion: Option, } From 9ef8f8378ef96bf21986c6b3364c28be4163c87c Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:09:46 +0100 Subject: [PATCH 45/58] feat: first correct flow test cases --- crates/tests/flows/01_return_object.json | 12 ++- crates/tests/flows/02_return_flow_input.json | 88 ++++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 crates/tests/flows/02_return_flow_input.json diff --git a/crates/tests/flows/01_return_object.json b/crates/tests/flows/01_return_object.json index ade5ebd..34c28c8 100644 --- a/crates/tests/flows/01_return_object.json +++ b/crates/tests/flows/01_return_object.json @@ -5,13 +5,11 @@ { "input": null, "expected_result": { - "http_status_code": 200, - "headers": [ - { - "Header": "X" - } - ], - "body": "Hello World" + "status_code": 200, + "headers": { + "Header": "X" + }, + "payload": "Hello World" } } ], diff --git a/crates/tests/flows/02_return_flow_input.json b/crates/tests/flows/02_return_flow_input.json new file mode 100644 index 0000000..ef8402d --- /dev/null +++ b/crates/tests/flows/02_return_flow_input.json @@ -0,0 +1,88 @@ +{ + "name": "02_return_object", + "description": "This flow expects the same output value as the input", + "inputs": [ + { + "input": null, + "expected_result": { + "status_code": 200, + "headers": { + "Authentication": "X" + }, + "payload": null + } + }, + { + "input": { + "name": "Joe Doe", + "age": 51, + "pets": [ + "dog", + "cat", + "bird" + ] + }, + "expected_result": { + "status_code": 200, + "headers": { + "Authentication": "X" + }, + "payload": { + "name": "Joe Doe", + "age": 51, + "pets": [ + "dog", + "cat", + "bird" + ] + } + } + } + ], + "flow": { + "flowId": "2", + "projectId": "1", + "startingNodeId": "3", + "nodeFunctions": [ + { + "databaseId": "3", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "5", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "6", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Authentication": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "7", + "runtimeParameterId": "payload", + "value": { + "referenceValue": { + "flowInput": {} + } + } + } + ] + } + ] + } +} From 3c66e32af4c94aeba928fe1d77ed4cadeae3ec95 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:01 +0100 Subject: [PATCH 46/58] feat: added readme --- crates/tests/README.md | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 crates/tests/README.md diff --git a/crates/tests/README.md b/crates/tests/README.md new file mode 100644 index 0000000..91f56b2 --- /dev/null +++ b/crates/tests/README.md @@ -0,0 +1,107 @@ +# Test Execution Suite + +This is the service for running a full execution for example flows. + +## How to run the execution suite? + +```console +cargo run --package tests +``` + +## How to add a flow to the suite? + +```json +{ + "name": "Descriptive snake case name", + "description": "Description on what logic should be tested", + "inputs": [ + { + "input": "Input Value/Flow Input (JSON)", + "expected_result": "Expected (JSON) Result of the flow", + } + ], + "flow": "The flow object (Exported from Aquila), parsed from Protobuf Message struct so it should be protobuf value not json value" +} + +``` + +An example + +```json +{ + "name": "01_return_object", + "description": "This flow expects a simple http response object as the return value", + "inputs": [ + { + "input": null, + "expected_result": { + "status_code": 200, + "headers": { + "Header": "X" + }, + "payload": "Hello World" + } + } + ], + "flow": { + "starting_node_id": "1", + "node_functions": [ + { + "databaseId": "2", + "runtimeFunctionId": "rest::control::respond", + "parameters": [ + { + "databaseId": "4", + "runtimeParameterId": "http_response", + "value": { + "referenceValue": { + "nodeId": "1" + } + } + } + ] + }, + { + "databaseId": "1", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "1", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "2", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Header": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "3", + "runtimeParameterId": "payload", + "value": { + "literalValue": { + "stringValue": "Hello World" + } + } + } + ], + "nextNodeId": "2" + } + ] + } +} +``` From eb815b58364d581ecf5fe99be90e68754d88efa1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:15 +0100 Subject: [PATCH 47/58] feat: added comparison of results to test suite --- crates/tests/src/main.rs | 62 ++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 67da994..87ea2dd 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -1,13 +1,17 @@ use core::context::{context::Context, executor::Executor, registry::FunctionStore}; use log::{error, info}; use serde::Deserialize; +use serde_json::json; use std::collections::HashMap; -use tucana::shared::NodeFunction; +use tucana::shared::{ + NodeFunction, ValidationFlow, + helper::value::{from_json_value, to_json_value}, +}; #[derive(Clone, Deserialize)] struct Input { - input: Option, + input: Option, expected_result: serde_json::Value, } @@ -16,13 +20,7 @@ struct Case { name: String, description: String, inputs: Vec, - flow: TestableFlow, -} - -#[derive(Clone, Deserialize)] -struct TestableFlow { - pub starting_node_id: i64, - pub node_functions: Vec, + flow: ValidationFlow, } #[derive(Clone, Deserialize)] @@ -34,10 +32,11 @@ fn print_success(case: &Case) { info!("test {} ... ok", case.name); } -fn print_failure(case: &Case, input: &Input) { +fn print_failure(case: &Case, input: &Input, result: serde_json::Value) { error!("test {} ... FAILED", case.name); error!(" input: {:?}", input.input); error!(" expected: {:?}", input.expected_result); + error!(" real_value: {:?}", result); error!(" message: {}", case.description); } @@ -88,7 +87,7 @@ impl TestCases { for case in self.cases.clone() { match case.run() { CaseResult::Success => print_success(&case), - CaseResult::Failure(input) => print_failure(&case, &input), + CaseResult::Failure(input, result) => print_failure(&case, &input, result), } } } @@ -96,7 +95,7 @@ impl TestCases { enum CaseResult { Success, - Failure(Input), + Failure(Input, serde_json::Value), } impl Case { @@ -113,18 +112,46 @@ impl Case { for input in self.inputs.clone() { let mut context = match input.clone().input { - Some(inp) => Context::new(inp), + Some(inp) => Context::new(from_json_value(inp)), None => Context::default(), }; let res = Executor::new(&store, node_functions.clone()) - .execute(self.flow.starting_node_id, &mut context); + .execute(self.flow.starting_node_id, &mut context, false); match res { - core::context::signal::Signal::Failure(_) => { - return CaseResult::Failure(input); + core::context::signal::Signal::Failure(err) => { + let json = json!({ + "name": err.name, + "message": err.message, + }); + return CaseResult::Failure(input, json); + } + core::context::signal::Signal::Success(value) => { + let json = to_json_value(value); + if json == input.clone().expected_result { + return CaseResult::Success; + } else { + return CaseResult::Failure(input, json); + } } - _ => continue, + core::context::signal::Signal::Return(value) => { + let json = to_json_value(value); + if json == input.clone().expected_result { + return CaseResult::Success; + } else { + return CaseResult::Failure(input, json); + } + } + core::context::signal::Signal::Respond(value) => { + let json = to_json_value(value); + if json == input.clone().expected_result { + return CaseResult::Success; + } else { + return CaseResult::Failure(input, json); + } + } + core::context::signal::Signal::Stop => continue, } } @@ -137,7 +164,6 @@ fn main() { .filter_level(log::LevelFilter::Info) .init(); - let cases = TestCases::from_path("./crates/tests/flows/"); cases.run_tests(); } From 92bc170f8672f2c7b1a6dfb761e7535127285dd1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:38 +0100 Subject: [PATCH 48/58] ref: cargo fix --- crates/core/src/runtime/functions/http.rs | 2 +- crates/taurus/src/main.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/core/src/runtime/functions/http.rs b/crates/core/src/runtime/functions/http.rs index 4f524d8..9fe305f 100644 --- a/crates/core/src/runtime/functions/http.rs +++ b/crates/core/src/runtime/functions/http.rs @@ -5,7 +5,7 @@ use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntr use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; use tucana::shared::value::Kind; -use tucana::shared::{ListValue, Struct, Value}; +use tucana::shared::{Struct, Value}; pub fn collect_http_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 9d285ba..29fa7fd 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -12,7 +12,6 @@ use core::context::context::Context; use core::context::executor::Executor; use core::context::registry::FunctionStore; use core::context::signal::Signal; -use core::runtime::functions::collect; use std::collections::HashMap; use tokio::signal; use tonic_health::pb::health_server::HealthServer; From 43d27eff00019021abe215dfb0621915e85232e9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:10:56 +0100 Subject: [PATCH 49/58] ref: cargo clippy --- crates/core/src/context/executor.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index bc180ff..17a27b4 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -25,11 +25,10 @@ impl<'a> Executor<'a> { let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); - if with_trace { - if let Some(run) = tracer.take_run() { + if with_trace + && let Some(run) = tracer.take_run() { println!("{}", crate::debug::render::render_trace(&run)); } - } signal } From 2264a440f9f398cc5f5ec0c9791f950b9291b9e1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Mar 2026 20:11:15 +0100 Subject: [PATCH 50/58] ref: cargo fmt --- crates/core/src/context/executor.rs | 7 +++---- crates/core/src/context/macros.rs | 10 ++++++---- crates/core/src/runtime/functions/number.rs | 1 - crates/core/src/runtime/functions/text.rs | 2 +- crates/taurus/src/main.rs | 6 +++--- crates/tests/src/main.rs | 7 +++++-- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 17a27b4..306d158 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -25,10 +25,9 @@ impl<'a> Executor<'a> { let (signal, _root_frame) = self.execute_call(start_node_id, ctx, &mut tracer); - if with_trace - && let Some(run) = tracer.take_run() { - println!("{}", crate::debug::render::render_trace(&run)); - } + if with_trace && let Some(run) = tracer.take_run() { + println!("{}", crate::debug::render::render_trace(&run)); + } signal } diff --git a/crates/core/src/context/macros.rs b/crates/core/src/context/macros.rs index 076db78..ed4644f 100644 --- a/crates/core/src/context/macros.rs +++ b/crates/core/src/context/macros.rs @@ -39,10 +39,12 @@ macro_rules! args { macro_rules! no_args { ($args_ident:ident) => { if !$args_ident.is_empty() { - return $crate::context::signal::Signal::Failure($crate::runtime::error::RuntimeError::simple( - "InvalidArgumentRuntimeError", - format!("Expected 0 args but received {}", $args_ident.len()), - )); + return $crate::context::signal::Signal::Failure( + $crate::runtime::error::RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!("Expected 0 args but received {}", $args_ident.len()), + ), + ); } }; } diff --git a/crates/core/src/runtime/functions/number.rs b/crates/core/src/runtime/functions/number.rs index 7b8d00f..c60e023 100644 --- a/crates/core/src/runtime/functions/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -793,7 +793,6 @@ mod tests { assert!(r >= 1.0 && r < 10.0); } - #[test] fn test_random_range_numbers_equal() { let mut ctx = Context::default(); diff --git a/crates/core/src/runtime/functions/text.rs b/crates/core/src/runtime/functions/text.rs index 2877ab5..37b70f2 100644 --- a/crates/core/src/runtime/functions/text.rs +++ b/crates/core/src/runtime/functions/text.rs @@ -1,9 +1,9 @@ use crate::context::argument::Argument; +use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; -use crate::{context::context::Context}; use base64::Engine; use tucana::shared::{ListValue, Value, value::Kind}; diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 29fa7fd..67827c3 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -5,13 +5,13 @@ use code0_flow::flow_service::FlowUpdateService; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; -use futures_lite::StreamExt; -use log::error; -use prost::Message; use core::context::context::Context; use core::context::executor::Executor; use core::context::registry::FunctionStore; use core::context::signal::Signal; +use futures_lite::StreamExt; +use log::error; +use prost::Message; use std::collections::HashMap; use tokio::signal; use tonic_health::pb::health_server::HealthServer; diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 87ea2dd..9d8d430 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -116,8 +116,11 @@ impl Case { None => Context::default(), }; - let res = Executor::new(&store, node_functions.clone()) - .execute(self.flow.starting_node_id, &mut context, false); + let res = Executor::new(&store, node_functions.clone()).execute( + self.flow.starting_node_id, + &mut context, + false, + ); match res { core::context::signal::Signal::Failure(err) => { From 7b07b0cd0a7a8b8439ec240657736efe77509951 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Mar 2026 15:12:29 +0100 Subject: [PATCH 51/58] feat: added more detail options for tracer --- crates/core/src/context/context.rs | 10 ++ crates/core/src/context/executor.rs | 76 ++++++++++-- crates/core/src/debug/render.rs | 122 +++++++++++++------ crates/core/src/debug/trace.rs | 2 + crates/core/src/debug/tracer.rs | 39 ++++++ crates/core/src/runtime/functions/array.rs | 87 ++++++++++++- crates/core/src/runtime/functions/control.rs | 3 + 7 files changed, 291 insertions(+), 48 deletions(-) diff --git a/crates/core/src/context/context.rs b/crates/core/src/context/context.rs index f521d37..0195c17 100644 --- a/crates/core/src/context/context.rs +++ b/crates/core/src/context/context.rs @@ -16,6 +16,7 @@ pub struct Context { input_types: HashMap, flow_input: Value, current_node_id: i64, + runtime_trace_labels: Vec, } impl Context { @@ -25,6 +26,7 @@ impl Context { input_types: HashMap::new(), flow_input, current_node_id: 0, + runtime_trace_labels: Vec::new(), } } @@ -138,4 +140,12 @@ impl Context { pub fn insert_error(&mut self, id: i64, runtime_error: RuntimeError) { self.results.insert(id, ContextResult::Error(runtime_error)); } + + pub fn push_runtime_trace_label(&mut self, label: String) { + self.runtime_trace_labels.push(label); + } + + pub fn pop_runtime_trace_label(&mut self) -> Option { + self.runtime_trace_labels.pop() + } } diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 306d158..34248cc 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -7,7 +7,9 @@ use crate::debug::tracer::{ExecutionTracer, Tracer}; use crate::runtime::error::RuntimeError; use std::collections::HashMap; -use tucana::shared::NodeFunction; +use tucana::shared::{NodeFunction, Value}; +use tucana::shared::reference_value::Target; +use tucana::shared::value::Kind; pub struct Executor<'a> { functions: &'a FunctionStore, @@ -127,7 +129,7 @@ impl<'a> Executor<'a> { } // ---- Invoke handler - let result = self.invoke_handler(entry, &args, ctx, tracer); + let result = self.invoke_handler(entry, &args, ctx, tracer, frame_id); // ---- Commit result let final_signal = self.commit_result(&node, result, ctx, tracer, frame_id); @@ -160,7 +162,7 @@ impl<'a> Executor<'a> { ArgTrace { index: i, kind: ArgKind::Literal, - preview: format!("{:?}", v), + preview: preview_value(v), }, ); args.push(Argument::Eval(v.clone())); @@ -195,7 +197,11 @@ impl<'a> Executor<'a> { reference, hit: true, }, - preview: format!("ctx.get({:?}) -> {:?}", r, v), + preview: format!( + "ctx.get({}) -> {}", + preview_reference(&r), + preview_value(&v) + ), }, ); args.push(Argument::Eval(v)); @@ -249,6 +255,7 @@ impl<'a> Executor<'a> { if matches!(mode, ParameterNode::Eager) && let Argument::Thunk(id) = *arg { + tracer.mark_thunk(parent_frame, i, true, true); let (child_sig, child_root) = self.execute_call(id, ctx, tracer); tracer.link_child( @@ -282,9 +289,13 @@ impl<'a> Executor<'a> { args: &[Argument], ctx: &mut Context, tracer: &mut dyn ExecutionTracer, + frame_id: u64, ) -> Signal { let mut run = |node_id: i64, ctx: &mut Context| { - let (sig, _) = self.execute_call(node_id, ctx, tracer); + tracer.mark_thunk_executed_by_node(frame_id, node_id); + let label = ctx.pop_runtime_trace_label(); + let (sig, child_root) = self.execute_call(node_id, ctx, tracer); + tracer.link_child(frame_id, child_root, EdgeKind::RuntimeCall { label }); sig }; @@ -306,7 +317,7 @@ impl<'a> Executor<'a> { tracer.exit_node( frame_id, Outcome::Success { - value_preview: format!("{:#?}", v), + value_preview: preview_value(&v), }, ); @@ -330,7 +341,7 @@ impl<'a> Executor<'a> { tracer.exit_node( frame_id, Outcome::Return { - value_preview: format!("{:#?}", v), + value_preview: preview_value(&v), }, ); Signal::Return(v) @@ -340,7 +351,7 @@ impl<'a> Executor<'a> { tracer.exit_node( frame_id, Outcome::Respond { - value_preview: format!("{:#?}", v), + value_preview: preview_value(&v), }, ); Signal::Respond(v) @@ -353,3 +364,52 @@ impl<'a> Executor<'a> { } } } + +fn preview_value(value: &Value) -> String { + format_value_json(value) +} + +fn format_value_json(value: &Value) -> String { + match value.kind.as_ref() { + Some(Kind::NumberValue(v)) => v.to_string(), + Some(Kind::BoolValue(v)) => v.to_string(), + Some(Kind::StringValue(v)) => format!("{:?}", v), + Some(Kind::NullValue(_)) | None => "null".to_string(), + Some(Kind::ListValue(list)) => { + let mut parts = Vec::new(); + for item in list.values.iter() { + parts.push(format_value_json(item)); + } + format!("[{}]", parts.join(", ")) + } + Some(Kind::StructValue(struct_value)) => { + let mut keys: Vec<_> = struct_value.fields.keys().collect(); + keys.sort(); + let mut parts = Vec::new(); + for key in keys.iter() { + if let Some(v) = struct_value.fields.get(*key) { + parts.push(format!("{:?}: {}", key, format_value_json(v))); + } + } + format!("{{{}}}", parts.join(", ")) + } + } +} + +fn preview_reference(r: &tucana::shared::ReferenceValue) -> String { + let target = match &r.target { + Some(Target::FlowInput(_)) => "flow_input".to_string(), + Some(Target::NodeId(id)) => format!("node({})", id), + Some(Target::InputType(input_type)) => format!( + "input(node={},param={},input={})", + input_type.node_id, input_type.parameter_index, input_type.input_index + ), + None => "empty".to_string(), + }; + + if r.paths.is_empty() { + target + } else { + format!("{}+paths({})", target, r.paths.len()) + } +} diff --git a/crates/core/src/debug/render.rs b/crates/core/src/debug/render.rs index afd770c..40b44c3 100644 --- a/crates/core/src/debug/render.rs +++ b/crates/core/src/debug/render.rs @@ -1,15 +1,8 @@ use crate::debug::trace::{ArgKind, EdgeKind, ExecFrame, Outcome, TraceRun}; use std::collections::HashMap; -fn short(s: &str, max: usize) -> String { - if s.len() <= max { - return s.to_string(); - } - format!("{}…", &s[..max]) -} - -fn ms(f: &ExecFrame) -> Option { - f.end.map(|e| e.duration_since(f.start).as_millis()) +fn micros(f: &ExecFrame) -> Option { + f.end.map(|e| e.duration_since(f.start).as_micros()) } pub fn render_trace(run: &TraceRun) -> String { @@ -19,7 +12,13 @@ pub fn render_trace(run: &TraceRun) -> String { } let mut out = String::new(); + if let Some(total_us) = total_duration_us(&run.frames) { + out.push_str(&format!("Total: {}µs\n", total_us)); + } render_frame(run.root, &by_id, "", true, &mut out); + if let Some(total_us) = total_duration_us(&run.frames) { + out.push_str(&format!("Summary: total_time={}µs\n", total_us)); + } out } @@ -40,7 +39,7 @@ fn render_frame( "├─ " }; - let dur = ms(f).map(|m| format!(" ({}ms)", m)).unwrap_or_default(); + let dur = micros(f).map(|u| format!(" ({}µs)", u)).unwrap_or_default(); out.push_str(&format!( "{prefix}{branch}#{fid} node={nid} fn={name}{dur}\n", @@ -55,7 +54,7 @@ fn render_frame( // args for a in &f.args { let pfx = if prefix.is_empty() { - "" + " " } else if is_last { " " } else { @@ -85,7 +84,7 @@ fn render_frame( "{prefix}{pfx} arg[{}] {:<12} {}\n", a.index, kind, - short(&a.preview, 1600), + a.preview, prefix = prefix, pfx = pfx )); @@ -93,16 +92,24 @@ fn render_frame( // outcome let outcome_line = match &f.outcome { - Some(Outcome::Success { value_preview }) => format!("✅ {}", short(value_preview, 1800)), - Some(Outcome::Failure { error_preview }) => format!("❌ {}", short(error_preview, 1800)), - Some(Outcome::Return { value_preview }) => format!("↩️ {}", short(value_preview, 1800)), - Some(Outcome::Respond { value_preview }) => format!("💬 {}", short(value_preview, 1800)), - Some(Outcome::Stop) => "🛑 Stop".to_string(), + Some(Outcome::Success { value_preview }) => { + colorize("SUCCESS", AnsiColor::Green, value_preview) + } + Some(Outcome::Failure { error_preview }) => { + colorize("FAILURE", AnsiColor::Red, error_preview) + } + Some(Outcome::Return { value_preview }) => { + colorize("RETURN", AnsiColor::Cyan, value_preview) + } + Some(Outcome::Respond { value_preview }) => { + colorize("RESPOND", AnsiColor::Blue, value_preview) + } + Some(Outcome::Stop) => colorize("STOP", AnsiColor::Yellow, "Stop"), None => "…".to_string(), }; let pfx = if prefix.is_empty() { - "" + " " } else if is_last { " " } else { @@ -121,12 +128,7 @@ fn render_frame( return; } - let new_prefix = if prefix.is_empty() { - String::new() - } else { - format!("{}{}", prefix, if is_last { " " } else { "│ " }) - }; - + let mut runtime_idx = 0usize; for (idx, (edge, child_id)) in kids.iter().enumerate() { let last = idx + 1 == kids.len(); @@ -134,23 +136,73 @@ fn render_frame( let edge_label = match edge { EdgeKind::Next => "→ NEXT".to_string(), EdgeKind::EagerCall { arg_index } => format!("↳ eager(arg#{})", arg_index), + EdgeKind::RuntimeCall { label } => { + runtime_idx += 1; + match label { + Some(l) => format!("↳ runtime(call #{}) {}", runtime_idx, l), + None => format!("↳ runtime(call #{})", runtime_idx), + } + } }; - let edge_pfx = if prefix.is_empty() { - "" - } else if is_last { - " " - } else { - "│ " - }; - + let edge_branch = if last { "└─ " } else { "├─ " }; out.push_str(&format!( - "{prefix}{edge_pfx} {edge}\n", + "{prefix}{branch}{edge}\n", prefix = prefix, - edge_pfx = edge_pfx, + branch = edge_branch, edge = edge_label )); - render_frame(*child_id, by_id, &new_prefix, last, out); + let edge_child_prefix = format!("{}{}", prefix, if last { " " } else { "│ " }); + render_frame(*child_id, by_id, &edge_child_prefix, true, out); + } +} + +enum AnsiColor { + Red, + Green, + Yellow, + Blue, + Cyan, +} + +fn colorize(label: &str, color: AnsiColor, payload: &str) -> String { + let code = match color { + AnsiColor::Red => 31, + AnsiColor::Green => 32, + AnsiColor::Yellow => 33, + AnsiColor::Blue => 34, + AnsiColor::Cyan => 36, + }; + format!("\x1b[{code}m[{label}]\x1b[0m {payload}") +} + +fn total_duration_us(frames: &[ExecFrame]) -> Option { + let mut start: Option = None; + let mut end: Option = None; + + for f in frames { + if let Some(s) = start { + if f.start < s { + start = Some(f.start); + } + } else { + start = Some(f.start); + } + + if let Some(f_end) = f.end { + if let Some(e) = end { + if f_end > e { + end = Some(f_end); + } + } else { + end = Some(f_end); + } + } + } + + match (start, end) { + (Some(s), Some(e)) => Some(e.duration_since(s).as_micros()), + _ => None, } } diff --git a/crates/core/src/debug/trace.rs b/crates/core/src/debug/trace.rs index 4a5d34b..e7b2bbb 100644 --- a/crates/core/src/debug/trace.rs +++ b/crates/core/src/debug/trace.rs @@ -6,6 +6,8 @@ pub enum EdgeKind { Next, /// Eager evaluation of a thunk argument (child execution) EagerCall { arg_index: usize }, + /// Child execution triggered inside a runtime handler + RuntimeCall { label: Option }, } #[derive(Debug, Clone)] diff --git a/crates/core/src/debug/tracer.rs b/crates/core/src/debug/tracer.rs index b0c26f6..9009727 100644 --- a/crates/core/src/debug/tracer.rs +++ b/crates/core/src/debug/tracer.rs @@ -6,6 +6,8 @@ pub trait ExecutionTracer { fn enter_node(&mut self, node_id: i64, function_name: &str) -> u64; fn record_arg(&mut self, frame_id: u64, arg: ArgTrace); fn link_child(&mut self, parent_frame: u64, child_frame: u64, edge: EdgeKind); + fn mark_thunk(&mut self, frame_id: u64, arg_index: usize, eager: bool, executed: bool); + fn mark_thunk_executed_by_node(&mut self, frame_id: u64, node_id: i64); fn exit_node(&mut self, frame_id: u64, outcome: Outcome); } @@ -91,6 +93,43 @@ impl ExecutionTracer for Tracer { .push((edge, child_frame)); } + fn mark_thunk(&mut self, frame_id: u64, arg_index: usize, eager: bool, executed: bool) { + let f = self.get_frame_mut(frame_id); + if let Some(arg) = f.args.iter_mut().find(|a| a.index == arg_index) { + if let ArgTrace { + kind: crate::debug::trace::ArgKind::Thunk { + eager: e, + executed: x, + .. + }, + .. + } = arg + { + *e = eager; + *x = executed; + } + } + } + + fn mark_thunk_executed_by_node(&mut self, frame_id: u64, node_id: i64) { + let f = self.get_frame_mut(frame_id); + if let Some(arg) = f.args.iter_mut().find(|a| { + matches!( + a.kind, + crate::debug::trace::ArgKind::Thunk { node_id: id, executed: false, .. } + if id == node_id + ) + }) { + if let ArgTrace { + kind: crate::debug::trace::ArgKind::Thunk { executed: x, .. }, + .. + } = arg + { + *x = true; + } + } + } + fn exit_node(&mut self, frame_id: u64, outcome: Outcome) { let f = self.get_frame_mut(frame_id); f.outcome = Some(outcome); diff --git a/crates/core/src/runtime/functions/array.rs b/crates/core/src/runtime/functions/array.rs index de149be..8d8829a 100644 --- a/crates/core/src/runtime/functions/array.rs +++ b/crates/core/src/runtime/functions/array.rs @@ -167,8 +167,13 @@ fn filter( parameter_index: 1, input_index: 0, }; - for item in array.values.iter() { + for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); + ctx.push_runtime_trace_label(format!( + "iter={} value={}", + idx, + preview_value(item) + )); let pred_sig = run(*predicate_node, ctx); match pred_sig { @@ -216,8 +221,13 @@ fn find( input_index: 0, }; - for item in array.values.iter() { + for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); + ctx.push_runtime_trace_label(format!( + "iter={} value={}", + idx, + preview_value(item) + )); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { @@ -272,8 +282,13 @@ fn find_last( input_index: 0, }; - for item in array.values.into_iter() { + for (idx, item) in array.values.into_iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); + ctx.push_runtime_trace_label(format!( + "iter={} value={}", + idx, + preview_value(&item) + )); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { @@ -329,6 +344,11 @@ fn find_index( for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); + ctx.push_runtime_trace_label(format!( + "iter={} value={}", + idx, + preview_value(item) + )); let pred_sig = run(*predicate_node, ctx); match pred_sig { @@ -416,8 +436,13 @@ fn for_each( input_index: 0, }; - for item in array.values.iter() { + for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); + ctx.push_runtime_trace_label(format!( + "iter={} value={}", + idx, + preview_value(item) + )); let sig = run(*transform_node, ctx); match sig { @@ -435,6 +460,37 @@ fn for_each( }) } +fn preview_value(value: &Value) -> String { + format_value_json(value) +} + +fn format_value_json(value: &Value) -> String { + match value.kind.as_ref() { + Some(Kind::NumberValue(v)) => v.to_string(), + Some(Kind::BoolValue(v)) => v.to_string(), + Some(Kind::StringValue(v)) => format!("{:?}", v), + Some(Kind::NullValue(_)) | None => "null".to_string(), + Some(Kind::ListValue(list)) => { + let mut parts = Vec::new(); + for item in list.values.iter() { + parts.push(format_value_json(item)); + } + format!("[{}]", parts.join(", ")) + } + Some(Kind::StructValue(struct_value)) => { + let mut keys: Vec<_> = struct_value.fields.keys().collect(); + keys.sort(); + let mut parts = Vec::new(); + for key in keys.iter() { + if let Some(v) = struct_value.fields.get(*key) { + parts.push(format!("{:?}: {}", key, format_value_json(v))); + } + } + format!("{{{}}}", parts.join(", ")) + } + } +} + fn map( args: &[Argument], ctx: &mut Context, @@ -463,8 +519,13 @@ fn map( input_index: 0, }; - for item in array.values.iter() { + for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); + ctx.push_runtime_trace_label(format!( + "iter={} value={}", + idx, + preview_value(item) + )); let sig = run(*transform_node, ctx); match sig { Signal::Success(v) => out.push(v), @@ -662,9 +723,17 @@ fn sort( }; let mut signals = Vec::new(); + let mut cmp_idx = 0usize; array.values.sort_by(|a, b| { ctx.insert_input_type(input_type, a.clone()); ctx.insert_input_type(input_type_next, b.clone()); + ctx.push_runtime_trace_label(format!( + "cmp#{} a={} b={}", + cmp_idx, + preview_value(a), + preview_value(b) + )); + cmp_idx += 1; let sig = run(*transform_node, ctx); signals.push(sig); Ordering::Equal @@ -748,9 +817,17 @@ fn sort_reverse( }; let mut signals = Vec::new(); + let mut cmp_idx = 0usize; array.values.sort_by(|a, b| { ctx.insert_input_type(input_type, a.clone()); ctx.insert_input_type(input_type_next, b.clone()); + ctx.push_runtime_trace_label(format!( + "cmp#{} a={} b={}", + cmp_idx, + preview_value(a), + preview_value(b) + )); + cmp_idx += 1; let sig = run(*transform_node, ctx); signals.push(sig); Ordering::Equal diff --git a/crates/core/src/runtime/functions/control.rs b/crates/core/src/runtime/functions/control.rs index a5d766a..a601cf7 100644 --- a/crates/core/src/runtime/functions/control.rs +++ b/crates/core/src/runtime/functions/control.rs @@ -59,6 +59,7 @@ fn r#if( }; if *bool { + ctx.push_runtime_trace_label("branch=if".to_string()); run(*if_pointer, ctx) } else { Signal::Return(Value { @@ -87,8 +88,10 @@ fn if_else( }; if *bool { + ctx.push_runtime_trace_label("branch=if".to_string()); run(*if_pointer, ctx) } else { + ctx.push_runtime_trace_label("branch=else".to_string()); run(*else_pointer, ctx) } } From fe61eabe2a6cd3f0605a0189476c4d58f124dafe Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Mar 2026 15:12:35 +0100 Subject: [PATCH 52/58] feat: added for each test flow --- crates/tests/flows/03_for_each.json | 85 +++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 crates/tests/flows/03_for_each.json diff --git a/crates/tests/flows/03_for_each.json b/crates/tests/flows/03_for_each.json new file mode 100644 index 0000000..4f8740d --- /dev/null +++ b/crates/tests/flows/03_for_each.json @@ -0,0 +1,85 @@ +{ + "name": "03_for_each", + "description": "This flow validates the functionality of the refernece type 'input_type'", + "inputs": [ + { + "input": null, + "expected_result": null + } + ], + "flow": { + "startingNodeId": "5", + "nodeFunctions": [ + { + "databaseId": "5", + "runtimeFunctionId": "std::list::for_each", + "parameters": [ + { + "databaseId": "11", + "runtimeParameterId": "list", + "value": { + "literalValue": { + "listValue": { + "values": [ + { + "numberValue": 1.0 + }, + { + "numberValue": 2.0 + }, + { + "numberValue": 3.0 + }, + { + "numberValue": 4.0 + }, + { + "numberValue": 5.0 + }, + { + "numberValue": 6.0 + } + ] + } + } + } + }, + { + "databaseId": "12", + "runtimeParameterId": "consumer", + "value": { + "nodeFunctionId": "6" + } + } + ] + }, + { + "databaseId": "6", + "runtimeFunctionId": "std::number::add", + "parameters": [ + { + "databaseId": "13", + "runtimeParameterId": "first", + "value": { + "referenceValue": { + "inputType": { + "nodeId": "5", + "parameterIndex": "1" + } + } + } + }, + { + "databaseId": "14", + "runtimeParameterId": "second", + "value": { + "literalValue": { + "numberValue": 2.0 + } + } + } + ] + } + ] + } +} From 6db39d146f8ac1d38fee57f4d55f5f3c5c8bf125 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Mar 2026 15:15:01 +0100 Subject: [PATCH 53/58] fix: renamed core crate to taurus core to fix naming errors because crate was referncing core lib --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- crates/core/Cargo.toml | 2 +- crates/taurus/Cargo.toml | 2 +- crates/taurus/src/main.rs | 8 ++++---- crates/tests/Cargo.toml | 2 +- crates/tests/src/main.rs | 12 ++++++------ 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac62f15..d9b0bf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,16 +275,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "core" -version = "0.1.0" -dependencies = [ - "base64", - "log", - "rand 0.10.0", - "tucana 0.0.54", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1716,18 +1706,28 @@ dependencies = [ "async-nats", "base64", "code0-flow", - "core", "env_logger", "futures-lite", "log", "prost", "rand 0.10.0", + "taurus-core", "tokio", "tonic", "tonic-health", "tucana 0.0.54", ] +[[package]] +name = "taurus-core" +version = "0.1.0" +dependencies = [ + "base64", + "log", + "rand 0.10.0", + "tucana 0.0.54", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -1745,11 +1745,11 @@ dependencies = [ name = "tests" version = "0.1.0" dependencies = [ - "core", "env_logger", "log", "serde", "serde_json", + "taurus-core", "tucana 0.0.54", ] diff --git a/Cargo.toml b/Cargo.toml index fee99c5..7815690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,5 @@ tonic = "0.14.1" serde_json = "1.0.149" serde = "1.0.228" -[workspace.dependencies.core] +[workspace.dependencies.taurus-core] path = "./crates/core" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 48a3a1f..78da80b 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "core" +name = "taurus-core" version.workspace = true edition.workspace = true diff --git a/crates/taurus/Cargo.toml b/crates/taurus/Cargo.toml index d2f7cf6..02b838a 100644 --- a/crates/taurus/Cargo.toml +++ b/crates/taurus/Cargo.toml @@ -16,4 +16,4 @@ async-nats = { workspace = true } prost = { workspace = true } tonic-health = { workspace = true } tonic = { workspace = true } -core = { workspace = true } +taurus-core = { workspace = true } diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 67827c3..4581990 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -5,10 +5,10 @@ use code0_flow::flow_service::FlowUpdateService; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; -use core::context::context::Context; -use core::context::executor::Executor; -use core::context::registry::FunctionStore; -use core::context::signal::Signal; +use taurus_core::context::context::Context; +use taurus_core::context::executor::Executor; +use taurus_core::context::registry::FunctionStore; +use taurus_core::context::signal::Signal; use futures_lite::StreamExt; use log::error; use prost::Message; diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 8f819a3..0e4f08a 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true [dependencies] tucana = { workspace = true } -core = { workspace = true } +taurus-core = { workspace = true } log = { workspace = true } env_logger = { workspace = true } serde_json = { workspace = true } diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 9d8d430..2f8c72e 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -1,4 +1,4 @@ -use core::context::{context::Context, executor::Executor, registry::FunctionStore}; +use taurus_core::context::{context::Context, executor::Executor, registry::FunctionStore}; use log::{error, info}; use serde::Deserialize; use serde_json::json; @@ -123,14 +123,14 @@ impl Case { ); match res { - core::context::signal::Signal::Failure(err) => { + taurus_core::context::signal::Signal::Failure(err) => { let json = json!({ "name": err.name, "message": err.message, }); return CaseResult::Failure(input, json); } - core::context::signal::Signal::Success(value) => { + taurus_core::context::signal::Signal::Success(value) => { let json = to_json_value(value); if json == input.clone().expected_result { return CaseResult::Success; @@ -138,7 +138,7 @@ impl Case { return CaseResult::Failure(input, json); } } - core::context::signal::Signal::Return(value) => { + taurus_core::context::signal::Signal::Return(value) => { let json = to_json_value(value); if json == input.clone().expected_result { return CaseResult::Success; @@ -146,7 +146,7 @@ impl Case { return CaseResult::Failure(input, json); } } - core::context::signal::Signal::Respond(value) => { + taurus_core::context::signal::Signal::Respond(value) => { let json = to_json_value(value); if json == input.clone().expected_result { return CaseResult::Success; @@ -154,7 +154,7 @@ impl Case { return CaseResult::Failure(input, json); } } - core::context::signal::Signal::Stop => continue, + taurus_core::context::signal::Signal::Stop => continue, } } From 73ea8c392f55640b190244ec75cb4be76e8a428f Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Mar 2026 15:18:35 +0100 Subject: [PATCH 54/58] feat: added mock functions for iterator that accepts some sort of functions --- crates/core/src/runtime/functions/array.rs | 67 ++++++++++++++++++---- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/crates/core/src/runtime/functions/array.rs b/crates/core/src/runtime/functions/array.rs index 8d8829a..1a6a70f 100644 --- a/crates/core/src/runtime/functions/array.rs +++ b/crates/core/src/runtime/functions/array.rs @@ -1019,6 +1019,9 @@ mod tests { fn a_val(v: Value) -> Argument { Argument::Eval(v) } + fn a_thunk(id: i64) -> Argument { + Argument::Thunk(id) + } fn v_num(n: f64) -> Value { Value { kind: Some(Kind::NumberValue(n)), @@ -1079,6 +1082,28 @@ mod tests { }) } + fn run_from_bools(seq: Vec) -> impl FnMut(i64, &mut Context) -> Signal { + let mut i = 0usize; + move |_, _| { + let b = *seq.get(i).unwrap_or(&false); + i += 1; + Signal::Success(Value { + kind: Some(Kind::BoolValue(b)), + }) + } + } + + fn run_from_values(seq: Vec) -> impl FnMut(i64, &mut Context) -> Signal { + let mut i = 0usize; + move |_, _| { + let v = seq.get(i).cloned().unwrap_or(Value { + kind: Some(Kind::NullValue(0)), + }); + i += 1; + Signal::Success(v) + } + } + // --- at ------------------------------------------------------------------ #[test] fn test_at_success() { @@ -1184,11 +1209,10 @@ mod tests { #[test] fn test_filter_success() { let mut ctx = Context::default(); - let mut run = dummy_run; let array = v_list(vec![v_num(1.0), v_num(2.0), v_num(3.0)]); - let predicate = v_list(vec![v_bool(true), v_bool(false), v_bool(true)]); + let mut run = run_from_bools(vec![true, false, true]); let out = expect_list(filter( - &[a_val(array), a_val(predicate)], + &[a_val(array), a_thunk(1)], &mut ctx, &mut run, )); @@ -1202,13 +1226,13 @@ mod tests { let mut ctx = Context::default(); let mut run = dummy_run; let array = v_list(vec![v_num(1.0)]); - let predicate = v_list(vec![v_bool(true)]); + let _predicate = v_list(vec![v_bool(true)]); match filter(&[a_val(array.clone())], &mut ctx, &mut run) { Signal::Failure(_) => {} x => panic!("{:?}", x), } match filter( - &[a_val(v_str("not_array")), a_val(predicate.clone())], + &[a_val(v_str("not_array")), a_thunk(1)], &mut ctx, &mut run, ) { @@ -1277,18 +1301,36 @@ mod tests { #[test] fn test_for_each_and_map() { let mut ctx = Context::default(); - let mut run = dummy_run; - match for_each(&[], &mut ctx, &mut run) { + let mut called = 0usize; + let mut run = |_, _ctx: &mut Context| { + called += 1; + Signal::Success(Value { + kind: Some(Kind::NullValue(0)), + }) + }; + match for_each( + &[ + a_val(v_list(vec![v_num(1.0), v_num(2.0)])), + a_thunk(1), + ], + &mut ctx, + &mut run, + ) { Signal::Success(Value { kind: Some(Kind::NullValue(_)), }) => {} x => panic!("expected NullValue, got {:?}", x), } + assert_eq!(called, 2); let transformed = v_list(vec![v_str("X"), v_str("Y")]); + let mut run = run_from_values(match transformed.kind.clone() { + Some(Kind::ListValue(ListValue { values })) => values, + _ => unreachable!(), + }); let out = expect_list(map( &[ a_val(v_list(vec![v_num(1.0), v_num(2.0)])), - a_val(transformed.clone()), + a_thunk(2), ], &mut ctx, &mut run, @@ -1503,20 +1545,21 @@ mod tests { #[test] fn test_sort_and_sort_reverse() { let mut ctx = Context::default(); - let mut run = dummy_run; // We don't rely on actual values; ordering is driven by the comparator sequence. let arr = v_list(vec![v_str("a"), v_str("b"), v_str("c"), v_str("d")]); - let comps = v_list(vec![v_num(-1.0), v_num(1.0), v_num(0.0), v_num(-1.0)]); + let comps = vec![v_num(-1.0), v_num(1.0), v_num(0.0), v_num(-1.0)]; + let mut run = run_from_values(comps.clone()); let out = expect_list(sort( - &[a_val(arr.clone()), a_val(comps.clone())], + &[a_val(arr.clone()), a_thunk(1)], &mut ctx, &mut run, )); assert_eq!(out.len(), 4); + let mut run = run_from_values(comps); let out_r = expect_list(sort_reverse( - &[a_val(arr), a_val(comps)], + &[a_val(arr), a_thunk(1)], &mut ctx, &mut run, )); From 9311a1295797fb05fd44a1f703e6cdab8d5061a9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Mar 2026 15:20:24 +0100 Subject: [PATCH 55/58] feat: added test suite to github workflow --- .github/workflows/build-and-test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 34ed793..326c472 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -16,4 +16,7 @@ jobs: env: RUST_BACKTRACE: 'full' - name: Run Tests - run: cargo test \ No newline at end of file + run: cargo test + - name: Run Test Suite + run: cargo run --package tests + From b57259370c1a9e45f9427d36e882aa12fbcacf2e Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 16 Mar 2026 15:25:16 +0100 Subject: [PATCH 56/58] feat: cargo clippy --- crates/core/src/context/executor.rs | 4 +- crates/core/src/debug/tracer.rs | 36 ++++++----- crates/core/src/runtime/functions/array.rs | 70 ++++------------------ crates/taurus/src/main.rs | 8 +-- crates/tests/src/main.rs | 2 +- 5 files changed, 36 insertions(+), 84 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 34248cc..905555a 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -7,9 +7,9 @@ use crate::debug::tracer::{ExecutionTracer, Tracer}; use crate::runtime::error::RuntimeError; use std::collections::HashMap; -use tucana::shared::{NodeFunction, Value}; use tucana::shared::reference_value::Target; use tucana::shared::value::Kind; +use tucana::shared::{NodeFunction, Value}; pub struct Executor<'a> { functions: &'a FunctionStore, @@ -199,7 +199,7 @@ impl<'a> Executor<'a> { }, preview: format!( "ctx.get({}) -> {}", - preview_reference(&r), + preview_reference(r), preview_value(&v) ), }, diff --git a/crates/core/src/debug/tracer.rs b/crates/core/src/debug/tracer.rs index 9009727..ff11c6b 100644 --- a/crates/core/src/debug/tracer.rs +++ b/crates/core/src/debug/tracer.rs @@ -95,19 +95,19 @@ impl ExecutionTracer for Tracer { fn mark_thunk(&mut self, frame_id: u64, arg_index: usize, eager: bool, executed: bool) { let f = self.get_frame_mut(frame_id); - if let Some(arg) = f.args.iter_mut().find(|a| a.index == arg_index) { - if let ArgTrace { - kind: crate::debug::trace::ArgKind::Thunk { - eager: e, - executed: x, - .. - }, + if let Some(arg) = f.args.iter_mut().find(|a| a.index == arg_index) + && let ArgTrace { + kind: + crate::debug::trace::ArgKind::Thunk { + eager: e, + executed: x, + .. + }, .. } = arg - { - *e = eager; - *x = executed; - } + { + *e = eager; + *x = executed; } } @@ -119,14 +119,12 @@ impl ExecutionTracer for Tracer { crate::debug::trace::ArgKind::Thunk { node_id: id, executed: false, .. } if id == node_id ) - }) { - if let ArgTrace { - kind: crate::debug::trace::ArgKind::Thunk { executed: x, .. }, - .. - } = arg - { - *x = true; - } + }) && let ArgTrace { + kind: crate::debug::trace::ArgKind::Thunk { executed: x, .. }, + .. + } = arg + { + *x = true; } } diff --git a/crates/core/src/runtime/functions/array.rs b/crates/core/src/runtime/functions/array.rs index 1a6a70f..ddd180d 100644 --- a/crates/core/src/runtime/functions/array.rs +++ b/crates/core/src/runtime/functions/array.rs @@ -169,11 +169,7 @@ fn filter( }; for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); - ctx.push_runtime_trace_label(format!( - "iter={} value={}", - idx, - preview_value(item) - )); + ctx.push_runtime_trace_label(format!("iter={} value={}", idx, preview_value(item))); let pred_sig = run(*predicate_node, ctx); match pred_sig { @@ -223,11 +219,7 @@ fn find( for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); - ctx.push_runtime_trace_label(format!( - "iter={} value={}", - idx, - preview_value(item) - )); + ctx.push_runtime_trace_label(format!("iter={} value={}", idx, preview_value(item))); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { @@ -284,11 +276,7 @@ fn find_last( for (idx, item) in array.values.into_iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); - ctx.push_runtime_trace_label(format!( - "iter={} value={}", - idx, - preview_value(&item) - )); + ctx.push_runtime_trace_label(format!("iter={} value={}", idx, preview_value(&item))); let pred_sig = run(*predicate_node, ctx); match pred_sig { Signal::Success(v) => match as_bool(&v) { @@ -344,11 +332,7 @@ fn find_index( for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); - ctx.push_runtime_trace_label(format!( - "iter={} value={}", - idx, - preview_value(item) - )); + ctx.push_runtime_trace_label(format!("iter={} value={}", idx, preview_value(item))); let pred_sig = run(*predicate_node, ctx); match pred_sig { @@ -438,11 +422,7 @@ fn for_each( for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); - ctx.push_runtime_trace_label(format!( - "iter={} value={}", - idx, - preview_value(item) - )); + ctx.push_runtime_trace_label(format!("iter={} value={}", idx, preview_value(item))); let sig = run(*transform_node, ctx); match sig { @@ -521,11 +501,7 @@ fn map( for (idx, item) in array.values.iter().enumerate() { ctx.insert_input_type(input_type, item.clone()); - ctx.push_runtime_trace_label(format!( - "iter={} value={}", - idx, - preview_value(item) - )); + ctx.push_runtime_trace_label(format!("iter={} value={}", idx, preview_value(item))); let sig = run(*transform_node, ctx); match sig { Signal::Success(v) => out.push(v), @@ -1211,11 +1187,7 @@ mod tests { let mut ctx = Context::default(); let array = v_list(vec![v_num(1.0), v_num(2.0), v_num(3.0)]); let mut run = run_from_bools(vec![true, false, true]); - let out = expect_list(filter( - &[a_val(array), a_thunk(1)], - &mut ctx, - &mut run, - )); + let out = expect_list(filter(&[a_val(array), a_thunk(1)], &mut ctx, &mut run)); assert_eq!(out.len(), 2); assert_eq!(out[0].kind, Some(Kind::NumberValue(1.0))); assert_eq!(out[1].kind, Some(Kind::NumberValue(3.0))); @@ -1231,11 +1203,7 @@ mod tests { Signal::Failure(_) => {} x => panic!("{:?}", x), } - match filter( - &[a_val(v_str("not_array")), a_thunk(1)], - &mut ctx, - &mut run, - ) { + match filter(&[a_val(v_str("not_array")), a_thunk(1)], &mut ctx, &mut run) { Signal::Failure(_) => {} x => panic!("{:?}", x), } @@ -1309,10 +1277,7 @@ mod tests { }) }; match for_each( - &[ - a_val(v_list(vec![v_num(1.0), v_num(2.0)])), - a_thunk(1), - ], + &[a_val(v_list(vec![v_num(1.0), v_num(2.0)])), a_thunk(1)], &mut ctx, &mut run, ) { @@ -1328,10 +1293,7 @@ mod tests { _ => unreachable!(), }); let out = expect_list(map( - &[ - a_val(v_list(vec![v_num(1.0), v_num(2.0)])), - a_thunk(2), - ], + &[a_val(v_list(vec![v_num(1.0), v_num(2.0)])), a_thunk(2)], &mut ctx, &mut run, )); @@ -1550,19 +1512,11 @@ mod tests { let arr = v_list(vec![v_str("a"), v_str("b"), v_str("c"), v_str("d")]); let comps = vec![v_num(-1.0), v_num(1.0), v_num(0.0), v_num(-1.0)]; let mut run = run_from_values(comps.clone()); - let out = expect_list(sort( - &[a_val(arr.clone()), a_thunk(1)], - &mut ctx, - &mut run, - )); + let out = expect_list(sort(&[a_val(arr.clone()), a_thunk(1)], &mut ctx, &mut run)); assert_eq!(out.len(), 4); let mut run = run_from_values(comps); - let out_r = expect_list(sort_reverse( - &[a_val(arr), a_thunk(1)], - &mut ctx, - &mut run, - )); + let out_r = expect_list(sort_reverse(&[a_val(arr), a_thunk(1)], &mut ctx, &mut run)); assert_eq!(out_r.len(), 4); } diff --git a/crates/taurus/src/main.rs b/crates/taurus/src/main.rs index 4581990..5da9b9e 100644 --- a/crates/taurus/src/main.rs +++ b/crates/taurus/src/main.rs @@ -5,14 +5,14 @@ use code0_flow::flow_service::FlowUpdateService; use code0_flow::flow_config::load_env_file; use code0_flow::flow_config::mode::Mode::DYNAMIC; -use taurus_core::context::context::Context; -use taurus_core::context::executor::Executor; -use taurus_core::context::registry::FunctionStore; -use taurus_core::context::signal::Signal; use futures_lite::StreamExt; use log::error; use prost::Message; use std::collections::HashMap; +use taurus_core::context::context::Context; +use taurus_core::context::executor::Executor; +use taurus_core::context::registry::FunctionStore; +use taurus_core::context::signal::Signal; use tokio::signal; use tonic_health::pb::health_server::HealthServer; use tucana::shared::value::Kind; diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 2f8c72e..5eb1e37 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -1,8 +1,8 @@ -use taurus_core::context::{context::Context, executor::Executor, registry::FunctionStore}; use log::{error, info}; use serde::Deserialize; use serde_json::json; use std::collections::HashMap; +use taurus_core::context::{context::Context, executor::Executor, registry::FunctionStore}; use tucana::shared::{ NodeFunction, ValidationFlow, From 511144d0a04415809340216b5255a76571777322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:36:39 +0100 Subject: [PATCH 57/58] Potential fix for pull request finding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- crates/core/src/context/context.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/core/src/context/context.rs b/crates/core/src/context/context.rs index 0195c17..06ef142 100644 --- a/crates/core/src/context/context.rs +++ b/crates/core/src/context/context.rs @@ -63,13 +63,16 @@ impl Context { if let Some(index) = path.array_index { match curr.kind { Some(ref kind) => { - if let Kind::ListValue(list) = &kind { - match list.values.get(index as usize) { - Some(x) => { - curr = x.clone(); + match kind { + Kind::ListValue(list) => { + match list.values.get(index as usize) { + Some(x) => { + curr = x.clone(); + } + None => return ContextResult::NotFound, } - None => return ContextResult::NotFound, } + _ => return ContextResult::NotFound, } } None => return ContextResult::NotFound, From 60b0465479a2cf3c2ac6b8dae7d6a16a7a874ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:36:56 +0100 Subject: [PATCH 58/58] Potential fix for pull request finding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- crates/core/src/context/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 905555a..307ee79 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -210,7 +210,7 @@ impl<'a> Executor<'a> { ContextResult::NotFound => { return Err(RuntimeError::simple_str( "ReferenceValueNotFound", - "Referenced node not executed", + "Reference not found in context", )); } },