Skip to main content

lex_trace/
diff.rs

1//! Trace diff: walk two trace trees in parallel and report the first
2//! NodeId where outputs differ. Spec ยง10.3.
3
4use crate::recorder::{TraceNode, TraceTree};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct Divergence {
9    /// First NodeId where the two traces diverge.
10    pub node_id: String,
11    /// Output (or error) on side A.
12    pub a: Side,
13    /// Output (or error) on side B.
14    pub b: Side,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18#[serde(tag = "kind", rename_all = "snake_case")]
19pub enum Side {
20    Output { value: serde_json::Value },
21    Error { message: String },
22    Missing,
23}
24
25pub fn diff_runs(a: &TraceTree, b: &TraceTree) -> Option<Divergence> {
26    walk_pair(&a.nodes, &b.nodes)
27}
28
29fn walk_pair(a: &[TraceNode], b: &[TraceNode]) -> Option<Divergence> {
30    for i in 0..std::cmp::max(a.len(), b.len()) {
31        match (a.get(i), b.get(i)) {
32            (Some(na), Some(nb)) => {
33                if na.node_id != nb.node_id {
34                    return Some(Divergence {
35                        node_id: na.node_id.clone(),
36                        a: side_of(na),
37                        b: side_of(nb),
38                    });
39                }
40                if na.input != nb.input || na.output != nb.output || na.error != nb.error {
41                    return Some(Divergence {
42                        node_id: na.node_id.clone(),
43                        a: side_of(na),
44                        b: side_of(nb),
45                    });
46                }
47                if let Some(d) = walk_pair(&na.children, &nb.children) {
48                    return Some(d);
49                }
50            }
51            (Some(na), None) => return Some(Divergence {
52                node_id: na.node_id.clone(),
53                a: side_of(na),
54                b: Side::Missing,
55            }),
56            (None, Some(nb)) => return Some(Divergence {
57                node_id: nb.node_id.clone(),
58                a: Side::Missing,
59                b: side_of(nb),
60            }),
61            (None, None) => break,
62        }
63    }
64    None
65}
66
67fn side_of(n: &TraceNode) -> Side {
68    if let Some(e) = &n.error {
69        Side::Error { message: e.clone() }
70    } else if let Some(o) = &n.output {
71        Side::Output { value: o.clone() }
72    } else {
73        Side::Missing
74    }
75}