Skip to main content

trellis_core/
trace.rs

1use crate::{
2    AuditEntry, ClearReason, OutputFrameKind, OutputKey, RebaselineReason, ResourceCommand,
3    ResourceKey, ScopeId, TransactionId, TransactionPhase, TransactionResult,
4};
5use crate::{NodeId, Revision};
6
7/// Deterministic payload-free projection of a committed transaction result.
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct TransactionTrace {
10    /// Committed transaction id.
11    pub transaction_id: TransactionId,
12    /// Graph revision after commit.
13    pub revision: Revision,
14    /// Input nodes changed by this transaction.
15    pub changed_inputs: Vec<NodeId>,
16    /// Derived nodes changed by this transaction.
17    pub changed_derived_nodes: Vec<NodeId>,
18    /// Collection nodes changed by this transaction.
19    pub changed_collection_nodes: Vec<NodeId>,
20    /// Resource command identity and operation trace.
21    pub resource_commands: Vec<ResourceCommandTrace>,
22    /// Output frame identity and kind trace.
23    pub output_frames: Vec<OutputFrameTrace>,
24    /// Audit log emitted by the transaction.
25    pub audit_log: Vec<AuditEntry>,
26    /// Phase trace emitted by the transaction.
27    pub phase_trace: Vec<TransactionPhase>,
28}
29
30impl TransactionTrace {
31    /// Builds a deterministic trace from a transaction result.
32    pub fn from_result<C, O>(result: &TransactionResult<C, O>) -> Self {
33        Self {
34            transaction_id: result.transaction_id,
35            revision: result.revision,
36            changed_inputs: result.changed_inputs.clone(),
37            changed_derived_nodes: result.changed_derived_nodes.clone(),
38            changed_collection_nodes: result.changed_collection_nodes.clone(),
39            resource_commands: result
40                .resource_plan
41                .commands()
42                .iter()
43                .map(ResourceCommandTrace::from_command)
44                .collect(),
45            output_frames: result
46                .output_frames
47                .iter()
48                .map(|frame| OutputFrameTrace {
49                    output_key: frame.output_key,
50                    scope: frame.scope,
51                    transaction_id: frame.transaction_id,
52                    revision: frame.revision,
53                    kind: OutputFrameKindTrace::from_kind(&frame.kind),
54                })
55                .collect(),
56            audit_log: result.audit_log.clone(),
57            phase_trace: result.phase_trace.clone(),
58        }
59    }
60}
61
62/// Payload-free resource command trace.
63#[derive(Clone, Debug, Eq, PartialEq)]
64pub struct ResourceCommandTrace {
65    /// Resource identity.
66    pub key: ResourceKey,
67    /// Scope associated with the command.
68    pub scope: ScopeId,
69    /// Command operation.
70    pub kind: ResourceCommandKind,
71}
72
73impl ResourceCommandTrace {
74    fn from_command<C>(command: &ResourceCommand<C>) -> Self {
75        Self {
76            key: command.key().clone(),
77            scope: command.scope(),
78            kind: ResourceCommandKind::from_command(command),
79        }
80    }
81}
82
83/// Resource command operation without application payload.
84#[derive(Copy, Clone, Debug, Eq, PartialEq)]
85pub enum ResourceCommandKind {
86    /// Open a resource.
87    Open,
88    /// Close a resource.
89    Close,
90    /// Replace a resource.
91    Replace,
92    /// Refresh a resource.
93    Refresh,
94}
95
96impl ResourceCommandKind {
97    pub(crate) fn from_command<C>(command: &ResourceCommand<C>) -> Self {
98        match command {
99            ResourceCommand::Open { .. } => Self::Open,
100            ResourceCommand::Close { .. } => Self::Close,
101            ResourceCommand::Replace { .. } => Self::Replace,
102            ResourceCommand::Refresh { .. } => Self::Refresh,
103        }
104    }
105}
106
107/// Payload-free output frame trace.
108#[derive(Clone, Debug, Eq, PartialEq)]
109pub struct OutputFrameTrace {
110    /// Output identity.
111    pub output_key: OutputKey,
112    /// Scope that owns the output.
113    pub scope: ScopeId,
114    /// Transaction that emitted the frame.
115    pub transaction_id: TransactionId,
116    /// Revision carried by the frame.
117    pub revision: Revision,
118    /// Frame kind without materialized payload.
119    pub kind: OutputFrameKindTrace,
120}
121
122/// Output frame kind without materialized payload.
123#[derive(Copy, Clone, Debug, Eq, PartialEq)]
124pub enum OutputFrameKindTrace {
125    /// Baseline frame.
126    Baseline,
127    /// Delta frame.
128    Delta,
129    /// Clear frame with reason.
130    Clear(ClearReason),
131    /// Rebaseline frame with reason.
132    Rebaseline(RebaselineReason),
133}
134
135impl OutputFrameKindTrace {
136    pub(crate) fn from_kind<O>(kind: &OutputFrameKind<O>) -> Self {
137        match kind {
138            OutputFrameKind::Baseline(_) => Self::Baseline,
139            OutputFrameKind::Delta(_) => Self::Delta,
140            OutputFrameKind::Clear(reason) => Self::Clear(*reason),
141            OutputFrameKind::Rebaseline(_, reason) => Self::Rebaseline(*reason),
142        }
143    }
144}
145
146impl<C, O> TransactionResult<C, O> {
147    /// Returns a deterministic payload-free projection of this result.
148    pub fn trace(&self) -> TransactionTrace {
149        TransactionTrace::from_result(self)
150    }
151}
152
153/// Difference between two replay trace sequences.
154#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct TraceMismatch {
156    /// Expected transaction traces.
157    pub expected: Vec<TransactionTrace>,
158    /// Actual transaction traces.
159    pub actual: Vec<TransactionTrace>,
160}
161
162/// Compares two deterministic transaction trace sequences.
163pub fn assert_transaction_traces_match(
164    expected: &[TransactionTrace],
165    actual: &[TransactionTrace],
166) -> Result<(), TraceMismatch> {
167    if expected == actual {
168        Ok(())
169    } else {
170        Err(TraceMismatch {
171            expected: expected.to_vec(),
172            actual: actual.to_vec(),
173        })
174    }
175}