Skip to main content

trellis_testing/
serialized.rs

1use trellis_core::TransactionTrace;
2
3use crate::{Scenario, ScenarioError};
4
5/// Version for serialized Trellis script and trace artifacts.
6pub const TRACE_FORMAT_VERSION: u32 = 1;
7
8/// A versioned, data-only transaction script.
9#[derive(Clone, Debug, Eq, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
11#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
12pub struct DataTransactionScript<Operation> {
13    format_version: u32,
14    steps: Vec<DataScriptStep<Operation>>,
15}
16
17/// One named transaction step in a data-only transaction script.
18#[derive(Clone, Debug, Eq, PartialEq)]
19#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
20#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
21pub struct DataScriptStep<Operation> {
22    name: String,
23    operations: Vec<Operation>,
24}
25
26/// Builder for one data-only transaction script step.
27pub struct DataScriptStepBuilder<'script, Operation> {
28    script: &'script mut DataTransactionScript<Operation>,
29    name: String,
30    operations: Vec<Operation>,
31}
32
33/// A versioned structural trace file for a recorded scenario.
34#[derive(Clone, Debug, Eq, PartialEq)]
35#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
36#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
37pub struct SerializedScenario {
38    format_version: u32,
39    steps: Vec<SerializedScenarioStep>,
40}
41
42/// One named transaction trace in a versioned scenario trace file.
43#[derive(Clone, Debug, Eq, PartialEq)]
44#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
45#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
46pub struct SerializedScenarioStep {
47    /// Human-readable step name.
48    pub name: String,
49    /// Structural transaction trace for this step.
50    pub trace: TransactionTrace,
51}
52
53impl<Operation> DataTransactionScript<Operation> {
54    /// Creates an empty versioned data script.
55    pub fn new() -> Self {
56        Self {
57            format_version: TRACE_FORMAT_VERSION,
58            steps: Vec::new(),
59        }
60    }
61
62    /// Starts a named data script step.
63    pub fn step(&mut self, name: impl Into<String>) -> DataScriptStepBuilder<'_, Operation> {
64        DataScriptStepBuilder {
65            script: self,
66            name: name.into(),
67            operations: Vec::new(),
68        }
69    }
70
71    /// Returns the serialized format version.
72    pub fn format_version(&self) -> u32 {
73        self.format_version
74    }
75
76    /// Returns script steps in replay order.
77    pub fn steps(&self) -> &[DataScriptStep<Operation>] {
78        &self.steps
79    }
80
81    /// Fails when the script uses an unsupported format version.
82    pub fn validate_format_version(&self) -> Result<(), ScenarioError> {
83        validate_format_version(self.format_version)
84    }
85
86    /// Serializes this data script to pretty JSON.
87    #[cfg(feature = "serde")]
88    pub fn to_json(&self) -> Result<String, serde_json::Error>
89    where
90        Operation: serde::Serialize,
91    {
92        serde_json::to_string_pretty(self)
93    }
94
95    /// Deserializes a data script from JSON.
96    #[cfg(feature = "serde")]
97    pub fn from_json(json: &str) -> Result<Self, serde_json::Error>
98    where
99        Operation: serde::de::DeserializeOwned,
100    {
101        serde_json::from_str(json)
102    }
103}
104
105impl<Operation> Default for DataTransactionScript<Operation> {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl<Operation> DataScriptStep<Operation> {
112    /// Returns the step name.
113    pub fn name(&self) -> &str {
114        &self.name
115    }
116
117    /// Returns data operations in staging order.
118    pub fn operations(&self) -> &[Operation] {
119        &self.operations
120    }
121}
122
123impl<Operation> DataScriptStepBuilder<'_, Operation> {
124    /// Adds one app-defined data operation to this step.
125    pub fn operation(mut self, operation: Operation) -> Self {
126        self.operations.push(operation);
127        self
128    }
129
130    /// Adds this step to the script.
131    pub fn commit(self) {
132        self.script.steps.push(DataScriptStep {
133            name: self.name,
134            operations: self.operations,
135        });
136    }
137}
138
139impl SerializedScenario {
140    /// Captures a scenario as a versioned structural trace file.
141    pub fn from_scenario(scenario: &Scenario) -> Self {
142        Self {
143            format_version: TRACE_FORMAT_VERSION,
144            steps: scenario
145                .steps()
146                .iter()
147                .map(|step| SerializedScenarioStep {
148                    name: step.name.clone(),
149                    trace: step.trace.clone(),
150                })
151                .collect(),
152        }
153    }
154
155    /// Returns the serialized format version.
156    pub fn format_version(&self) -> u32 {
157        self.format_version
158    }
159
160    /// Returns trace steps in commit order.
161    pub fn steps(&self) -> &[SerializedScenarioStep] {
162        &self.steps
163    }
164
165    /// Rebuilds a scenario recorder from this versioned trace file.
166    pub fn into_scenario(self) -> Result<Scenario, ScenarioError> {
167        validate_format_version(self.format_version)?;
168        let mut scenario = Scenario::new();
169        for step in self.steps {
170            scenario.record_trace(step.name, step.trace)?;
171        }
172        Ok(scenario)
173    }
174
175    /// Checks this trace file against a freshly replayed scenario.
176    pub fn assert_matches_scenario(&self, actual: &Scenario) -> Result<(), ScenarioError> {
177        self.clone().into_scenario()?.assert_replay_matches(actual)
178    }
179
180    /// Serializes this trace file to pretty JSON.
181    #[cfg(feature = "serde")]
182    pub fn to_json(&self) -> Result<String, serde_json::Error> {
183        serde_json::to_string_pretty(self)
184    }
185
186    /// Deserializes a versioned trace file from JSON.
187    #[cfg(feature = "serde")]
188    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
189        serde_json::from_str(json)
190    }
191}
192
193fn validate_format_version(actual: u32) -> Result<(), ScenarioError> {
194    if actual == TRACE_FORMAT_VERSION {
195        Ok(())
196    } else {
197        Err(ScenarioError::TraceFormatVersionMismatch {
198            expected: TRACE_FORMAT_VERSION,
199            actual,
200        })
201    }
202}