1use std::cell::{Cell, RefCell};
22use std::collections::VecDeque;
23use std::io;
24
25use serde::{de::DeserializeOwned, Deserialize, Serialize};
26use serde_json::Value;
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29#[serde(tag = "kind", rename_all = "snake_case")]
30pub enum Node {
31 Seed {
32 value: Value,
33 },
34 Action {
35 tick: Tick,
36 value: Value,
37 },
38 Request {
39 operation: String,
40 arguments: Value,
41 },
42 Call {
43 operation: String,
44 arguments: Value,
45 result: Value,
46 },
47 Check {
48 value: Value,
49 },
50 End,
51}
52
53#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
56pub struct Tick {
57 pub monotonic_ms: u64,
58 pub unix_seconds: i64,
59}
60
61enum Mode {
62 Live,
63 Replay(VecDeque<Node>),
64}
65
66pub struct Tape {
67 mode: RefCell<Mode>,
68 fault: Cell<Option<&'static str>>,
69 tick: Cell<Tick>,
70 finished: Cell<bool>,
71 started: std::time::Instant,
72 #[cfg(feature = "test-support")]
73 fixture: Option<Fixture>,
74}
75
76#[cfg(feature = "test-support")]
77type FixtureResponder = dyn Fn(&str, &Value) -> io::Result<Value>;
78
79#[cfg(feature = "test-support")]
80struct Fixture {
81 nodes: RefCell<Vec<Node>>,
82 respond: Box<FixtureResponder>,
83}
84
85impl Default for Tape {
86 fn default() -> Self {
87 Self::live()
88 }
89}
90
91impl Tape {
92 pub fn live() -> Self {
95 Self {
96 mode: RefCell::new(Mode::Live),
97 fault: Cell::new(None),
98 tick: Cell::new(Tick::default()),
99 finished: Cell::new(false),
100 started: std::time::Instant::now(),
101 #[cfg(feature = "test-support")]
102 fixture: None,
103 }
104 }
105
106 pub fn new() -> Self {
109 Self::live()
110 }
111
112 pub fn replay(nodes: Vec<Node>) -> Self {
114 Self {
115 mode: RefCell::new(Mode::Replay(nodes.into())),
116 ..Self::live()
117 }
118 }
119
120 #[cfg(feature = "test-support")]
124 pub fn fixture(respond: impl Fn(&str, &Value) -> io::Result<Value> + 'static) -> Self {
125 Self {
126 fixture: Some(Fixture {
127 nodes: RefCell::new(Vec::new()),
128 respond: Box::new(respond),
129 }),
130 ..Self::live()
131 }
132 }
133
134 #[cfg(feature = "test-support")]
136 pub fn fixture_nodes(&self) -> Vec<Node> {
137 self.fixture
138 .as_ref()
139 .expect("fixture recorder")
140 .nodes
141 .borrow()
142 .clone()
143 }
144
145 #[cfg(feature = "test-support")]
146 fn has_fixture(&self) -> bool {
147 self.fixture.is_some()
148 }
149
150 #[cfg(not(feature = "test-support"))]
151 fn has_fixture(&self) -> bool {
152 false
153 }
154
155 pub fn is_replay(&self) -> bool {
156 matches!(&*self.mode.borrow(), Mode::Replay(_))
157 }
158
159 pub fn observes(&self) -> bool {
161 self.is_replay() || self.captures()
162 }
163
164 pub fn sample_tick(&self) -> Tick {
167 if self.is_replay() || self.has_fixture() {
168 return self.now();
169 }
170 Tick {
171 monotonic_ms: u64::try_from(self.started.elapsed().as_millis())
172 .unwrap_or(u64::MAX)
173 .max(self.now().monotonic_ms),
174 unix_seconds: std::time::SystemTime::now()
175 .duration_since(std::time::UNIX_EPOCH)
176 .map_or(0, |duration| {
177 i64::try_from(duration.as_secs()).unwrap_or(i64::MAX)
178 }),
179 }
180 }
181
182 pub fn now(&self) -> Tick {
183 self.tick.get()
184 }
185
186 pub fn set_tick(&self, tick: Tick) -> io::Result<()> {
187 if tick.monotonic_ms < self.tick.get().monotonic_ms {
188 return self.fail("clock moved backwards");
189 }
190 self.tick.set(tick);
191 Ok(())
192 }
193
194 fn fail<T>(&self, message: &'static str) -> io::Result<T> {
195 if self.fault.get().is_none() {
196 self.fault.set(Some(message));
197 }
198 Err(io::Error::other(message))
199 }
200
201 pub fn healthy(&self) -> io::Result<()> {
203 match self.fault.get() {
204 Some(message) => Err(io::Error::other(message)),
205 None => Ok(()),
206 }
207 }
208
209 fn pop(&self) -> io::Result<Node> {
210 self.healthy()?;
211 let node = match &mut *self.mode.borrow_mut() {
212 Mode::Replay(nodes) => nodes.pop_front(),
213 Mode::Live => None,
214 };
215 node.ok_or_else(|| {
216 if self.fault.get().is_none() {
217 self.fault.set(Some("unexpected end of replay"));
218 }
219 io::Error::other("unexpected end of replay")
220 })
221 }
222
223 fn captures(&self) -> bool {
224 if self.has_fixture() {
225 return true;
226 }
227 crate::capture_content()
228 }
229
230 fn emit(&self, node: &Node) {
231 #[cfg(feature = "test-support")]
232 if let Some(fixture) = &self.fixture {
233 fixture.nodes.borrow_mut().push(node.clone());
234 return;
235 }
236 if crate::capture_content() {
237 crate::record(crate::EventKind::Replay, node);
238 }
239 }
240
241 fn value<T: Serialize>(&self, value: &T) -> io::Result<Value> {
244 let mut bytes = crate::bounded::Bytes::new(crate::MAX_RECORD_BYTES);
245 if serde_json::to_writer(&mut bytes, value).is_err() {
246 if self.is_replay() || self.has_fixture() {
247 return self.fail("replay value exceeds capture bound");
248 }
249 crate::mark_incomplete("forensic value exceeds cap or cannot serialize");
252 return Ok(Value::Null);
253 }
254 match serde_json::from_slice(&bytes.into_vec()) {
255 Ok(value) => Ok(value),
256 Err(_) => self.fail("encoded replay value cannot decode"),
257 }
258 }
259
260 fn decode<T: DeserializeOwned>(&self, value: Value) -> io::Result<T> {
261 serde_json::from_value(value).map_err(|_| {
262 if self.fault.get().is_none() {
263 self.fault.set(Some("replay payload does not decode"));
264 }
265 io::Error::other("replay payload does not decode")
266 })
267 }
268
269 pub fn seed<S: Serialize>(&self, seed: &S) -> io::Result<()> {
272 if self.is_replay() {
273 return self.fail("live seed entered replay");
274 }
275 if self.finished.get() {
276 return self.fail("recording finished");
277 }
278 if self.captures() {
279 self.emit(&Node::Seed {
280 value: self.value(seed)?,
281 });
282 }
283 Ok(())
284 }
285
286 pub fn take_seed<S: DeserializeOwned>(&self) -> io::Result<S> {
288 match self.pop()? {
289 Node::Seed { value } => self.decode(value),
290 _ => self.fail("replay must start with seed"),
291 }
292 }
293
294 pub fn action<A: Serialize>(&self, tick: Tick, action: &A) -> io::Result<()> {
296 if self.is_replay() {
297 return self.fail("live action entered replay");
298 }
299 if self.finished.get() {
300 return self.fail("recording finished");
301 }
302 self.set_tick(tick)?;
303 if self.captures() {
304 self.emit(&Node::Action {
305 tick,
306 value: self.value(action)?,
307 });
308 }
309 Ok(())
310 }
311
312 pub fn next<A: DeserializeOwned>(&self) -> io::Result<Option<A>> {
315 match self.pop()? {
316 Node::Action { tick, value } => {
317 self.set_tick(tick)?;
318 self.decode(value).map(Some)
319 }
320 Node::End => {
321 let empty = matches!(&*self.mode.borrow(), Mode::Replay(nodes) if nodes.is_empty());
322 if !empty {
323 return self.fail("records after replay end");
324 }
325 Ok(None)
326 }
327 _ => self.fail("unconsumed replay observation"),
328 }
329 }
330
331 pub fn request<A: Serialize>(&self, operation: &str, arguments: &A) -> io::Result<bool> {
336 self.healthy()?;
337 if self.finished.get() && !self.is_replay() {
338 return self.fail("recording finished");
339 }
340 if !self.is_replay() && !self.captures() {
341 return Ok(true);
342 }
343 let arguments = self.value(arguments)?;
344 #[cfg(feature = "test-support")]
345 if self.fixture.is_some() {
346 self.emit(&Node::Request {
347 operation: operation.into(),
348 arguments,
349 });
350 return Ok(false);
351 }
352 if self.is_replay() {
353 match self.pop()? {
354 Node::Request {
355 operation: expected,
356 arguments: wanted,
357 } if expected == operation && wanted == arguments => Ok(false),
358 _ => self.fail("request identity or arguments diverged"),
359 }
360 } else {
361 self.emit(&Node::Request {
362 operation: operation.into(),
363 arguments,
364 });
365 Ok(true)
366 }
367 }
368
369 pub fn call<A: Serialize, R: Serialize + DeserializeOwned>(
374 &self,
375 operation: &str,
376 arguments: &A,
377 native: impl FnOnce() -> R,
378 ) -> io::Result<R> {
379 self.healthy()?;
380 if self.finished.get() && !self.is_replay() {
381 return self.fail("recording finished");
382 }
383 if !self.is_replay() && !self.captures() {
384 return Ok(native());
385 }
386 let arguments = self.value(arguments)?;
387 #[cfg(feature = "test-support")]
388 if let Some(fixture) = &self.fixture {
389 let result = match (fixture.respond)(operation, &arguments) {
390 Ok(result) => result,
391 Err(error) => {
392 if self.fault.get().is_none() {
393 self.fault.set(Some("fixture observation missing"));
394 }
395 return Err(error);
396 }
397 };
398 self.emit(&Node::Call {
399 operation: operation.into(),
400 arguments,
401 result: result.clone(),
402 });
403 return self.decode(result);
404 }
405 if self.is_replay() {
406 match self.pop()? {
407 Node::Call {
408 operation: expected,
409 arguments: wanted,
410 result,
411 } if expected == operation && wanted == arguments => self.decode(result),
412 _ => self.fail("synchronous service call diverged"),
413 }
414 } else {
415 let result = native();
416 self.emit(&Node::Call {
417 operation: operation.into(),
418 arguments,
419 result: self.value(&result)?,
420 });
421 Ok(result)
422 }
423 }
424
425 pub fn check<C: Serialize>(&self, state: &C) -> io::Result<()> {
427 self.healthy()?;
428 if self.finished.get() && !self.is_replay() {
429 return self.fail("recording finished");
430 }
431 if !self.is_replay() && !self.captures() {
432 return Ok(());
433 }
434 let value = self.value(state)?;
435 if self.is_replay() {
436 match self.pop()? {
437 Node::Check { value: expected } if value == expected => Ok(()),
438 _ => self.fail("editor state diverged"),
439 }
440 } else {
441 self.emit(&Node::Check { value });
442 Ok(())
443 }
444 }
445
446 pub fn finish(&self) -> io::Result<()> {
448 self.healthy()?;
449 if self.is_replay() {
450 return self.fail("live finish entered replay");
451 }
452 if self.finished.get() {
453 return self.fail("recording finished");
454 }
455 self.finished.set(true);
456 self.emit(&Node::End);
457 Ok(())
458 }
459}