conduit-core 2.1.1

Binary IPC core: codec, router, ring buffer, handler trait.
Documentation
//! Handler trait for conduit commands (sync and async).
//!
//! [`ConduitHandler`] is implemented by `#[tauri_conduit::command]` for both
//! synchronous and asynchronous functions. The plugin dispatches via this
//! trait and branches on [`HandlerResponse`] automatically.

use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::error::Error;

/// Context passed from the plugin to `#[command]` handlers.
///
/// Wraps the application handle (as `Arc<dyn Any>`) and optionally the
/// webview label of the requesting window. The `#[command]` macro generates
/// code that downcasts the app handle to extract `State<T>`, `AppHandle`,
/// `Window`, or `WebviewWindow` parameters.
///
/// Plugin authors construct this in the protocol handler; end users never
/// interact with it directly.
pub struct HandlerContext {
    /// The `AppHandle<Wry>` (or equivalent), type-erased.
    pub app_handle: Arc<dyn Any + Send + Sync>,
    /// Label of the webview that originated the request, if known.
    pub webview_label: Option<String>,
}

impl HandlerContext {
    /// Create a new handler context.
    pub fn new(app_handle: Arc<dyn Any + Send + Sync>, webview_label: Option<String>) -> Self {
        Self {
            app_handle,
            webview_label,
        }
    }
}

/// Response from a conduit command handler.
///
/// Sync handlers return [`Sync`](HandlerResponse::Sync) with the result
/// immediately available. Async handlers return [`Async`](HandlerResponse::Async)
/// with a future that resolves to the response bytes.
pub enum HandlerResponse {
    /// Synchronous result, available immediately.
    Sync(Result<Vec<u8>, Error>),
    /// Asynchronous result — a pinned future that resolves to the response.
    Async(Pin<Box<dyn Future<Output = Result<Vec<u8>, Error>> + Send>>),
}

/// Trait for conduit command handlers, supporting both sync and async.
///
/// Generated by `#[tauri_conduit::command]` as a unit struct implementing this
/// trait. The plugin calls [`call`](ConduitHandler::call) and branches on
/// the [`HandlerResponse`] variant to handle sync and async uniformly.
///
/// # Usage
///
/// ```rust,ignore
/// use tauri_conduit::{command, handler};
///
/// #[command]
/// fn greet(name: String) -> String {
///     format!("Hello, {name}!")
/// }
///
/// #[command]
/// async fn fetch(id: u64) -> User {
///     db::get_user(id).await
/// }
///
/// // Both registered the same way:
/// tauri_plugin_conduit::init()
///     .handler("greet", handler!(greet))
///     .handler("fetch", handler!(fetch))
///     .build()
/// ```
pub trait ConduitHandler: Send + Sync + 'static {
    /// Execute the handler with the given payload and application context.
    ///
    /// The `ctx` is typically an `Arc<AppHandle<Wry>>` provided by the
    /// plugin's protocol handler. Handlers needing `State<T>` downcast
    /// the context to extract the app handle.
    fn call(&self, payload: Vec<u8>, ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse;
}

#[cfg(test)]
mod tests {
    use super::*;

    struct EchoHandler;

    impl ConduitHandler for EchoHandler {
        fn call(&self, payload: Vec<u8>, _ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
            HandlerResponse::Sync(Ok(payload))
        }
    }

    struct AsyncEchoHandler;

    impl ConduitHandler for AsyncEchoHandler {
        fn call(&self, payload: Vec<u8>, _ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
            HandlerResponse::Async(Box::pin(async move { Ok(payload) }))
        }
    }

    struct FailHandler;

    impl ConduitHandler for FailHandler {
        fn call(&self, _payload: Vec<u8>, _ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
            HandlerResponse::Sync(Err(Error::Handler("intentional".into())))
        }
    }

    #[test]
    fn sync_handler_returns_payload() {
        let h = EchoHandler;
        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(());
        match h.call(b"hello".to_vec(), ctx) {
            HandlerResponse::Sync(Ok(bytes)) => assert_eq!(bytes, b"hello"),
            _ => panic!("expected Sync(Ok)"),
        }
    }

    #[test]
    fn async_handler_returns_future() {
        let h = AsyncEchoHandler;
        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(());
        match h.call(b"world".to_vec(), ctx) {
            HandlerResponse::Async(_) => {} // future exists, can't poll without runtime
            _ => panic!("expected Async"),
        }
    }

    #[test]
    fn sync_handler_error_variant() {
        let h = FailHandler;
        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(());
        match h.call(vec![], ctx) {
            HandlerResponse::Sync(Err(Error::Handler(msg))) => {
                assert_eq!(msg, "intentional");
            }
            _ => panic!("expected Sync(Err(Handler))"),
        }
    }

    #[test]
    fn handler_context_downcast() {
        struct CtxHandler;
        impl ConduitHandler for CtxHandler {
            fn call(&self, _payload: Vec<u8>, ctx: Arc<dyn Any + Send + Sync>) -> HandlerResponse {
                if ctx.downcast_ref::<String>().is_some() {
                    HandlerResponse::Sync(Ok(b"got string".to_vec()))
                } else {
                    HandlerResponse::Sync(Ok(b"no string".to_vec()))
                }
            }
        }

        let h = CtxHandler;
        let ctx: Arc<dyn Any + Send + Sync> = Arc::new(String::from("hello"));
        match h.call(vec![], ctx) {
            HandlerResponse::Sync(Ok(bytes)) => assert_eq!(bytes, b"got string"),
            _ => panic!("expected Sync(Ok)"),
        }

        let ctx2: Arc<dyn Any + Send + Sync> = Arc::new(());
        match h.call(vec![], ctx2) {
            HandlerResponse::Sync(Ok(bytes)) => assert_eq!(bytes, b"no string"),
            _ => panic!("expected Sync(Ok)"),
        }
    }
}