telltale_runtime/effects/registry.rs
1//! Extension handler registry for type-safe extension dispatch
2
3use crate::effects::extension::{ExtensionEffect, ExtensionError};
4use crate::effects::{
5 contract::{ExtensionDispatchContract, ExtensionDispatchMode},
6 Endpoint, RoleId,
7};
8use std::any::TypeId;
9use std::collections::HashMap;
10use std::future::Future;
11use std::pin::Pin;
12
13/// Boxed future for async extension handlers
14pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
15
16/// Handler function type for extensions
17///
18/// Extension handlers receive:
19/// - `&mut Endpoint`: Mutable endpoint for state/communication
20/// - `&dyn ExtensionEffect`: The extension to handle (must downcast)
21///
22/// Handlers return `Result<(), ExtensionError>` and must handle
23/// their extension type or return an error.
24pub type ExtensionHandler<E, R> = Box<
25 dyn for<'a> Fn(
26 &'a mut E,
27 &'a dyn ExtensionEffect<R>,
28 ) -> BoxFuture<'a, Result<(), ExtensionError>>
29 + Send
30 + Sync,
31>;
32
33/// Registry of extension handlers for a choreography handler
34///
35/// Handlers must register extension types they support. Unregistered
36/// extensions cause runtime errors (fail-fast behavior).
37/// The machine-checkable dispatch contract is available through
38/// [`ExtensionRegistry::dispatch_contract`].
39///
40/// # Example
41///
42/// ```text
43/// let mut registry = ExtensionRegistry::new();
44/// registry.register::<ValidateCapability, _>(|ep, ext| {
45/// Box::pin(async move {
46/// let validate = ext.as_any()
47/// .downcast_ref::<ValidateCapability>()
48/// .ok_or_else(|| ExtensionError::TypeMismatch {
49/// expected: "ValidateCapability",
50/// actual: ext.type_name(),
51/// })?;
52///
53/// // Validate capability logic here
54/// ep.check_capability(&validate.capability)?;
55/// Ok(())
56/// })
57/// });
58/// ```
59pub struct ExtensionRegistry<E: Endpoint, R: RoleId> {
60 handlers: HashMap<TypeId, (ExtensionHandler<E, R>, &'static str)>,
61}
62
63impl<E: Endpoint, R: RoleId> ExtensionRegistry<E, R> {
64 /// Create a new empty extension registry
65 #[must_use]
66 pub fn new() -> Self {
67 Self {
68 handlers: HashMap::new(),
69 }
70 }
71
72 /// Register a handler for a specific extension type
73 ///
74 /// # Type Safety
75 ///
76 /// The handler receives `&dyn ExtensionEffect` and must downcast to `Ext`.
77 /// The registry ensures the handler is only called for `Ext` instances.
78 ///
79 /// # Errors
80 ///
81 /// Returns `ExtensionError::DuplicateHandler` if a handler is already registered for type `Ext`.
82 pub fn register<Ext, F>(&mut self, handler: F) -> Result<(), ExtensionError>
83 where
84 Ext: ExtensionEffect<R> + 'static,
85 F: for<'a> Fn(
86 &'a mut E,
87 &'a dyn ExtensionEffect<R>,
88 ) -> BoxFuture<'a, Result<(), ExtensionError>>
89 + Send
90 + Sync
91 + 'static,
92 {
93 let type_id = TypeId::of::<Ext>();
94 let type_name = std::any::type_name::<Ext>();
95 if self.handlers.contains_key(&type_id) {
96 return Err(ExtensionError::DuplicateHandler { type_name });
97 }
98 self.handlers
99 .insert(type_id, (Box::new(handler), type_name));
100 Ok(())
101 }
102
103 /// Machine-checkable dispatch contract for all extension registries.
104 #[must_use]
105 pub fn dispatch_contract() -> ExtensionDispatchContract {
106 ExtensionDispatchContract {
107 mode: ExtensionDispatchMode::RegisteredOnlyTypeExact,
108 fail_closed_when_unregistered: true,
109 type_exact_before_side_effects: true,
110 }
111 }
112
113 /// Handle an extension effect
114 ///
115 /// # Errors
116 ///
117 /// Returns `ExtensionError::HandlerNotRegistered` if no handler exists
118 /// for the extension's type. This enforces fail-fast behavior for
119 /// unknown extensions.
120 pub async fn handle(
121 &self,
122 endpoint: &mut E,
123 extension: &dyn ExtensionEffect<R>,
124 ) -> Result<(), ExtensionError> {
125 let type_id = extension.type_id();
126
127 match self.handlers.get(&type_id) {
128 Some((handler, _type_name)) => handler(endpoint, extension).await,
129 None => Err(ExtensionError::HandlerNotRegistered {
130 type_name: extension.type_name(),
131 }),
132 }
133 }
134
135 /// Check if a handler is registered for an extension type
136 #[must_use]
137 pub fn is_registered<Ext: ExtensionEffect<R> + 'static>(&self) -> bool {
138 self.handlers.contains_key(&TypeId::of::<Ext>())
139 }
140
141 /// Number of registered extension handlers.
142 #[must_use]
143 pub fn registered_handler_count(&self) -> usize {
144 self.handlers.len()
145 }
146
147 /// Merge another registry into this one
148 ///
149 /// # Errors
150 ///
151 /// Returns `ExtensionError::MergeConflict` if there are overlapping extension types.
152 pub fn merge(&mut self, other: ExtensionRegistry<E, R>) -> Result<(), ExtensionError> {
153 for (type_id, (handler, type_name)) in other.handlers {
154 if self.handlers.contains_key(&type_id) {
155 return Err(ExtensionError::MergeConflict { type_name });
156 }
157 self.handlers.insert(type_id, (handler, type_name));
158 }
159 Ok(())
160 }
161}
162
163impl<E: Endpoint, R: RoleId> Default for ExtensionRegistry<E, R> {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169/// Trait for handlers that support extensions
170///
171/// This trait provides access to the extension registry. Handlers
172/// should populate the registry during construction.
173pub trait ExtensibleHandler: crate::effects::ChoreoHandler {
174 /// Get the extension registry for this handler
175 ///
176 /// The interpreter uses this to dispatch extension effects.
177 fn extension_registry(&self) -> &ExtensionRegistry<Self::Endpoint, Self::Role>;
178}