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 #[cfg(feature = "templating")]
63 CompiledNode::StructuredObject(data) => ExpressionNode {
64 id,
65 expression: node_serialize::structured_to_json_string(&data.fields),
66 children: Self::op_children_from_fields(&data.fields),
67 },
68 CompiledNode::Var {
69 scope_level,
70 segments,
71 default_value,
72 ..
73 } => Self::build_compiled_var(id, *scope_level, segments, default_value.as_deref()),
74 #[cfg(feature = "ext-control")]
75 CompiledNode::Exists(data) => Self::leaf(
76 id,
77 node_serialize::compiled_exists_to_json_string(&data.segments),
78 ),
79 #[cfg(feature = "error-handling")]
80 CompiledNode::Throw(_) | CompiledNode::Missing(_) | CompiledNode::MissingSome(_) => {
81 Self::leaf(id, node_serialize::node_to_json_string(node))
82 }
83 #[cfg(not(feature = "error-handling"))]
84 CompiledNode::Missing(_) | CompiledNode::MissingSome(_) => {
85 Self::leaf(id, node_serialize::node_to_json_string(node))
86 }
87 CompiledNode::InvalidArgs { .. } => {
88 Self::leaf(id, "{\"<invalid args>\": null}".to_string())
89 }
90 }
91 }
92
93 #[inline]
95 fn leaf(id: u32, expression: String) -> ExpressionNode {
96 ExpressionNode {
97 id,
98 expression,
99 children: vec![],
100 }
101 }
102
103 #[inline]
106 fn op_children(nodes: &[CompiledNode]) -> Vec<ExpressionNode> {
107 nodes
108 .iter()
109 .filter(|n| Self::is_operator_node(n))
110 .map(Self::build_node)
111 .collect()
112 }
113
114 #[cfg(feature = "templating")]
117 #[inline]
118 fn op_children_from_fields(fields: &[(String, CompiledNode)]) -> Vec<ExpressionNode> {
119 fields
120 .iter()
121 .filter(|(_, n)| Self::is_operator_node(n))
122 .map(|(_, n)| Self::build_node(n))
123 .collect()
124 }
125
126 fn build_compiled_var(
130 id: u32,
131 scope_level: u32,
132 segments: &[crate::node::PathSegment],
133 default_value: Option<&CompiledNode>,
134 ) -> ExpressionNode {
135 let mut children = Vec::new();
136 if let Some(def) = default_value {
137 if Self::is_operator_node(def) {
138 children.push(Self::build_node(def));
139 }
140 }
141 ExpressionNode {
142 id,
143 expression: node_serialize::compiled_var_to_json_string(
144 scope_level,
145 segments,
146 default_value,
147 ),
148 children,
149 }
150 }
151
152 fn is_operator_node(node: &CompiledNode) -> bool {
154 !matches!(node, CompiledNode::Value { .. })
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ExecutionStep {
161 pub step_id: u32,
166 pub node_id: u32,
168 pub context: Value,
170 pub result: Option<Value>,
172 pub error: Option<String>,
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub iteration_index: Option<u32>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub iteration_total: Option<u32>,
180}
181
182pub(crate) struct TraceCollector {
184 steps: Vec<ExecutionStep>,
186 step_counter: u32,
188 iteration_stack: Vec<(u32, u32)>,
190}
191
192impl TraceCollector {
193 pub fn new() -> Self {
195 Self {
196 steps: Vec::new(),
197 step_counter: 0,
198 iteration_stack: Vec::new(),
199 }
200 }
201
202 pub fn record_step(&mut self, node_id: u32, context: Value, result: Value) {
204 let (iteration_index, iteration_total) = self.current_iteration();
205 let step = ExecutionStep {
206 step_id: self.step_counter,
207 node_id,
208 context,
209 result: Some(result),
210 error: None,
211 iteration_index,
212 iteration_total,
213 };
214 self.steps.push(step);
215 self.step_counter += 1;
216 }
217
218 pub fn record_error(&mut self, node_id: u32, context: Value, error: String) {
220 let (iteration_index, iteration_total) = self.current_iteration();
221 let step = ExecutionStep {
222 step_id: self.step_counter,
223 node_id,
224 context,
225 result: None,
226 error: Some(error),
227 iteration_index,
228 iteration_total,
229 };
230 self.steps.push(step);
231 self.step_counter += 1;
232 }
233
234 pub fn push_iteration(&mut self, index: u32, total: u32) {
236 self.iteration_stack.push((index, total));
237 }
238
239 pub fn pop_iteration(&mut self) {
241 self.iteration_stack.pop();
242 }
243
244 fn current_iteration(&self) -> (Option<u32>, Option<u32>) {
246 self.iteration_stack
247 .last()
248 .map(|(i, t)| (Some(*i), Some(*t)))
249 .unwrap_or((None, None))
250 }
251
252 pub fn into_steps(self) -> Vec<ExecutionStep> {
254 self.steps
255 }
256}
257
258impl Default for TraceCollector {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264#[derive(Debug, Clone)]
272pub struct TracedRun<R> {
273 pub result: Result<R, Error>,
276 pub steps: Vec<ExecutionStep>,
278 pub expression_tree: ExpressionNode,
280}
281
282pub struct TracedSession<'e> {
290 engine: &'e crate::Engine,
291}
292
293impl<'e> TracedSession<'e> {
294 #[inline]
297 pub(crate) fn new(engine: &'e crate::Engine) -> Self {
298 Self { engine }
299 }
300
301 pub fn eval<D>(&self, compiled: &crate::Logic, data: D) -> TracedRun<datavalue::OwnedDataValue>
307 where
308 D: crate::OwnedInput,
309 {
310 let owned_data = match data.into_owned_input() {
311 Ok(d) => d,
312 Err(e) => return Self::compile_failed(e),
313 };
314 let arena = bumpalo::Bump::new();
315 let inner = self.eval_borrowed_in(compiled, &owned_data, &arena);
316 TracedRun {
317 result: inner.result.and_then(crate::FromDataValue::from_arena),
318 steps: inner.steps,
319 expression_tree: inner.expression_tree,
320 }
321 }
322
323 pub fn eval_str<R, D>(&self, rule: R, data: D) -> TracedRun<String>
328 where
329 R: crate::IntoLogic,
330 D: crate::OwnedInput,
331 {
332 let owned = match rule.into_owned_logic() {
333 Ok(o) => o,
334 Err(e) => return Self::compile_failed(e),
335 };
336 let compiled = match crate::Logic::compile_for_trace(&owned, self.engine) {
337 Ok(c) => c,
338 Err(e) => return Self::compile_failed(e),
339 };
340 let owned_data = match data.into_owned_input() {
341 Ok(d) => d,
342 Err(e) => return Self::compile_failed(e),
343 };
344 let arena = bumpalo::Bump::new();
345 let inner = self.eval_borrowed_in(&compiled, &owned_data, &arena);
346 TracedRun {
347 result: inner.result.map(|v| v.to_string()),
348 steps: inner.steps,
349 expression_tree: inner.expression_tree,
350 }
351 }
352
353 #[cfg(feature = "serde_json")]
356 #[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
357 pub fn eval_into<T, R, D>(&self, rule: R, data: D) -> TracedRun<T>
358 where
359 T: serde::de::DeserializeOwned,
360 R: crate::IntoLogic,
361 D: crate::OwnedInput,
362 {
363 let owned = match rule.into_owned_logic() {
364 Ok(o) => o,
365 Err(e) => return Self::compile_failed(e),
366 };
367 let compiled = match crate::Logic::compile_for_trace(&owned, self.engine) {
368 Ok(c) => c,
369 Err(e) => return Self::compile_failed(e),
370 };
371 let owned_data = match data.into_owned_input() {
372 Ok(d) => d,
373 Err(e) => return Self::compile_failed(e),
374 };
375 let arena = bumpalo::Bump::new();
376 let inner = self.eval_borrowed_in(&compiled, &owned_data, &arena);
377 let result = inner.result.and_then(|v| {
378 let value: serde_json::Value = crate::FromDataValue::from_arena(v)?;
379 serde_json::from_value(value).map_err(crate::Error::from)
380 });
381 TracedRun {
382 result,
383 steps: inner.steps,
384 expression_tree: inner.expression_tree,
385 }
386 }
387
388 pub fn eval_borrowed<'a, D>(
393 &self,
394 compiled: &'a crate::Logic,
395 data: D,
396 arena: &'a bumpalo::Bump,
397 ) -> TracedRun<&'a crate::DataValue<'a>>
398 where
399 D: crate::EvalInput<'a>,
400 {
401 self.eval_borrowed_in(compiled, data, arena)
402 }
403
404 fn eval_borrowed_in<'a, D>(
406 &self,
407 compiled: &'a crate::Logic,
408 data: D,
409 arena: &'a bumpalo::Bump,
410 ) -> TracedRun<&'a crate::DataValue<'a>>
411 where
412 D: crate::EvalInput<'a>,
413 {
414 let expression_tree = ExpressionNode::build_from_compiled(&compiled.root);
415 let _depth_guard = match self.engine.enter_dispatch_boundary() {
416 Ok(g) => g,
417 Err(e) => {
418 return TracedRun {
419 expression_tree,
420 steps: TraceCollector::new().into_steps(),
421 result: Err(e),
422 };
423 }
424 };
425 let data_ref = match data.into_arena_value(arena) {
426 Ok(av) => av,
427 Err(e) => {
428 return TracedRun {
429 expression_tree,
430 steps: TraceCollector::new().into_steps(),
431 result: Err(e),
432 };
433 }
434 };
435 let mut ctx = crate::arena::ContextStack::new(data_ref);
436 ctx.attach_tracer(TraceCollector::new());
437
438 let outcome = self.engine.dispatch_node(&compiled.root, &mut ctx, arena);
439 let result = match outcome {
440 Ok(av) => Ok(av),
441 Err(e) => Err(e.decorated(ctx.take_error_path(), compiled, false)),
442 };
443 let collector = ctx.detach_tracer().expect("attach_tracer was called above");
444 TracedRun {
445 result,
446 steps: collector.into_steps(),
447 expression_tree,
448 }
449 }
450
451 fn compile_failed<R>(error: crate::Error) -> TracedRun<R> {
452 TracedRun {
453 result: Err(error),
454 steps: Vec::new(),
455 expression_tree: ExpressionNode {
456 id: 0,
457 expression: String::new(),
458 children: Vec::new(),
459 },
460 }
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use crate::OpCode;
468
469 #[test]
470 fn test_expression_node_from_simple_operator() {
471 let node = CompiledNode::BuiltinOperator {
473 id: crate::node::SYNTHETIC_ID,
474 opcode: OpCode::Val,
475 args: vec![CompiledNode::synthetic_value(
476 datavalue::OwnedDataValue::from("age"),
477 )]
478 .into_boxed_slice(),
479 predicate_hint: None,
480 iter_arg_kind: crate::operators::array::IterArgKind::General,
481 };
482
483 let tree = ExpressionNode::build_from_compiled(&node);
484
485 assert_eq!(tree.id, 0);
489 assert_eq!(tree.expression, r#"{"val": "age"}"#);
490 assert!(tree.children.is_empty()); }
492
493 #[test]
494 fn test_expression_node_from_nested_operator() {
495 let var_node = CompiledNode::BuiltinOperator {
497 id: crate::node::SYNTHETIC_ID,
498 opcode: OpCode::Val,
499 args: vec![CompiledNode::synthetic_value(
500 datavalue::OwnedDataValue::from("age"),
501 )]
502 .into_boxed_slice(),
503 predicate_hint: None,
504 iter_arg_kind: crate::operators::array::IterArgKind::General,
505 };
506 let node = CompiledNode::BuiltinOperator {
507 id: crate::node::SYNTHETIC_ID,
508 opcode: OpCode::GreaterThanEqual,
509 args: vec![
510 var_node,
511 CompiledNode::synthetic_value(datavalue::OwnedDataValue::Number(
512 datavalue::NumberValue::Integer(18),
513 )),
514 ]
515 .into_boxed_slice(),
516 predicate_hint: None,
517 iter_arg_kind: crate::operators::array::IterArgKind::General,
518 };
519
520 let tree = ExpressionNode::build_from_compiled(&node);
521
522 assert_eq!(tree.id, 0);
523 assert!(tree.expression.contains(">="));
524 assert_eq!(tree.children.len(), 1); assert!(tree.children[0].expression.contains("val"));
526 }
527
528 #[test]
529 fn test_trace_collector_records_steps() {
530 let mut collector = TraceCollector::new();
531
532 collector.record_step(0, serde_json::json!({"age": 25}), serde_json::json!(25));
533 collector.record_step(1, serde_json::json!({"age": 25}), serde_json::json!(true));
534
535 let steps = collector.into_steps();
536 assert_eq!(steps.len(), 2);
537 assert_eq!(steps[0].step_id, 0);
538 assert_eq!(steps[0].node_id, 0);
539 assert_eq!(steps[1].step_id, 1);
540 assert_eq!(steps[1].node_id, 1);
541 }
542
543 #[test]
544 fn test_trace_collector_iteration_context() {
545 let mut collector = TraceCollector::new();
546
547 collector.push_iteration(0, 3);
548 collector.record_step(2, serde_json::json!(1), serde_json::json!(2));
549
550 let steps = collector.into_steps();
551 assert_eq!(steps[0].iteration_index, Some(0));
552 assert_eq!(steps[0].iteration_total, Some(3));
553 }
554
555 #[test]
556 fn traced_session_evaluate_str_smoke() {
557 let engine = crate::Engine::new();
558 let run = engine.trace().eval_str(r#"{"+": [1, 2, 3]}"#, "null");
559 assert_eq!(run.result.unwrap(), "6");
560 assert!(!run.steps.is_empty(), "expected non-empty steps");
563 assert_ne!(run.expression_tree.id, 0);
564 }
565
566 #[test]
567 fn traced_pre_compiled_inherits_fold() {
568 let engine = crate::Engine::new();
571 let compiled = engine.compile(r#"{"+": [1, 2]}"#).unwrap();
572 let arena = bumpalo::Bump::new();
573 let data = datavalue::DataValue::from_str("null", &arena).unwrap();
574 let run = engine.trace().eval_borrowed(&compiled, data, &arena);
575 assert_eq!(run.result.as_ref().unwrap().as_i64(), Some(3));
576 assert!(
577 run.steps.is_empty(),
578 "folded rule should not produce trace steps"
579 );
580 }
581
582 #[test]
583 fn traced_session_carries_error_metadata() {
584 let engine = crate::Engine::new();
585 let run = engine.trace().eval_str(r#"{"+": ["x", 1]}"#, "null");
586 let err = run.result.expect_err("string-arith should fail");
587 assert_eq!(err.operator(), Some("+"));
588 assert!(!err.node_ids().is_empty(), "expected populated breadcrumb");
589 }
590}