Skip to main content

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}