use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::{CompiledNode, OpCode};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracedResult {
pub result: Value,
pub expression_tree: ExpressionNode,
pub steps: Vec<ExecutionStep>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExpressionNode {
pub id: u32,
pub expression: String,
pub children: Vec<ExpressionNode>,
}
impl ExpressionNode {
pub fn build_from_compiled(node: &CompiledNode) -> (ExpressionNode, HashMap<usize, u32>) {
let mut id_counter = 0u32;
let mut node_id_map = HashMap::new();
let tree = Self::build_node(node, &mut id_counter, &mut node_id_map);
(tree, node_id_map)
}
fn build_node(
node: &CompiledNode,
id_counter: &mut u32,
node_id_map: &mut HashMap<usize, u32>,
) -> ExpressionNode {
let id = *id_counter;
*id_counter += 1;
let node_ptr = node as *const CompiledNode as usize;
node_id_map.insert(node_ptr, id);
match node {
CompiledNode::Value { value, .. } => {
ExpressionNode {
id,
expression: value.to_string(),
children: vec![],
}
}
CompiledNode::Array { nodes, .. } => {
let children: Vec<ExpressionNode> = nodes
.iter()
.filter(|n| Self::is_operator_node(n))
.map(|n| Self::build_node(n, id_counter, node_id_map))
.collect();
ExpressionNode {
id,
expression: Self::node_to_json_string(node),
children,
}
}
CompiledNode::BuiltinOperator { opcode, args, .. } => {
let children: Vec<ExpressionNode> = args
.iter()
.filter(|n| Self::is_operator_node(n))
.map(|n| Self::build_node(n, id_counter, node_id_map))
.collect();
ExpressionNode {
id,
expression: Self::builtin_to_json_string(opcode, args),
children,
}
}
CompiledNode::CustomOperator(data) => {
let children: Vec<ExpressionNode> = data
.args
.iter()
.filter(|n| Self::is_operator_node(n))
.map(|n| Self::build_node(n, id_counter, node_id_map))
.collect();
ExpressionNode {
id,
expression: Self::custom_to_json_string(&data.name, &data.args),
children,
}
}
CompiledNode::StructuredObject(data) => {
let children: Vec<ExpressionNode> = data
.fields
.iter()
.filter(|(_, n)| Self::is_operator_node(n))
.map(|(_, n)| Self::build_node(n, id_counter, node_id_map))
.collect();
ExpressionNode {
id,
expression: Self::structured_to_json_string(&data.fields),
children,
}
}
CompiledNode::CompiledVar {
scope_level,
segments,
default_value,
..
} => {
let mut children = Vec::new();
if let Some(def) = default_value
&& Self::is_operator_node(def)
{
children.push(Self::build_node(def, id_counter, node_id_map));
}
ExpressionNode {
id,
expression: Self::compiled_var_to_json_string(
*scope_level,
segments,
default_value.as_deref(),
),
children,
}
}
CompiledNode::CompiledExists(data) => ExpressionNode {
id,
expression: Self::compiled_exists_to_json_string(data.scope_level, &data.segments),
children: vec![],
},
CompiledNode::CompiledSplitRegex(data) => {
let children: Vec<ExpressionNode> = data
.args
.iter()
.filter(|n| Self::is_operator_node(n))
.map(|n| Self::build_node(n, id_counter, node_id_map))
.collect();
ExpressionNode {
id,
expression: format!(
"{{\"split\": [{}, \"{}\"]}}",
Self::node_to_json_string(&data.args[0]),
data.regex.as_str()
),
children,
}
}
CompiledNode::CompiledThrow(_) => ExpressionNode {
id,
expression: Self::node_to_json_string(node),
children: vec![],
},
}
}
fn is_operator_node(node: &CompiledNode) -> bool {
!matches!(node, CompiledNode::Value { .. })
}
fn node_to_json_string(node: &CompiledNode) -> String {
match node {
CompiledNode::Value { value, .. } => value.to_string(),
CompiledNode::Array { nodes, .. } => {
let items: Vec<String> = nodes.iter().map(Self::node_to_json_string).collect();
format!("[{}]", items.join(", "))
}
CompiledNode::BuiltinOperator { opcode, args, .. } => {
Self::builtin_to_json_string(opcode, args)
}
CompiledNode::CustomOperator(data) => {
Self::custom_to_json_string(&data.name, &data.args)
}
CompiledNode::StructuredObject(data) => Self::structured_to_json_string(&data.fields),
CompiledNode::CompiledVar {
scope_level,
segments,
default_value,
..
} => {
Self::compiled_var_to_json_string(*scope_level, segments, default_value.as_deref())
}
CompiledNode::CompiledExists(data) => {
Self::compiled_exists_to_json_string(data.scope_level, &data.segments)
}
CompiledNode::CompiledSplitRegex(data) => {
format!(
"{{\"split\": [{}, \"{}\"]}}",
Self::node_to_json_string(&data.args[0]),
data.regex.as_str()
)
}
CompiledNode::CompiledThrow(error_obj) => {
if let serde_json::Value::Object(err_map) = error_obj.as_ref()
&& let Some(serde_json::Value::String(s)) = err_map.get("type")
{
return format!("{{\"throw\": \"{}\"}}", s);
}
format!("{{\"throw\": {}}}", error_obj)
}
}
}
fn builtin_to_json_string(opcode: &OpCode, args: &[CompiledNode]) -> String {
let op_str = opcode.as_str();
let args_str = if args.len() == 1 {
Self::node_to_json_string(&args[0])
} else {
let items: Vec<String> = args.iter().map(Self::node_to_json_string).collect();
format!("[{}]", items.join(", "))
};
format!("{{\"{}\": {}}}", op_str, args_str)
}
fn custom_to_json_string(name: &str, args: &[CompiledNode]) -> String {
let args_str = if args.len() == 1 {
Self::node_to_json_string(&args[0])
} else {
let items: Vec<String> = args.iter().map(Self::node_to_json_string).collect();
format!("[{}]", items.join(", "))
};
format!("{{\"{}\": {}}}", name, args_str)
}
fn structured_to_json_string(fields: &[(String, CompiledNode)]) -> String {
let items: Vec<String> = fields
.iter()
.map(|(key, node)| format!("\"{}\": {}", key, Self::node_to_json_string(node)))
.collect();
format!("{{{}}}", items.join(", "))
}
fn compiled_var_to_json_string(
scope_level: u32,
segments: &[crate::node::PathSegment],
default_value: Option<&CompiledNode>,
) -> String {
use crate::node::PathSegment;
if scope_level == 0 {
let path: String = segments
.iter()
.map(|seg| match seg {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => s.to_string(),
PathSegment::Index(i) => i.to_string(),
})
.collect::<Vec<_>>()
.join(".");
match default_value {
Some(def) => {
format!(
"{{\"var\": [\"{}\", {}]}}",
path,
Self::node_to_json_string(def)
)
}
None => format!("{{\"var\": \"{}\"}}", path),
}
} else {
let mut parts = vec![format!("[{}]", scope_level)];
for seg in segments {
match seg {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => {
parts.push(format!("\"{}\"", s))
}
PathSegment::Index(i) => parts.push(i.to_string()),
}
}
format!("{{\"val\": [{}]}}", parts.join(", "))
}
}
fn compiled_exists_to_json_string(
_scope_level: u32,
segments: &[crate::node::PathSegment],
) -> String {
use crate::node::PathSegment;
if segments.len() == 1 {
match &segments[0] {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => {
format!("{{\"exists\": \"{}\"}}", s)
}
PathSegment::Index(i) => format!("{{\"exists\": {}}}", i),
}
} else {
let parts: Vec<String> = segments
.iter()
.map(|seg| match seg {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => {
format!("\"{}\"", s)
}
PathSegment::Index(i) => i.to_string(),
})
.collect();
format!("{{\"exists\": [{}]}}", parts.join(", "))
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionStep {
pub id: u32,
pub node_id: u32,
pub context: Value,
pub result: Option<Value>,
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iteration_index: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iteration_total: Option<u32>,
}
pub struct TraceCollector {
steps: Vec<ExecutionStep>,
step_counter: u32,
iteration_stack: Vec<(u32, u32)>,
}
impl TraceCollector {
pub fn new() -> Self {
Self {
steps: Vec::new(),
step_counter: 0,
iteration_stack: Vec::new(),
}
}
pub fn record_step(&mut self, node_id: u32, context: Value, result: Value) {
let (iteration_index, iteration_total) = self.current_iteration();
let step = ExecutionStep {
id: self.step_counter,
node_id,
context,
result: Some(result),
error: None,
iteration_index,
iteration_total,
};
self.steps.push(step);
self.step_counter += 1;
}
pub fn record_error(&mut self, node_id: u32, context: Value, error: String) {
let (iteration_index, iteration_total) = self.current_iteration();
let step = ExecutionStep {
id: self.step_counter,
node_id,
context,
result: None,
error: Some(error),
iteration_index,
iteration_total,
};
self.steps.push(step);
self.step_counter += 1;
}
pub fn push_iteration(&mut self, index: u32, total: u32) {
self.iteration_stack.push((index, total));
}
pub fn pop_iteration(&mut self) {
self.iteration_stack.pop();
}
fn current_iteration(&self) -> (Option<u32>, Option<u32>) {
self.iteration_stack
.last()
.map(|(i, t)| (Some(*i), Some(*t)))
.unwrap_or((None, None))
}
pub fn into_steps(self) -> Vec<ExecutionStep> {
self.steps
}
}
impl Default for TraceCollector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::OpCode;
#[test]
fn test_expression_node_from_simple_operator() {
let node = CompiledNode::BuiltinOperator {
opcode: OpCode::Var,
args: vec![CompiledNode::Value {
value: serde_json::json!("age"),
}]
.into_boxed_slice(),
};
let (tree, node_id_map) = ExpressionNode::build_from_compiled(&node);
assert_eq!(tree.id, 0);
assert_eq!(tree.expression, r#"{"var": "age"}"#);
assert!(tree.children.is_empty()); assert_eq!(node_id_map.len(), 1);
}
#[test]
fn test_expression_node_from_nested_operator() {
let var_node = CompiledNode::BuiltinOperator {
opcode: OpCode::Var,
args: vec![CompiledNode::Value {
value: serde_json::json!("age"),
}]
.into_boxed_slice(),
};
let node = CompiledNode::BuiltinOperator {
opcode: OpCode::GreaterThanEqual,
args: vec![
var_node,
CompiledNode::Value {
value: serde_json::json!(18),
},
]
.into_boxed_slice(),
};
let (tree, node_id_map) = ExpressionNode::build_from_compiled(&node);
assert_eq!(tree.id, 0);
assert!(tree.expression.contains(">="));
assert_eq!(tree.children.len(), 1); assert_eq!(tree.children[0].id, 1);
assert!(tree.children[0].expression.contains("var"));
assert_eq!(node_id_map.len(), 2);
}
#[test]
fn test_trace_collector_records_steps() {
let mut collector = TraceCollector::new();
collector.record_step(0, serde_json::json!({"age": 25}), serde_json::json!(25));
collector.record_step(1, serde_json::json!({"age": 25}), serde_json::json!(true));
let steps = collector.into_steps();
assert_eq!(steps.len(), 2);
assert_eq!(steps[0].id, 0);
assert_eq!(steps[0].node_id, 0);
assert_eq!(steps[1].id, 1);
assert_eq!(steps[1].node_id, 1);
}
#[test]
fn test_trace_collector_iteration_context() {
let mut collector = TraceCollector::new();
collector.push_iteration(0, 3);
collector.record_step(2, serde_json::json!(1), serde_json::json!(2));
let steps = collector.into_steps();
assert_eq!(steps[0].iteration_index, Some(0));
assert_eq!(steps[0].iteration_total, Some(3));
}
}