Skip to main content

conduit_core/
handler.rs

1//! Handler trait for conduit commands (sync and async).
2//!
3//! [`ConduitHandler`] is implemented by `#[tauri_conduit::command]` for both
4//! synchronous and asynchronous functions. The plugin dispatches via this
5//! trait and branches on [`HandlerResponse`] automatically.
6
7use std::any::Any;
8use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11
12use crate::error::Error;
13
14/// Context passed from the plugin to `#[command]` handlers.
15///
16/// Wraps the application handle (as `Arc<dyn Any>`) and optionally the
17/// webview label of the requesting window. The `#[command]` macro generates
18/// code that downcasts the app handle to extract `State<T>`, `AppHandle`,
19/// `Window`, or `WebviewWindow` parameters.
20///
21/// Plugin authors construct this in the protocol handler; end users never
22/// interact with it directly.
23pub struct HandlerContext {
24    /// The `AppHandle<Wry>` (or equivalent), type-erased.
25    pub app_handle: Arc<dyn Any + Send + Sync>,
26    /// Label of the webview that originated the request, if known.
27    pub webview_label: Option<String>,
28}
29
30impl HandlerContext {
31    /// Create a new handler context.
32    pub fn new(app_handle: Arc<dyn Any + Send + Sync>, webview_label: Option<String>) -> Self {
33        Self {
34            app_handle,
35            webview_label,
36        }
37    }
38}
39
40/// Response from a conduit command handler.
41///
42/// Sync handlers return [`Sync`](HandlerResponse::Sync) with the result
43/// immediately available. Async handlers return [`Async`](HandlerResponse::Async)
44/// with a future that resolves to the response bytes.
45pub enum HandlerResponse {
46    /// Synchronous result, available immediately.
47    Sync(Result<Vec<u8>, Error>),
48    /// Asynchronous result — a pinned future that resolves to the response.
49    Async(Pin<Box<dyn Future<Output = Result<Vec<u8>, Error>> + Send>>),
50}
51
52/// Trait for conduit command handlers, supporting both sync and async.
53///
54/// Generated by `#[tauri_conduit::command]` as a unit struct implementing this
55/// trait. The plugin calls [`call`](ConduitHandler::call) and branches on
56/// the [`HandlerResponse`] variant to handle sync and async uniformly.
57///
58/// # Usage
59///
60/// ```rust,ignore
61/// use tauri_conduit::{command, handler};
62///
63/// #[command]
64/// fn greet(name: String) -> String {
65///     format!("Hello, {name}!")
66/// }
67///
68/// #[command]
69/// async fn fetch(id: u64) -> User {
70///     db::get_user(id).await
71/// }
72///
73/// // Both registered the same way:
74/// tauri_plugin_conduit::init()
75///     .handler("greet", handler!(greet))
76///     .handler("fetch", handler!(fetch))
77///     .build()
78/// ```
79pub trait ConduitHandler: Send + Sync + 'static {
80    /// Execute the handler with the given payload and application context.
81    ///
82    /// The `ctx` is typically an `Arc<AppHandle<Wry>>` provided by the
83    /// plugin's protocol handler. Handlers needing `State<T>` downcast
84    /// the context to extract the app handle.
85    fn call(&self, payload: Vec<u8>, ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse;
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    struct EchoHandler;
93
94    impl ConduitHandler for EchoHandler {
95        fn call(&self, payload: Vec<u8>, _ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
96            HandlerResponse::Sync(Ok(payload))
97        }
98    }
99
100    struct AsyncEchoHandler;
101
102    impl ConduitHandler for AsyncEchoHandler {
103        fn call(&self, payload: Vec<u8>, _ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
104            HandlerResponse::Async(Box::pin(async move { Ok(payload) }))
105        }
106    }
107
108    struct FailHandler;
109
110    impl ConduitHandler for FailHandler {
111        fn call(&self, _payload: Vec<u8>, _ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
112            HandlerResponse::Sync(Err(Error::Handler("intentional".into())))
113        }
114    }
115
116    #[test]
117    fn sync_handler_returns_payload() {
118        let h = EchoHandler;
119        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(());
120        match h.call(b"hello".to_vec(), ctx) {
121            HandlerResponse::Sync(Ok(bytes)) => assert_eq!(bytes, b"hello"),
122            _ => panic!("expected Sync(Ok)"),
123        }
124    }
125
126    #[test]
127    fn async_handler_returns_future() {
128        let h = AsyncEchoHandler;
129        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(());
130        match h.call(b"world".to_vec(), ctx) {
131            HandlerResponse::Async(_) => {} // future exists, can't poll without runtime
132            _ => panic!("expected Async"),
133        }
134    }
135
136    #[test]
137    fn sync_handler_error_variant() {
138        let h = FailHandler;
139        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(());
140        match h.call(vec![], ctx) {
141            HandlerResponse::Sync(Err(Error::Handler(msg))) => {
142                assert_eq!(msg, "intentional");
143            }
144            _ => panic!("expected Sync(Err(Handler))"),
145        }
146    }
147
148    #[test]
149    fn handler_context_downcast() {
150        struct CtxHandler;
151        impl ConduitHandler for CtxHandler {
152            fn call(&self, _payload: Vec<u8>, ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
153                if ctx.downcast_ref::<String>().is_some() {
154                    HandlerResponse::Sync(Ok(b"got string".to_vec()))
155                } else {
156                    HandlerResponse::Sync(Ok(b"no string".to_vec()))
157                }
158            }
159        }
160
161        let h = CtxHandler;
162        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(String::from("hello"));
163        match h.call(vec![], ctx) {
164            HandlerResponse::Sync(Ok(bytes)) => assert_eq!(bytes, b"got string"),
165            _ => panic!("expected Sync(Ok)"),
166        }
167
168        let ctx2: Arc<dyn Any + Send + Sync> = Arc::new(());
169        match h.call(vec![], ctx2) {
170            HandlerResponse::Sync(Ok(bytes)) => assert_eq!(bytes, b"no string"),
171            _ => panic!("expected Sync(Ok)"),
172        }
173    }
174}