Skip to main content

botkit_core/
handler.rs

1#![allow(clippy::type_complexity)]
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use crate::context::Context;
7use crate::extractor::FromContext;
8use crate::responder::IntoResponse;
9use crate::response::Response;
10
11#[cfg(not(target_arch = "wasm32"))]
12pub type HandlerCallFuture<'a> = Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
13#[cfg(target_arch = "wasm32")]
14pub type HandlerCallFuture<'a> = Pin<Box<dyn Future<Output = Response> + 'a>>;
15
16#[cfg(not(target_arch = "wasm32"))]
17pub trait HandlerBounds: Send + Sync {}
18#[cfg(not(target_arch = "wasm32"))]
19impl<T: Send + Sync + ?Sized> HandlerBounds for T {}
20
21#[cfg(target_arch = "wasm32")]
22pub trait HandlerBounds {}
23#[cfg(target_arch = "wasm32")]
24impl<T: ?Sized> HandlerBounds for T {}
25
26#[cfg(not(target_arch = "wasm32"))]
27pub trait HandlerFnBounds: Send + Sync {}
28#[cfg(not(target_arch = "wasm32"))]
29impl<T: Send + Sync + ?Sized> HandlerFnBounds for T {}
30
31#[cfg(target_arch = "wasm32")]
32pub trait HandlerFnBounds {}
33#[cfg(target_arch = "wasm32")]
34impl<T: ?Sized> HandlerFnBounds for T {}
35
36#[cfg(not(target_arch = "wasm32"))]
37pub trait HandlerFutureBounds: Future + Send {}
38#[cfg(not(target_arch = "wasm32"))]
39impl<T: Future + Send + ?Sized> HandlerFutureBounds for T {}
40
41#[cfg(target_arch = "wasm32")]
42pub trait HandlerFutureBounds: Future {}
43#[cfg(target_arch = "wasm32")]
44impl<T: Future + ?Sized> HandlerFutureBounds for T {}
45
46/// Trait for bot event handlers
47///
48/// Handlers use the extractor/responder pattern - they extract typed
49/// data from context and return types that implement IntoResponse.
50///
51/// # Example
52/// ```ignore
53/// // No parameters
54/// async fn ping() -> &'static str {
55///     "Pong!"
56/// }
57///
58/// // With extractors
59/// async fn greet(user: User) -> String {
60///     format!("Hello, {}!", user.name)
61/// }
62///
63/// // Multiple extractors
64/// async fn info(user: User, channel: Channel) -> String {
65///     format!("User {} in channel {}", user.name, channel.id)
66/// }
67///
68/// // Full context access when needed
69/// async fn advanced(ctx: Context) -> Response {
70///     // ... complex logic
71///     Response::text("Done")
72/// }
73/// ```
74pub trait Handler: HandlerBounds + 'static {
75    /// Handle the event and produce a response
76    fn call(&self, ctx: Context) -> HandlerCallFuture<'_>;
77}
78
79/// Boxed handler for storage
80pub type BoxedHandler = Arc<dyn Handler>;
81
82/// Trait to convert functions into handlers
83pub trait IntoHandler<Args> {
84    /// Convert this function into a boxed handler
85    fn into_handler(self) -> BoxedHandler;
86}
87
88// Zero args
89impl<F, Fut, R> IntoHandler<()> for F
90where
91    F: Fn() -> Fut + HandlerFnBounds + 'static,
92    Fut: HandlerFutureBounds<Output = R> + 'static,
93    R: IntoResponse + 'static,
94{
95    fn into_handler(self) -> BoxedHandler {
96        struct H<F>(F);
97
98        impl<F, Fut, R> Handler for H<F>
99        where
100            F: Fn() -> Fut + HandlerFnBounds + 'static,
101            Fut: HandlerFutureBounds<Output = R> + 'static,
102            R: IntoResponse + 'static,
103        {
104            fn call(&self, _ctx: Context) -> HandlerCallFuture<'_> {
105                let fut = (self.0)();
106                Box::pin(async move { fut.await.into_response() })
107            }
108        }
109
110        Arc::new(H(self))
111    }
112}
113
114// One arg
115impl<F, Fut, T1, R> IntoHandler<(T1,)> for F
116where
117    F: Fn(T1) -> Fut + HandlerFnBounds + 'static,
118    Fut: HandlerFutureBounds<Output = R> + 'static,
119    T1: FromContext + 'static,
120    R: IntoResponse + 'static,
121{
122    fn into_handler(self) -> BoxedHandler {
123        struct H<F, T1>(F, std::marker::PhantomData<fn() -> T1>);
124
125        // Safety: PhantomData<fn() -> T1> is always Send+Sync
126        unsafe impl<F: Send, T1> Send for H<F, T1> {}
127        unsafe impl<F: Sync, T1> Sync for H<F, T1> {}
128
129        impl<F, Fut, T1, R> Handler for H<F, T1>
130        where
131            F: Fn(T1) -> Fut + HandlerFnBounds + 'static,
132            Fut: HandlerFutureBounds<Output = R> + 'static,
133            T1: FromContext + 'static,
134            R: IntoResponse + 'static,
135        {
136            fn call(&self, ctx: Context) -> HandlerCallFuture<'_> {
137                Box::pin(async move {
138                    let t1 = T1::from_context(&ctx).await;
139                    (self.0)(t1).await.into_response()
140                })
141            }
142        }
143
144        Arc::new(H(self, std::marker::PhantomData))
145    }
146}
147
148// Two args
149impl<F, Fut, T1, T2, R> IntoHandler<(T1, T2)> for F
150where
151    F: Fn(T1, T2) -> Fut + HandlerFnBounds + 'static,
152    Fut: HandlerFutureBounds<Output = R> + 'static,
153    T1: FromContext + 'static,
154    T2: FromContext + 'static,
155    R: IntoResponse + 'static,
156{
157    fn into_handler(self) -> BoxedHandler {
158        struct H<F, T1, T2>(F, std::marker::PhantomData<fn() -> (T1, T2)>);
159
160        unsafe impl<F: Send, T1, T2> Send for H<F, T1, T2> {}
161        unsafe impl<F: Sync, T1, T2> Sync for H<F, T1, T2> {}
162
163        impl<F, Fut, T1, T2, R> Handler for H<F, T1, T2>
164        where
165            F: Fn(T1, T2) -> Fut + HandlerFnBounds + 'static,
166            Fut: HandlerFutureBounds<Output = R> + 'static,
167            T1: FromContext + 'static,
168            T2: FromContext + 'static,
169            R: IntoResponse + 'static,
170        {
171            fn call(&self, ctx: Context) -> HandlerCallFuture<'_> {
172                Box::pin(async move {
173                    let t1 = T1::from_context(&ctx).await;
174                    let t2 = T2::from_context(&ctx).await;
175                    (self.0)(t1, t2).await.into_response()
176                })
177            }
178        }
179
180        Arc::new(H(self, std::marker::PhantomData))
181    }
182}
183
184// Three args
185impl<F, Fut, T1, T2, T3, R> IntoHandler<(T1, T2, T3)> for F
186where
187    F: Fn(T1, T2, T3) -> Fut + HandlerFnBounds + 'static,
188    Fut: HandlerFutureBounds<Output = R> + 'static,
189    T1: FromContext + 'static,
190    T2: FromContext + 'static,
191    T3: FromContext + 'static,
192    R: IntoResponse + 'static,
193{
194    fn into_handler(self) -> BoxedHandler {
195        struct H<F, T1, T2, T3>(F, std::marker::PhantomData<fn() -> (T1, T2, T3)>);
196
197        unsafe impl<F: Send, T1, T2, T3> Send for H<F, T1, T2, T3> {}
198        unsafe impl<F: Sync, T1, T2, T3> Sync for H<F, T1, T2, T3> {}
199
200        impl<F, Fut, T1, T2, T3, R> Handler for H<F, T1, T2, T3>
201        where
202            F: Fn(T1, T2, T3) -> Fut + HandlerFnBounds + 'static,
203            Fut: HandlerFutureBounds<Output = R> + 'static,
204            T1: FromContext + 'static,
205            T2: FromContext + 'static,
206            T3: FromContext + 'static,
207            R: IntoResponse + 'static,
208        {
209            fn call(&self, ctx: Context) -> HandlerCallFuture<'_> {
210                Box::pin(async move {
211                    let t1 = T1::from_context(&ctx).await;
212                    let t2 = T2::from_context(&ctx).await;
213                    let t3 = T3::from_context(&ctx).await;
214                    (self.0)(t1, t2, t3).await.into_response()
215                })
216            }
217        }
218
219        Arc::new(H(self, std::marker::PhantomData))
220    }
221}
222
223// Four args
224impl<F, Fut, T1, T2, T3, T4, R> IntoHandler<(T1, T2, T3, T4)> for F
225where
226    F: Fn(T1, T2, T3, T4) -> Fut + HandlerFnBounds + 'static,
227    Fut: HandlerFutureBounds<Output = R> + 'static,
228    T1: FromContext + 'static,
229    T2: FromContext + 'static,
230    T3: FromContext + 'static,
231    T4: FromContext + 'static,
232    R: IntoResponse + 'static,
233{
234    fn into_handler(self) -> BoxedHandler {
235        struct H<F, T1, T2, T3, T4>(F, std::marker::PhantomData<fn() -> (T1, T2, T3, T4)>);
236
237        unsafe impl<F: Send, T1, T2, T3, T4> Send for H<F, T1, T2, T3, T4> {}
238        unsafe impl<F: Sync, T1, T2, T3, T4> Sync for H<F, T1, T2, T3, T4> {}
239
240        impl<F, Fut, T1, T2, T3, T4, R> Handler for H<F, T1, T2, T3, T4>
241        where
242            F: Fn(T1, T2, T3, T4) -> Fut + HandlerFnBounds + 'static,
243            Fut: HandlerFutureBounds<Output = R> + 'static,
244            T1: FromContext + 'static,
245            T2: FromContext + 'static,
246            T3: FromContext + 'static,
247            T4: FromContext + 'static,
248            R: IntoResponse + 'static,
249        {
250            fn call(&self, ctx: Context) -> HandlerCallFuture<'_> {
251                Box::pin(async move {
252                    let t1 = T1::from_context(&ctx).await;
253                    let t2 = T2::from_context(&ctx).await;
254                    let t3 = T3::from_context(&ctx).await;
255                    let t4 = T4::from_context(&ctx).await;
256                    (self.0)(t1, t2, t3, t4).await.into_response()
257                })
258            }
259        }
260
261        Arc::new(H(self, std::marker::PhantomData))
262    }
263}