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}