Skip to main content

telltale_choreography/runtime/
adapter.rs

1//! Choreographic Adapter Trait
2//!
3//! This module provides the `ChoreographicAdapter` trait, a simplified interface
4//! for executing choreographic protocols. Unlike `ChoreoHandler` which is designed
5//! for the effect interpreter, `ChoreographicAdapter` is designed for direct use
6//! by generated `run_{role}` functions.
7//!
8//! # Design Goals
9//!
10//! - Simpler interface than `ChoreoHandler` (no endpoint parameter threading)
11//! - Generic over message types with `Message` trait bound
12//! - Support for broadcast/collect patterns for indexed roles
13//! - Easy integration with existing transport implementations
14//!
15//! # Example
16//!
17//! ```ignore
18//! use telltale_choreography::runtime::ChoreographicAdapter;
19//!
20//! struct MyAdapter { /* ... */ }
21//!
22//! #[async_trait]
23//! impl ChoreographicAdapter for MyAdapter {
24//!     type Error = MyError;
25//!     type Role = MyRole;
26//!
27//!     async fn send<M: Message>(&mut self, to: MyRole, msg: M) -> Result<(), Self::Error> {
28//!         // Send implementation
29//!     }
30//!
31//!     async fn recv<M: Message>(&mut self, from: MyRole) -> Result<M, Self::Error> {
32//!         // Receive implementation
33//!     }
34//! }
35//! ```
36
37use async_trait::async_trait;
38use serde::{de::DeserializeOwned, Deserialize, Serialize};
39use std::fmt::Debug;
40
41use crate::effects::{LabelId, RoleId};
42use crate::identifiers::RoleName;
43
44/// Trait for message types that can be sent/received in a choreography.
45///
46/// Messages must be serializable, deserializable, sendable between threads,
47/// and debuggable for tracing purposes.
48pub trait Message: Serialize + DeserializeOwned + Send + Sync + Debug + 'static {}
49
50/// Blanket implementation for all types satisfying the bounds.
51impl<T: Serialize + DeserializeOwned + Send + Sync + Debug + 'static> Message for T {}
52
53/// The core adapter trait for choreographic protocol execution.
54///
55/// This trait abstracts the communication primitives needed to execute
56/// a choreographic protocol. Implementations can provide different
57/// transport mechanisms (channels, network, simulated, etc.).
58///
59/// # Type Parameters
60///
61/// The trait is generic over the error type, allowing implementations
62/// to use their own error types.
63///
64/// # Async Safety
65///
66/// All methods are async and the trait requires `Send`, making it
67/// compatible with multi-threaded runtimes.
68#[async_trait]
69pub trait ChoreographicAdapter: Send {
70    /// The error type for this adapter.
71    type Error: std::error::Error + Send + Sync + 'static;
72    /// The role identifier type for this adapter.
73    type Role: RoleId;
74
75    /// Send a message to a specific role.
76    ///
77    /// # Arguments
78    ///
79    /// * `to` - The recipient role
80    /// * `msg` - The message to send
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if the send fails (transport error, serialization, etc.)
85    async fn send<M: Message>(&mut self, to: Self::Role, msg: M) -> Result<(), Self::Error>;
86
87    /// Receive a message from a specific role.
88    ///
89    /// # Arguments
90    ///
91    /// * `from` - The sender role
92    ///
93    /// # Returns
94    ///
95    /// The received message, or an error if receive fails.
96    async fn recv<M: Message>(&mut self, from: Self::Role) -> Result<M, Self::Error>;
97
98    /// Broadcast a message to multiple roles.
99    ///
100    /// Default implementation sends sequentially. Override for parallel sending.
101    ///
102    /// # Arguments
103    ///
104    /// * `to` - The recipient roles
105    /// * `msg` - The message to send (cloned for each recipient)
106    async fn broadcast<M: Message + Clone>(
107        &mut self,
108        to: &[Self::Role],
109        msg: M,
110    ) -> Result<(), Self::Error> {
111        for role in to {
112            self.send(*role, msg.clone()).await?;
113        }
114        Ok(())
115    }
116
117    /// Collect messages from multiple roles.
118    ///
119    /// Default implementation receives sequentially. Override for parallel receiving.
120    ///
121    /// # Arguments
122    ///
123    /// * `from` - The sender roles
124    ///
125    /// # Returns
126    ///
127    /// A vector of messages in the order of the `from` roles.
128    async fn collect<M: Message>(&mut self, from: &[Self::Role]) -> Result<Vec<M>, Self::Error> {
129        let mut messages = Vec::with_capacity(from.len());
130        for role in from {
131            let msg = self.recv::<M>(*role).await?;
132            messages.push(msg);
133        }
134        Ok(messages)
135    }
136
137    /// Send a choice label to a role.
138    ///
139    /// Used for internal choice (Select) - the choosing role broadcasts
140    /// which branch was selected.
141    ///
142    /// # Arguments
143    ///
144    /// * `to` - The role to inform of the choice
145    /// * `label` - The selected branch label
146    async fn choose(
147        &mut self,
148        to: Self::Role,
149        label: <Self::Role as RoleId>::Label,
150    ) -> Result<(), Self::Error> {
151        self.send(to, ChoiceLabel(label)).await
152    }
153
154    /// Receive a choice label from a role.
155    ///
156    /// Used for external choice (Branch) - receive which branch the
157    /// choosing role selected.
158    ///
159    /// # Arguments
160    ///
161    /// * `from` - The role that made the choice
162    ///
163    /// # Returns
164    ///
165    /// The label of the selected branch.
166    async fn offer(
167        &mut self,
168        from: Self::Role,
169    ) -> Result<<Self::Role as RoleId>::Label, Self::Error> {
170        let choice: ChoiceLabel<<Self::Role as RoleId>::Label> = self.recv(from).await?;
171        Ok(choice.0)
172    }
173
174    /// Resolve all instances of a parameterized role family.
175    ///
176    /// This method is used to resolve wildcards like `Worker[*]` to concrete
177    /// role instances. The default implementation returns an error indicating
178    /// that role families are not supported.
179    ///
180    /// # Arguments
181    ///
182    /// * `family` - The role family name (e.g., "Worker" for `Worker[*]`)
183    ///
184    /// # Returns
185    ///
186    /// A vector of role instances belonging to the family.
187    ///
188    /// # Example
189    ///
190    /// ```ignore
191    /// // For a protocol with `Witness[*]`
192    /// let witnesses = adapter.resolve_family("Witness")?;
193    /// adapter.broadcast(&witnesses, msg).await?;
194    /// ```
195    fn resolve_family(&self, family: &str) -> Result<Vec<Self::Role>, Self::Error>;
196
197    /// Resolve a range of role instances [start, end).
198    ///
199    /// This method is used to resolve ranges like `Worker[0..3]` to concrete
200    /// role instances. The default implementation returns an error indicating
201    /// that role ranges are not supported.
202    ///
203    /// # Arguments
204    ///
205    /// * `family` - The role family name (e.g., "Worker")
206    /// * `start` - The start index (inclusive)
207    /// * `end` - The end index (exclusive)
208    ///
209    /// # Returns
210    ///
211    /// A vector of role instances in the range [start, end).
212    ///
213    /// # Example
214    ///
215    /// ```ignore
216    /// // For a protocol with `Witness[0..3]`
217    /// let witnesses = adapter.resolve_range("Witness", 0, 3)?;
218    /// adapter.broadcast(&witnesses, msg).await?;
219    /// ```
220    fn resolve_range(
221        &self,
222        family: &str,
223        start: u32,
224        end: u32,
225    ) -> Result<Vec<Self::Role>, Self::Error>;
226
227    /// Get the total count of instances in a role family.
228    ///
229    /// This is useful for validating constraints like minimum participant counts.
230    ///
231    /// # Arguments
232    ///
233    /// * `family` - The role family name
234    ///
235    /// # Returns
236    ///
237    /// The number of role instances in the family.
238    fn family_size(&self, family: &str) -> Result<usize, Self::Error> {
239        self.resolve_family(family).map(|v| v.len())
240    }
241}
242
243/// A choice label message for internal/external choice communication.
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
245pub struct ChoiceLabel<L: LabelId>(pub L);
246
247impl<L: LabelId> Serialize for ChoiceLabel<L> {
248    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
249    where
250        S: serde::Serializer,
251    {
252        serializer.serialize_str(self.0.as_str())
253    }
254}
255
256impl<'de, L: LabelId> Deserialize<'de> for ChoiceLabel<L> {
257    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
258    where
259        D: serde::Deserializer<'de>,
260    {
261        let label = String::deserialize(deserializer)?;
262        L::from_str(&label)
263            .map(ChoiceLabel)
264            .ok_or_else(|| serde::de::Error::custom("Unknown choice label"))
265    }
266}
267
268/// Extension trait for adapters with lifecycle management.
269#[async_trait]
270pub trait ChoreographicAdapterExt: ChoreographicAdapter {
271    /// Called before protocol execution starts.
272    async fn setup(&mut self) -> Result<(), Self::Error>;
273
274    /// Called after protocol execution completes.
275    async fn teardown(&mut self) -> Result<(), Self::Error>;
276}
277
278/// Context information passed to generated runner functions.
279///
280/// Contains metadata about the protocol and role being executed.
281#[derive(Debug, Clone)]
282pub struct ProtocolContext {
283    /// Name of the protocol being executed
284    pub protocol: &'static str,
285    /// Name of the role being executed
286    pub role: RoleName,
287    /// Optional role index for parameterized roles
288    pub index: Option<u32>,
289}
290
291impl ProtocolContext {
292    /// Create a new protocol context.
293    #[must_use]
294    pub fn new(protocol: &'static str, role: RoleName) -> Self {
295        Self {
296            protocol,
297            role,
298            index: None,
299        }
300    }
301
302    /// Create a new indexed protocol context.
303    #[must_use]
304    pub fn indexed(protocol: &'static str, role: RoleName, index: u32) -> Self {
305        Self {
306            protocol,
307            role,
308            index: Some(index),
309        }
310    }
311
312    /// Create a context from a role identifier.
313    #[must_use]
314    pub fn for_role<R: RoleId>(protocol: &'static str, role: R) -> Self {
315        Self {
316            protocol,
317            role: role.role_name(),
318            index: role.role_index(),
319        }
320    }
321}
322
323/// Output from protocol execution.
324///
325/// Wraps the return value with optional metadata.
326#[derive(Debug)]
327pub struct ProtocolOutput<T> {
328    /// The result value from protocol execution
329    pub value: T,
330    /// Optional execution metadata
331    pub metadata: Option<ExecutionMetadata>,
332}
333
334impl<T> ProtocolOutput<T> {
335    /// Create a new protocol output with just a value.
336    pub fn new(value: T) -> Self {
337        Self {
338            value,
339            metadata: None,
340        }
341    }
342
343    /// Create a new protocol output with metadata.
344    pub fn with_metadata(value: T, metadata: ExecutionMetadata) -> Self {
345        Self {
346            value,
347            metadata: Some(metadata),
348        }
349    }
350}
351
352/// Metadata about protocol execution.
353#[derive(Debug, Default)]
354pub struct ExecutionMetadata {
355    /// Number of messages sent
356    pub messages_sent: usize,
357    /// Number of messages received
358    pub messages_received: usize,
359    /// Execution duration in milliseconds
360    pub duration_ms: Option<u64>,
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn test_role_id_display() {
369        #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
370        enum TestRole {
371            Client,
372            Witness(u32),
373        }
374
375        #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
376        enum TestLabel {
377            Ping,
378        }
379
380        impl LabelId for TestLabel {
381            fn as_str(&self) -> &'static str {
382                match self {
383                    TestLabel::Ping => "Ping",
384                }
385            }
386
387            fn from_str(label: &str) -> Option<Self> {
388                match label {
389                    "Ping" => Some(TestLabel::Ping),
390                    _ => None,
391                }
392            }
393        }
394
395        impl RoleId for TestRole {
396            type Label = TestLabel;
397
398            fn role_name(&self) -> RoleName {
399                match self {
400                    TestRole::Client => RoleName::from_static("Client"),
401                    TestRole::Witness(_) => RoleName::from_static("Witness"),
402                }
403            }
404
405            fn role_index(&self) -> Option<u32> {
406                match self {
407                    TestRole::Witness(index) => Some(*index),
408                    _ => None,
409                }
410            }
411        }
412
413        let static_role = TestRole::Client;
414        assert_eq!(static_role.role_name().as_str(), "Client");
415
416        let indexed_role = TestRole::Witness(2);
417        assert_eq!(indexed_role.role_name().as_str(), "Witness");
418        assert_eq!(indexed_role.role_index(), Some(2));
419    }
420
421    #[test]
422    fn test_protocol_context() {
423        let ctx = ProtocolContext::new("TwoBuyer", RoleName::from_static("Buyer1"));
424        assert_eq!(ctx.protocol, "TwoBuyer");
425        assert_eq!(ctx.role.as_str(), "Buyer1");
426        assert!(ctx.index.is_none());
427
428        let indexed_ctx =
429            ProtocolContext::indexed("Broadcast", RoleName::from_static("Witness"), 0);
430        assert_eq!(indexed_ctx.index, Some(0));
431    }
432}