1use serde::{Deserialize, Serialize};
17use serde_json::Value;
18
19use crate::node_serialize;
20use crate::{CompiledNode, Error};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ExpressionNode {
25 pub id: u32,
27 pub expression: String,
29 pub children: Vec<ExpressionNode>,
31}
32
33impl ExpressionNode {
34 pub(crate) fn build_from_compiled(node: &CompiledNode) -> ExpressionNode {
40 Self::build_node(node)
41 }
42
43 fn build_node(node: &CompiledNode) -> ExpressionNode {
44 let id = node.id();
45 match node {
46 CompiledNode::Value { value, .. } => Self::leaf(id, value.to_json_string()),
47 CompiledNode::Array { nodes, .. } => ExpressionNode {
48 id,
49 expression: node_serialize::node_to_json_string(node),
50 children: Self::op_children(nodes),
51 },
52 CompiledNode::BuiltinOperator { opcode, args, .. } => ExpressionNode {
53 id,
54 expression: node_serialize::builtin_to_json_string(opcode, args),
55 children: Self::op_children(args),
56 },
57 CompiledNode::CustomOperator(data) => ExpressionNode {
58 id,
59 expression: node_serialize::custom_to_json_string(&data.name, &data.args),
60 children: Self::op_children(&data.args),
61 },
62 CompiledNode::Cse(data) => Self::build_node(&data.inner),
64 #[cfg(feature = "templating")]
65 CompiledNode::StructuredObject(data) => ExpressionNode {
66 id,
67 expression: node_serialize::structured_to_json_string(&data.fields),
68 children: Self::op_children_from_fields(&data.fields),
69 },
70 CompiledNode::Var {
71 scope_level,
72 segments,
73 default_value,
74 ..
75 } => Self::build_compiled_var(id, *scope_level, segments, default_value.as_deref()),
76 #[cfg(feature = "ext-control")]
77 CompiledNode::Exists(data) => Self::leaf(
78 id,
79 node_serialize::compiled_exists_to_json_string(&data.segments),
80 ),
81 #[cfg(feature = "error-handling")]
82 CompiledNode::Throw(_) | CompiledNode::Missing(_) | CompiledNode::MissingSome(_) => {
83 Self::leaf(id, node_serialize::node_to_json_string(node))
84 }
85 #[cfg(not(feature = "error-handling"))]
86 CompiledNode::Missing(_) | CompiledNode::MissingSome(_) => {
87 Self::leaf(id, node_serialize::node_to_json_string(node))
88 }
89 CompiledNode::InvalidArgs { .. } => {
94 Self::leaf(id, node_serialize::node_to_json_string(node))
95 }
96 }
97 }
98
99 #[inline]
101 fn leaf(id: u32, expression: String) -> ExpressionNode {
102 ExpressionNode {
103 id,
104 expression,
105 children: vec![],
106 }
107 }
108
109 #[inline]
112 fn op_children(nodes: &[CompiledNode]) -> Vec<ExpressionNode> {
113 nodes
114 .iter()
115 .filter(|n| Self::is_operator_node(n))
116 .map(Self::build_node)
117 .collect()
118 }
119
120 #[cfg(feature = "templating")]
123 #[inline]
124 fn op_children_from_fields(fields: &[(String, CompiledNode)]) -> Vec<ExpressionNode> {
125 fields
126 .iter()
127 .filter(|(_, n)| Self::is_operator_node(n))
128 .map(|(_, n)| Self::build_node(n))
129 .collect()
130 }
131
132 fn build_compiled_var(
136 id: u32,
137 scope_level: u32,
138 segments: &[crate::node::PathSegment],
139 default_value: Option<&CompiledNode>,
140 ) -> ExpressionNode {
141 let mut children = Vec::new();
142 if let Some(def) = default_value
143 && Self::is_operator_node(def)
144 {
145 children.push(Self::build_node(def));
146 }
147 ExpressionNode {
148 id,
149 expression: node_serialize::compiled_var_to_json_string(
150 scope_level,
151 segments,
152 default_value,
153 ),
154 children,
155 }
156 }
157
158 fn is_operator_node(node: &CompiledNode) -> bool {
160 !matches!(node, CompiledNode::Value { .. })
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct ExecutionStep {
167 pub step_id: u32,
172 pub node_id: u32,
174 pub context: Value,
176 pub result: Option<Value>,
178 pub error: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub iteration_index: Option<u32>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub iteration_total: Option<u32>,
186}
187
188pub(crate) struct TraceCollector {
190 steps: Vec<ExecutionStep>,
192 step_counter: u32,
194 iteration_stack: Vec<(u32, u32)>,
196}
197
198impl TraceCollector {
199 pub(crate) fn new() -> Self {
201 Self {
202 steps: Vec::new(),
203 step_counter: 0,
204 iteration_stack: Vec::new(),
205 }
206 }
207
208 pub(crate) fn record_step(&mut self, node_id: u32, context: Value, result: Value) {
210 self.record(node_id, context, Some(result), None);
211 }
212
213 pub(crate) fn record_error(&mut self, node_id: u32, context: Value, error: String) {
215 self.record(node_id, context, None, Some(error));
216 }
217
218 fn record(
222 &mut self,
223 node_id: u32,
224 context: Value,
225 result: Option<Value>,
226 error: Option<String>,
227 ) {
228 let (iteration_index, iteration_total) = self.current_iteration();
229 self.steps.push(ExecutionStep {
230 step_id: self.step_counter,
231 node_id,
232 context,
233 result,
234 error,
235 iteration_index,
236 iteration_total,
237 });
238 self.step_counter += 1;
239 }
240
241 pub(crate) fn push_iteration(&mut self, index: u32, total: u32) {
243 self.iteration_stack.push((index, total));
244 }
245
246 pub(crate) fn pop_iteration(&mut self) {
248 self.iteration_stack.pop();
249 }
250
251 fn current_iteration(&self) -> (Option<u32>, Option<u32>) {
253 self.iteration_stack
254 .last()
255 .map(|(i, t)| (Some(*i), Some(*t)))
256 .unwrap_or((None, None))
257 }
258
259 pub(crate) fn into_steps(self) -> Vec<ExecutionStep> {
261 self.steps
262 }
263}
264
265impl Default for TraceCollector {
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271#[derive(Debug, Clone)]
279pub struct TracedRun<R> {
280 pub result: Result<R, Error>,
283 pub steps: Vec<ExecutionStep>,
285 pub expression_tree: ExpressionNode,
287}
288
289impl<R> TracedRun<R> {
290 fn convert<T>(self, f: impl FnOnce(Result<R, Error>) -> Result<T, Error>) -> TracedRun<T> {
295 TracedRun {
296 result: f(self.result),
297 steps: self.steps,
298 expression_tree: self.expression_tree,
299 }
300 }
301}
302
303pub struct TracedSession<'e> {
315 engine: &'e crate::Engine,
316}
317
318impl<'e> TracedSession<'e> {
319 #[inline]
322 pub(crate) fn new(engine: &'e crate::Engine) -> Self {
323 Self { engine }
324 }
325
326 pub fn eval<D>(&self, compiled: &crate::Logic, data: D) -> TracedRun<datavalue::OwnedDataValue>
332 where
333 D: crate::OwnedInput,
334 {
335 let owned_data = match data.into_owned_input() {
336 Ok(d) => d,
337 Err(e) => return Self::compile_failed(e),
338 };
339 let arena = bumpalo::Bump::new();
340 self.eval_borrowed_in(compiled, &owned_data, &arena)
341 .convert(|result| result.and_then(crate::FromDataValue::from_arena))
342 }
343
344 pub fn eval_str<R, D>(&self, rule: R, data: D) -> TracedRun<String>
349 where
350 R: crate::IntoLogic,
351 D: crate::OwnedInput,
352 {
353 let (compiled, owned_data) = match self.prepare(rule, data) {
354 Ok(prepared) => prepared,
355 Err(e) => return Self::compile_failed(e),
356 };
357 let arena = bumpalo::Bump::new();
358 self.eval_borrowed_in(&compiled, &owned_data, &arena)
359 .convert(|result| result.map(|v| v.to_string()))
360 }
361
362 #[cfg(feature = "serde_json")]
365 #[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
366 pub fn eval_into<T, R, D>(&self, rule: R, data: D) -> TracedRun<T>
367 where
368 T: serde::de::DeserializeOwned,
369 R: crate::IntoLogic,
370 D: crate::OwnedInput,
371 {
372 let (compiled, owned_data) = match self.prepare(rule, data) {
373 Ok(prepared) => prepared,
374 Err(e) => return Self::compile_failed(e),
375 };
376 let arena = bumpalo::Bump::new();
377 self.eval_borrowed_in(&compiled, &owned_data, &arena)
378 .convert(|result| {
379 result.and_then(|v| {
380 let value: serde_json::Value = crate::FromDataValue::from_arena(v)?;
381 serde_json::from_value(value).map_err(crate::Error::from)
382 })
383 })
384 }
385
386 fn prepare<R, D>(
391 &self,
392 rule: R,
393 data: D,
394 ) -> crate::Result<(crate::Logic, datavalue::OwnedDataValue)>
395 where
396 R: crate::IntoLogic,
397 D: crate::OwnedInput,
398 {
399 let owned = rule.into_owned_logic()?;
400 let compiled = crate::Logic::compile_for_trace(&owned, self.engine)?;
401 let owned_data = data.into_owned_input()?;
402 Ok((compiled, owned_data))
403 }
404
405 pub fn eval_borrowed<'a, D>(
410 &self,
411 compiled: &'a crate::Logic,
412 data: D,
413 arena: &'a bumpalo::Bump,
414 ) -> TracedRun<&'a crate::DataValue<'a>>
415 where
416 D: crate::EvalInput<'a>,
417 {
418 self.eval_borrowed_in(compiled, data, arena)
419 }
420
421 fn eval_borrowed_in<'a, D>(
423 &self,
424 compiled: &'a crate::Logic,
425 data: D,
426 arena: &'a bumpalo::Bump,
427 ) -> TracedRun<&'a crate::DataValue<'a>>
428 where
429 D: crate::EvalInput<'a>,
430 {
431 let expression_tree = ExpressionNode::build_from_compiled(&compiled.root);
432 let _depth_guard = match self.engine.enter_dispatch_boundary() {
433 Ok(g) => g,
434 Err(e) => return Self::failed(expression_tree, e),
435 };
436 let data_ref = match data.into_arena_value(arena) {
437 Ok(av) => av,
438 Err(e) => return Self::failed(expression_tree, e),
439 };
440 let mut ctx = self.engine.new_context(compiled, data_ref);
444 ctx.attach_tracer(TraceCollector::new());
445
446 let outcome = self.engine.dispatch_node(&compiled.root, &mut ctx, arena);
447 let result = match outcome {
448 Ok(av) => Ok(av),
449 Err(e) => Err(e.decorated(ctx.take_error_path(), compiled, false)),
450 };
451 let collector = ctx.detach_tracer().expect("attach_tracer was called above");
452 TracedRun {
453 result,
454 steps: collector.into_steps(),
455 expression_tree,
456 }
457 }
458
459 fn failed<R>(expression_tree: ExpressionNode, error: crate::Error) -> TracedRun<R> {
462 TracedRun {
463 result: Err(error),
464 steps: Vec::new(),
465 expression_tree,
466 }
467 }
468
469 fn compile_failed<R>(error: crate::Error) -> TracedRun<R> {
473 Self::failed(
474 ExpressionNode {
475 id: 0,
476 expression: String::new(),
477 children: Vec::new(),
478 },
479 error,
480 )
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use crate::OpCode;
488
489 #[test]
490 fn test_expression_node_from_simple_operator() {
491 let node = CompiledNode::BuiltinOperator {
493 id: crate::node::SYNTHETIC_ID,
494 opcode: OpCode::Val,
495 args: vec![CompiledNode::synthetic_value(
496 datavalue::OwnedDataValue::from("age"),
497 )]
498 .into_boxed_slice(),
499 predicate_hint: None,
500 iter_arg_kind: crate::operators::array::IterArgKind::General,
501 };
502
503 let tree = ExpressionNode::build_from_compiled(&node);
504
505 assert_eq!(tree.id, 0);
509 assert_eq!(tree.expression, r#"{"val": "age"}"#);
510 assert!(tree.children.is_empty()); }
512
513 #[test]
514 fn test_expression_node_from_nested_operator() {
515 let var_node = CompiledNode::BuiltinOperator {
517 id: crate::node::SYNTHETIC_ID,
518 opcode: OpCode::Val,
519 args: vec![CompiledNode::synthetic_value(
520 datavalue::OwnedDataValue::from("age"),
521 )]
522 .into_boxed_slice(),
523 predicate_hint: None,
524 iter_arg_kind: crate::operators::array::IterArgKind::General,
525 };
526 let node = CompiledNode::BuiltinOperator {
527 id: crate::node::SYNTHETIC_ID,
528 opcode: OpCode::GreaterThanEqual,
529 args: vec![
530 var_node,
531 CompiledNode::synthetic_value(datavalue::OwnedDataValue::Number(
532 datavalue::NumberValue::Integer(18),
533 )),
534 ]
535 .into_boxed_slice(),
536 predicate_hint: None,
537 iter_arg_kind: crate::operators::array::IterArgKind::General,
538 };
539
540 let tree = ExpressionNode::build_from_compiled(&node);
541
542 assert_eq!(tree.id, 0);
543 assert!(tree.expression.contains(">="));
544 assert_eq!(tree.children.len(), 1); assert!(tree.children[0].expression.contains("val"));
546 }
547
548 #[test]
549 fn test_trace_collector_records_steps() {
550 let mut collector = TraceCollector::new();
551
552 collector.record_step(0, serde_json::json!({"age": 25}), serde_json::json!(25));
553 collector.record_step(1, serde_json::json!({"age": 25}), serde_json::json!(true));
554
555 let steps = collector.into_steps();
556 assert_eq!(steps.len(), 2);
557 assert_eq!(steps[0].step_id, 0);
558 assert_eq!(steps[0].node_id, 0);
559 assert_eq!(steps[1].step_id, 1);
560 assert_eq!(steps[1].node_id, 1);
561 }
562
563 #[test]
564 fn test_trace_collector_iteration_context() {
565 let mut collector = TraceCollector::new();
566
567 collector.push_iteration(0, 3);
568 collector.record_step(2, serde_json::json!(1), serde_json::json!(2));
569
570 let steps = collector.into_steps();
571 assert_eq!(steps[0].iteration_index, Some(0));
572 assert_eq!(steps[0].iteration_total, Some(3));
573 }
574
575 #[test]
576 fn traced_session_evaluate_str_smoke() {
577 let engine = crate::Engine::new();
578 let run = engine.trace().eval_str(r#"{"+": [1, 2, 3]}"#, "null");
579 assert_eq!(run.result.unwrap(), "6");
580 assert!(!run.steps.is_empty(), "expected non-empty steps");
583 assert_ne!(run.expression_tree.id, 0);
584 }
585
586 #[test]
587 fn traced_pre_compiled_inherits_fold() {
588 let engine = crate::Engine::new();
591 let compiled = engine.compile(r#"{"+": [1, 2]}"#).unwrap();
592 let arena = bumpalo::Bump::new();
593 let data = datavalue::DataValue::from_str("null", &arena).unwrap();
594 let run = engine.trace().eval_borrowed(&compiled, data, &arena);
595 assert_eq!(run.result.as_ref().unwrap().as_i64(), Some(3));
596 assert!(
597 run.steps.is_empty(),
598 "folded rule should not produce trace steps"
599 );
600 }
601
602 #[test]
603 fn traced_session_carries_error_metadata() {
604 let engine = crate::Engine::new();
605 let run = engine.trace().eval_str(r#"{"+": ["x", 1]}"#, "null");
606 let err = run.result.expect_err("string-arith should fail");
607 assert_eq!(err.operator(), Some("+"));
608 assert!(!err.node_ids().is_empty(), "expected populated breadcrumb");
609 }
610}