web_rpc/wrap.rs
1//! Explicit routing wrappers.
2//!
3//! A value in an RPC signature crosses the channel either as postcard bytes inside the
4//! payload, or as a Javascript value in the message array. The wrapper picks the route:
5//! [`Post`] and [`Transfer`] take the Javascript path, everything else is postcard-encoded
6//! and must implement [`postcard_schema::Schema`].
7//!
8//! [`Transfer`] additionally puts the value on the `postMessage` transfer list, so it moves
9//! rather than being copied by the structured clone algorithm.
10//!
11//! No trait constrains what may be transferred. `T` must be a transferable object as defined
12//! by the structured clone algorithm (`ArrayBuffer`, `MessagePort`, `OffscreenCanvas`,
13//! `ImageBitmap`, the stream types, ...); anything else is a `DataCloneError` thrown by the
14//! browser at the moment of sending. A typed array is not transferable: send
15//! `Transfer<ArrayBuffer>` and rebuild the view on the other side. A view over wasm linear
16//! memory can never be transferred, so [`Post`] it instead.
17//!
18//! A bare Javascript type in a signature does not compile:
19//!
20//! ```compile_fail
21//! #[web_rpc::service]
22//! pub trait Echo {
23//! fn echo(&self, value: js_sys::JsString) -> js_sys::JsString;
24//! }
25//! ```
26//!
27//! A payload type without `#[derive(Schema)]` is rejected at the argument that uses it:
28//!
29//! ```compile_fail
30//! #[derive(serde::Serialize, serde::Deserialize)]
31//! pub struct Point { x: u32 }
32//!
33//! #[web_rpc::service]
34//! pub trait Plot {
35//! fn plot(&self, point: Point);
36//! }
37//! ```
38
39use std::ops::Deref;
40
41use wasm_bindgen::JsCast;
42
43/// Wrapper that routes `T` across the channel as a Javascript value.
44///
45/// ```rust
46/// # use web_rpc::wrap::Post;
47/// #[web_rpc::service]
48/// pub trait Echo {
49/// fn echo(&self, value: Post<js_sys::JsString>) -> Post<js_sys::JsString>;
50/// }
51/// ```
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct Post<T: JsCast>(pub T);
54
55/// Wrapper that routes `T` across the channel as a Javascript value and puts it on the
56/// transfer list, moving it out of the sending context.
57///
58/// ```rust
59/// # use web_rpc::wrap::Transfer;
60/// #[web_rpc::service]
61/// pub trait Upload {
62/// fn upload(&self, buffer: Transfer<js_sys::ArrayBuffer>) -> u32;
63/// }
64/// ```
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct Transfer<T: JsCast>(pub T);
67
68macro_rules! impl_wrapper {
69 ($wrapper:ident) => {
70 impl<T: JsCast> $wrapper<T> {
71 /// Wrap a value.
72 pub fn new(value: T) -> Self {
73 Self(value)
74 }
75
76 /// Unwrap, returning the inner Javascript value.
77 pub fn into_inner(self) -> T {
78 self.0
79 }
80 }
81
82 impl<T: JsCast> Deref for $wrapper<T> {
83 type Target = T;
84
85 fn deref(&self) -> &Self::Target {
86 &self.0
87 }
88 }
89
90 impl<T: JsCast> From<T> for $wrapper<T> {
91 fn from(value: T) -> Self {
92 Self(value)
93 }
94 }
95
96 impl<T: JsCast> AsRef<T> for $wrapper<T> {
97 fn as_ref(&self) -> &T {
98 &self.0
99 }
100 }
101 };
102}
103
104impl_wrapper!(Post);
105impl_wrapper!(Transfer);
106
107/// Borrow a Javascript value as a [`JsValue`](wasm_bindgen::JsValue).
108///
109/// The `js_sys` and `web_sys` types implement `AsRef` for their whole prototype chain; this
110/// selects the `JsValue` impl.
111#[doc(hidden)]
112pub fn js_value<T: JsCast>(value: &T) -> &wasm_bindgen::JsValue {
113 value.as_ref()
114}