Skip to main content

telltale_runtime/effects/handlers/
recording.rs

1// Recording effect handler for testing
2//
3// Captures all choreographic effects for verification and testing.
4// Does not produce actual values - use for protocol structure testing only.
5
6use async_trait::async_trait;
7use serde::{de::DeserializeOwned, Serialize};
8use std::time::Duration;
9
10use crate::effects::{ChoreoHandler, ChoreoResult, ChoreographyError, RoleId};
11
12/// Recording handler for testing - captures all effects for verification
13#[derive(Clone)]
14pub struct RecordingHandler<R: RoleId> {
15    pub events: std::sync::Arc<std::sync::Mutex<Vec<RecordedEvent<R>>>>,
16    role: R,
17}
18
19#[derive(Debug, Clone)]
20pub enum RecordedEvent<R: RoleId> {
21    Send { from: R, to: R, msg_type: String },
22    Recv { from: R, to: R, msg_type: String },
23    Choose { at: R, label: <R as RoleId>::Label },
24    Offer { from: R, to: R },
25}
26
27impl<R: RoleId> RecordingHandler<R> {
28    pub fn new(role: R) -> Self {
29        Self {
30            events: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
31            role,
32        }
33    }
34
35    pub fn events(&self) -> Vec<RecordedEvent<R>> {
36        self.events
37            .lock()
38            .unwrap_or_else(std::sync::PoisonError::into_inner)
39            .clone()
40    }
41
42    pub fn clear(&self) {
43        self.events
44            .lock()
45            .unwrap_or_else(std::sync::PoisonError::into_inner)
46            .clear();
47    }
48}
49
50#[async_trait]
51impl<R: RoleId + 'static> ChoreoHandler for RecordingHandler<R> {
52    type Role = R;
53    type Endpoint = ();
54
55    async fn send<M: Serialize + Send + Sync>(
56        &mut self,
57        _ep: &mut Self::Endpoint,
58        to: Self::Role,
59        _msg: &M,
60    ) -> ChoreoResult<()> {
61        self.events
62            .lock()
63            .unwrap_or_else(std::sync::PoisonError::into_inner)
64            .push(RecordedEvent::Send {
65                from: self.role,
66                to,
67                msg_type: std::any::type_name::<M>().to_string(),
68            });
69        Ok(())
70    }
71
72    async fn recv<M: DeserializeOwned + Send>(
73        &mut self,
74        _ep: &mut Self::Endpoint,
75        from: Self::Role,
76    ) -> ChoreoResult<M> {
77        self.events
78            .lock()
79            .unwrap_or_else(std::sync::PoisonError::into_inner)
80            .push(RecordedEvent::Recv {
81                from,
82                to: self.role,
83                msg_type: std::any::type_name::<M>().to_string(),
84            });
85        Err(ChoreographyError::Transport(
86            "RecordingHandler cannot produce values".into(),
87        ))
88    }
89
90    async fn choose(
91        &mut self,
92        _ep: &mut Self::Endpoint,
93        at: Self::Role,
94        label: <Self::Role as RoleId>::Label,
95    ) -> ChoreoResult<()> {
96        self.events
97            .lock()
98            .unwrap_or_else(std::sync::PoisonError::into_inner)
99            .push(RecordedEvent::Choose { at, label });
100        Ok(())
101    }
102
103    async fn offer(
104        &mut self,
105        _ep: &mut Self::Endpoint,
106        from: Self::Role,
107    ) -> ChoreoResult<<Self::Role as RoleId>::Label> {
108        self.events
109            .lock()
110            .unwrap_or_else(std::sync::PoisonError::into_inner)
111            .push(RecordedEvent::Offer {
112                from,
113                to: self.role,
114            });
115        Err(ChoreographyError::Transport(
116            "RecordingHandler cannot produce labels".into(),
117        ))
118    }
119
120    async fn with_timeout<F, T>(
121        &mut self,
122        _ep: &mut Self::Endpoint,
123        _at: Self::Role,
124        _dur: Duration,
125        body: F,
126    ) -> ChoreoResult<T>
127    where
128        F: std::future::Future<Output = ChoreoResult<T>> + Send,
129    {
130        body.await
131    }
132}