Skip to main content

telltale_runtime/effects/
extension.rs

1//! Type-safe extension system for choreographic effects
2//!
3//! Extensions enable domain-specific effects (capabilities, budgets, logging, error handling)
4//! while maintaining full type safety and algebraic effects composition.
5
6use std::any::{Any, TypeId};
7use std::fmt::Debug;
8
9use crate::effects::RoleId;
10
11/// Trait for type-safe extension effects
12///
13/// Extension effects are domain-specific effects that extend the core
14/// choreography effect system. Each extension defines its own data type
15/// and semantics while participating in the full effect lifecycle.
16///
17/// # Type Safety
18///
19/// Extensions are identified by `TypeId`, ensuring compile-time type safety
20/// and preventing string-based errors. The type `Self` is the extension's
21/// payload and must be clonable for effect algebra operations.
22///
23/// # Projection Semantics
24///
25/// Extensions must specify which roles participate via `participating_roles()`.
26/// Only those roles will see the extension in their projected local types.
27/// Extensions that return an empty vec are included in all role projections.
28///
29/// # Example
30///
31/// ```text
32/// #[derive(Clone, Debug)]
33/// pub struct ValidateCapability<R: RoleId> {
34///     pub capability: String,
35///     pub role: R,
36/// }
37///
38/// impl<R: RoleId> ExtensionEffect<R> for ValidateCapability<R> {
39///     fn type_id(&self) -> TypeId {
40///         TypeId::of::<Self>()
41///     }
42///
43///     fn type_name(&self) -> &'static str {
44///         "ValidateCapability"
45///     }
46///
47///     fn participating_roles(&self) -> Vec<R> {
48///         vec![self.role]
49///     }
50///
51///     fn as_any(&self) -> &dyn Any {
52///         self
53///     }
54///
55///     fn as_any_mut(&mut self) -> &mut dyn Any {
56///         self
57///     }
58///
59///     fn clone_box(&self) -> Box<dyn ExtensionEffect<R>> {
60///         Box::new(self.clone())
61///     }
62/// }
63/// ```
64pub trait ExtensionEffect<R: RoleId>: Send + Sync + Debug {
65    /// Get the TypeId of this extension type
66    ///
67    /// This is used for type-safe downcasting and extension discrimination.
68    /// Typically implemented as `TypeId::of::<Self>()`.
69    fn type_id(&self) -> TypeId;
70
71    /// Get a human-readable name for this extension type
72    ///
73    /// Used in error messages and debugging. Should be the type name.
74    fn type_name(&self) -> &'static str;
75
76    /// Get the roles that participate in this extension effect
77    ///
78    /// # Projection Semantics
79    ///
80    /// Returns a vector of roles participating in the effect.
81    ///
82    /// - Empty vec: Extension appears in all role projections (global effect)
83    /// - Non-empty: Extension appears only in specified role projections
84    fn participating_roles(&self) -> Vec<R> {
85        vec![] // Default: global extensions
86    }
87
88    /// Downcast to `&dyn Any` for type-safe casting
89    fn as_any(&self) -> &dyn Any;
90
91    /// Downcast to `&mut dyn Any` for type-safe mutable casting
92    fn as_any_mut(&mut self) -> &mut dyn Any;
93
94    /// Clone this extension into a boxed trait object
95    ///
96    /// Required for cloning `Effect` enum which contains `Box<dyn ExtensionEffect>`.
97    fn clone_box(&self) -> Box<dyn ExtensionEffect<R>>;
98}
99
100/// Extension-specific errors
101#[derive(Debug, Clone, thiserror::Error)]
102pub enum ExtensionError {
103    #[error("Unknown extension type: {type_name} (TypeId: {type_id:?})")]
104    UnknownExtension {
105        type_name: &'static str,
106        type_id: TypeId,
107    },
108
109    #[error("Extension handler not registered for {type_name}")]
110    HandlerNotRegistered { type_name: &'static str },
111
112    #[error("Extension {type_name} failed: {error}")]
113    ExecutionFailed {
114        type_name: &'static str,
115        error: String,
116    },
117
118    #[error("Type mismatch: expected {expected}, got {actual}")]
119    TypeMismatch {
120        expected: &'static str,
121        actual: &'static str,
122    },
123
124    #[error("Handler already registered for extension type: {type_name}")]
125    DuplicateHandler { type_name: &'static str },
126
127    #[error("Cannot merge registries: duplicate handler for {type_name}")]
128    MergeConflict { type_name: &'static str },
129}