1use crate::recorder::{TraceNode, TraceTree};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct Divergence {
9 pub node_id: String,
11 pub a: Side,
13 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}