Skip to main content

connectrpc_tauri/
deferred.rs

1//! A dispatcher initialized after the Tauri plugin is registered.
2
3use std::{
4    fmt,
5    sync::{Arc, OnceLock},
6};
7
8use bytes::Bytes;
9use connectrpc::{
10    CodecFormat, Dispatcher, MethodDescriptor, Payload, RequestContext,
11    dispatcher::{
12        RequestStream, StreamingResult, UnaryResult, unimplemented_streaming, unimplemented_unary,
13    },
14};
15
16/// A ConnectRPC dispatcher that can be initialized exactly once.
17///
18/// Tauri requires plugins that register URI schemes to be attached to the
19/// builder, but application services may depend on state created later in the
20/// app setup hook. Clone this dispatcher into the plugin's service, then set
21/// the real dispatcher during setup.
22///
23/// Calls made before initialization are reported as unimplemented.
24///
25/// ```rust
26/// use connectrpc::{ConnectRpcService, Router};
27/// use connectrpc_tauri::{DeferredDispatcher, serve};
28///
29/// let deferred = DeferredDispatcher::new();
30/// fn plugin<R: tauri::Runtime>(
31///     deferred: DeferredDispatcher<Router>,
32/// ) -> tauri::plugin::TauriPlugin<R> {
33///     serve(ConnectRpcService::new(deferred))
34/// }
35/// let _service = ConnectRpcService::new(deferred.clone());
36/// assert!(deferred.set(Router::new()).is_ok());
37/// ```
38pub struct DeferredDispatcher<D> {
39    /// Shared one-time initialization cell used by every clone.
40    inner: Arc<OnceLock<D>>,
41}
42
43impl<D> DeferredDispatcher<D> {
44    /// Creates an uninitialized dispatcher.
45    #[must_use = "retain and initialize the dispatcher before requests arrive"]
46    pub fn new() -> Self {
47        Self {
48            inner: Arc::new(OnceLock::new()),
49        }
50    }
51
52    /// Sets the dispatcher used for all subsequent calls.
53    ///
54    /// # Errors
55    ///
56    /// Returns `dispatcher` unchanged if this instance was already initialized.
57    pub fn set(&self, dispatcher: D) -> Result<(), D> {
58        self.inner.set(dispatcher)
59    }
60
61    /// Returns `true` when both handles refer to the same deferred dispatcher.
62    #[inline]
63    #[must_use = "the identity comparison is returned without modifying either dispatcher"]
64    pub fn same_dispatcher(&self, other: &Self) -> bool {
65        Arc::ptr_eq(&self.inner, &other.inner)
66    }
67}
68
69impl<D> Clone for DeferredDispatcher<D> {
70    #[inline]
71    fn clone(&self) -> Self {
72        Self {
73            inner: Arc::clone(&self.inner),
74        }
75    }
76}
77
78impl<D> Default for DeferredDispatcher<D> {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl<D> fmt::Debug for DeferredDispatcher<D> {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        formatter
87            .debug_struct("DeferredDispatcher")
88            .field("is_initialized", &self.inner.get().is_some())
89            .finish()
90    }
91}
92
93impl<D: Dispatcher> Dispatcher for DeferredDispatcher<D> {
94    #[inline]
95    fn lookup(&self, path: &str) -> Option<MethodDescriptor> {
96        self.inner.get()?.lookup(path)
97    }
98
99    #[inline]
100    fn call_unary(
101        &self,
102        path: &str,
103        context: RequestContext,
104        request: Payload,
105        format: CodecFormat,
106    ) -> UnaryResult {
107        match self.inner.get() {
108            Some(dispatcher) => dispatcher.call_unary(path, context, request, format),
109            None => unimplemented_unary(path),
110        }
111    }
112
113    #[inline]
114    fn call_server_streaming(
115        &self,
116        path: &str,
117        context: RequestContext,
118        request: Bytes,
119        format: CodecFormat,
120    ) -> StreamingResult {
121        match self.inner.get() {
122            Some(dispatcher) => dispatcher.call_server_streaming(path, context, request, format),
123            None => unimplemented_streaming(path),
124        }
125    }
126
127    #[inline]
128    fn call_client_streaming(
129        &self,
130        path: &str,
131        context: RequestContext,
132        requests: RequestStream,
133        format: CodecFormat,
134    ) -> UnaryResult {
135        match self.inner.get() {
136            Some(dispatcher) => dispatcher.call_client_streaming(path, context, requests, format),
137            None => unimplemented_unary(path),
138        }
139    }
140
141    #[inline]
142    fn call_bidi_streaming(
143        &self,
144        path: &str,
145        context: RequestContext,
146        requests: RequestStream,
147        format: CodecFormat,
148    ) -> StreamingResult {
149        match self.inner.get() {
150            Some(dispatcher) => dispatcher.call_bidi_streaming(path, context, requests, format),
151            None => unimplemented_streaming(path),
152        }
153    }
154}