Skip to main content

cpex_core/hooks/
adapter.rs

1// Location: ./crates/cpex-core/src/hooks/adapter.rs
2// Copyright 2025
3// SPDX-License-Identifier: Apache-2.0
4// Authors: Teryl Taylor
5//
6// TypedHandlerAdapter — bridges typed HookHandler<H> to the
7// type-erased AnyHookHandler.
8//
9// This is framework plumbing that plugin authors never see. When a
10// plugin is registered via `manager.register_handler::<H, P>()`, the
11// manager creates a TypedHandlerAdapter internally. The adapter
12// translates between Box<dyn PluginPayload> (what the executor passes)
13// and the concrete payload type (what the handler expects), and awaits
14// the typed handler's future before re-erasing the result.
15//
16// `HookHandler<H>` is async-by-default (native AFIT). Plugins that
17// don't await anything still write `async fn handle(...)`; the
18// compiler emits a trivially-ready future that LLVM inlines, so the
19// `.await` here is a no-op for sync-style plugins.
20
21use std::marker::PhantomData;
22use std::sync::Arc;
23
24use crate::context::PluginContext;
25use crate::error::PluginError;
26use crate::executor::erase_result;
27use crate::hooks::payload::{Extensions, PluginPayload};
28use crate::hooks::trait_def::{HookHandler, HookTypeDef, PluginResult};
29use crate::plugin::Plugin;
30use crate::registry::AnyHookHandler;
31
32// ---------------------------------------------------------------------------
33// Typed Handler Adapter
34// ---------------------------------------------------------------------------
35
36/// Adapts a typed `HookHandler<H>` into the type-erased `AnyHookHandler`
37/// interface used by the executor.
38///
39/// Created automatically by `PluginManager::register_handler()`. Plugin
40/// authors never instantiate this directly.
41///
42/// `HookHandler<H>` is async (native AFIT), so the adapter awaits the
43/// returned future before re-erasing the result. Plugins that don't
44/// `.await` anything compile to a ready future that LLVM inlines, so
45/// they pay no observable cost over a plain function call.
46///
47/// # Type Parameters
48///
49/// - `H` — the hook type (implements `HookTypeDef`).
50/// - `P` — the plugin type (implements `Plugin + HookHandler<H>`).
51pub struct TypedHandlerAdapter<H, P>
52where
53    H: HookTypeDef,
54    H::Result: Into<PluginResult<H::Payload>>,
55    P: Plugin + HookHandler<H> + 'static,
56{
57    /// The plugin instance.
58    plugin: Arc<P>,
59
60    /// Phantom data to carry the hook type parameter.
61    _hook: PhantomData<H>,
62}
63
64impl<H, P> TypedHandlerAdapter<H, P>
65where
66    H: HookTypeDef,
67    H::Result: Into<PluginResult<H::Payload>>,
68    P: Plugin + HookHandler<H> + 'static,
69{
70    /// Create a new adapter wrapping the given plugin.
71    pub fn new(plugin: Arc<P>) -> Self {
72        Self {
73            plugin,
74            _hook: PhantomData,
75        }
76    }
77}
78
79#[async_trait::async_trait]
80impl<H, P> AnyHookHandler for TypedHandlerAdapter<H, P>
81where
82    H: HookTypeDef,
83    H::Result: Into<PluginResult<H::Payload>>,
84    P: Plugin + HookHandler<H> + 'static,
85{
86    /// Downcast the type-erased payload to the concrete type and call
87    /// the plugin's typed `handle()` method, awaiting the returned
88    /// future.
89    ///
90    /// The framework retains ownership of the payload — the handler
91    /// receives a borrow (`&H::Payload`) and clones only if it needs
92    /// to modify. The result is erased back to `ErasedResultFields`
93    /// for the executor.
94    ///
95    /// For plugins whose body contains no `.await`, the compiler emits
96    /// a trivially-ready future and LLVM inlines this `.await` to a
97    /// direct return — there is no observable runtime cost over a
98    /// plain function call.
99    async fn invoke(
100        &self,
101        payload: &dyn PluginPayload,
102        extensions: &Extensions,
103        ctx: &mut PluginContext,
104    ) -> Result<Box<dyn std::any::Any + Send + Sync>, Box<PluginError>> {
105        let typed_ref: &H::Payload =
106            payload
107                .as_any()
108                .downcast_ref::<H::Payload>()
109                .ok_or_else(|| PluginError::Config {
110                    message: format!(
111                        "payload type mismatch for hook '{}': expected {}",
112                        H::NAME,
113                        std::any::type_name::<H::Payload>()
114                    ),
115                })?;
116
117        let result = self.plugin.handle(typed_ref, extensions, ctx).await;
118        let plugin_result: PluginResult<H::Payload> = result.into();
119
120        Ok(erase_result(plugin_result))
121    }
122
123    fn hook_type_name(&self) -> &'static str {
124        H::NAME
125    }
126}