combadge/
post.rs

1use std::any::type_name;
2
3use combadge_macros::{build_post_for_tuple, build_transfer_for_tuple};
4use js_sys::{Array, Uint32Array};
5use serde::{de::DeserializeOwned, Serialize};
6use wasm_bindgen::prelude::*;
7use web_sys::MessagePort;
8
9#[cfg(feature = "experimental_shared_memory")]
10use js_sys::Reflect;
11#[cfg(feature = "experimental_shared_memory")]
12use wasm_bindgen::convert::RefFromWasmAbi;
13
14use crate::Error;
15
16trait SerdePost: Sized {
17    const POSTABLE: bool;
18    fn from_js_value(value: JsValue) -> Result<Self, Error>;
19    fn to_js_value(&self) -> Result<JsValue, Error>;
20}
21
22impl<T> SerdePost for T {
23    default const POSTABLE: bool = false;
24
25    default fn from_js_value(_value: JsValue) -> Result<Self, Error> {
26        Err(Error::UnsupportedType {
27            name: String::from(type_name::<T>()),
28        })
29    }
30
31    default fn to_js_value(&self) -> Result<JsValue, Error> {
32        Err(Error::UnsupportedType {
33            name: String::from(type_name::<T>()),
34        })
35    }
36}
37
38impl<T> SerdePost for T
39where
40    T: DeserializeOwned + Serialize,
41{
42    const POSTABLE: bool = true;
43
44    fn from_js_value(value: JsValue) -> Result<Self, Error> {
45        serde_wasm_bindgen::from_value(value).map_err(|error| Error::DeserializeFailed {
46            type_name: String::from(type_name::<T>()),
47            error: format!("{error:?}"),
48        })
49    }
50
51    fn to_js_value(&self) -> Result<JsValue, Error> {
52        let serializer = serde_wasm_bindgen::Serializer::json_compatible()
53            .serialize_large_number_types_as_bigints(true);
54        self.serialize(&serializer)
55            .map_err(|error| Error::SerializeFailed {
56                type_name: String::from(type_name::<T>()),
57                error: format!("{error:?}"),
58            })
59    }
60}
61
62pub trait WasmPost: Sized {
63    const POSTABLE: bool;
64    fn from_js_value(value: JsValue) -> Result<Self, Error>;
65    fn to_js_value(self) -> Result<JsValue, Error>;
66}
67
68impl<T> WasmPost for T {
69    const POSTABLE: bool = <T as SerdePost>::POSTABLE;
70
71    default fn from_js_value(value: JsValue) -> Result<Self, Error> {
72        SerdePost::from_js_value(value)
73    }
74
75    default fn to_js_value(self) -> Result<JsValue, Error> {
76        SerdePost::to_js_value(&self)
77    }
78}
79
80// If both ends of the communication share the same WASM memory, it should be
81// possible to extract the pointer from the JsValue and use it on both sides of
82// the channel. However, this has not been tested, so try it at your own risk.
83#[cfg(feature = "experimental_shared_memory")]
84impl<T> WasmPost for T
85where
86    T: Into<JsValue> + RefFromWasmAbi<Abi = u32> + Clone + std::fmt::Debug,
87{
88    fn from_js_value(value: JsValue) -> Result<Self, Error> {
89        let ptr = Reflect::get(&value, &JsValue::from_str("__wbg_ptr"))
90            .map_err(|error| Error::DeserializeFailed {
91                type_name: String::from(type_name::<T>()),
92                error: format!("__wbg_ptr not found in JsValue: {error:?}"),
93            })?
94            .as_f64()
95            .ok_or_else(|| Error::DeserializeFailed {
96                type_name: String::from(type_name::<T>()),
97                error: String::from("failed to convert __wbg_ptr to f64"),
98            })? as u32;
99
100        let instance_ref = unsafe { T::ref_from_abi(ptr) };
101        let cloned = instance_ref.clone();
102
103        Ok(instance_ref.clone())
104    }
105
106    fn to_js_value(self) -> Result<JsValue, Error> {
107        let value = self.into();
108
109        let ptr = Reflect::get(&value, &JsValue::from_str("__wbg_ptr"))
110            .map_err(|error| Error::DeserializeFailed {
111                type_name: String::from(type_name::<T>()),
112                error: format!("__wbg_ptr not found in JsValue: {error:?}"),
113            })?
114            .as_f64()
115            .ok_or_else(|| Error::DeserializeFailed {
116                type_name: String::from(type_name::<T>()),
117                error: String::from("failed to convert __wbg_ptr to f64"),
118            })? as u32;
119
120        Ok(value)
121    }
122}
123
124trait JsPost: Sized {
125    const POSTABLE: bool;
126    fn from_js_value(value: JsValue) -> Result<Self, Error>;
127    fn to_js_value(self) -> Result<JsValue, Error>;
128}
129
130impl<T: Sized> JsPost for T {
131    default const POSTABLE: bool = <T as WasmPost>::POSTABLE;
132
133    default fn from_js_value(value: JsValue) -> Result<Self, Error> {
134        WasmPost::from_js_value(value)
135    }
136
137    default fn to_js_value(self) -> Result<JsValue, Error> {
138        WasmPost::to_js_value(self)
139    }
140}
141
142impl<T> JsPost for T
143where
144    T: Into<JsValue> + From<JsValue>,
145{
146    const POSTABLE: bool = true;
147
148    fn from_js_value(value: JsValue) -> Result<Self, Error> {
149        Ok(value.into())
150    }
151
152    fn to_js_value(self) -> Result<JsValue, Error> {
153        Ok(self.into())
154    }
155}
156
157pub trait Post: Sized {
158    const POSTABLE: bool;
159    fn from_js_value(value: JsValue) -> Result<Self, Error>;
160    fn to_js_value(self) -> Result<JsValue, Error>;
161}
162
163impl<T> Post for T
164where
165    T: Sized,
166{
167    default const POSTABLE: bool = <T as JsPost>::POSTABLE;
168
169    default fn from_js_value(value: JsValue) -> Result<Self, Error> {
170        JsPost::from_js_value(value)
171    }
172
173    default fn to_js_value(self) -> Result<JsValue, Error> {
174        JsPost::to_js_value(self)
175    }
176}
177
178impl<T, E> Post for Result<T, E> {
179    const POSTABLE: bool = <T as Post>::POSTABLE && <E as Post>::POSTABLE;
180
181    fn from_js_value(value: JsValue) -> Result<Self, Error> {
182        let value: Array = value.into();
183        let tag = value
184            .at(0)
185            .as_string()
186            .ok_or_else(|| Error::DeserializeFailed {
187                type_name: String::from(type_name::<Self>()),
188                error: String::from("failed to convert first field to string"),
189            })?;
190
191        match tag.as_str() {
192            "Ok" => Ok(Self::Ok(Post::from_js_value(value.at(1))?)),
193            "Err" => Ok(Self::Err(Post::from_js_value(value.at(1))?)),
194            _ => Err(Error::DeserializeFailed {
195                type_name: String::from(type_name::<Self>()),
196                error: format!("found unexpected tag {tag}"),
197            }),
198        }
199    }
200
201    fn to_js_value(self) -> Result<JsValue, Error> {
202        match self {
203            Self::Ok(value) => {
204                Ok(Array::of2(&JsValue::from_str("Ok"), &Post::to_js_value(value)?).into())
205            }
206            Self::Err(error) => {
207                Ok(Array::of2(&JsValue::from_str("Err"), &Post::to_js_value(error)?).into())
208            }
209        }
210    }
211}
212
213impl<T> Post for Box<T>
214where
215    T: Post,
216{
217    const POSTABLE: bool = <T as Post>::POSTABLE;
218
219    fn from_js_value(value: JsValue) -> Result<Self, Error> {
220        let value = <T as Post>::from_js_value(value)?;
221        Ok(Self::new(value))
222    }
223
224    fn to_js_value(self) -> Result<JsValue, Error> {
225        (*self).to_js_value()
226    }
227}
228
229impl<T> Post for Vec<T>
230where
231    T: Post,
232{
233    const POSTABLE: bool = <T as Post>::POSTABLE;
234
235    fn from_js_value(value: JsValue) -> Result<Self, Error> {
236        let array: Array = value.dyn_into().map_err(|error| Error::DeserializeFailed {
237            type_name: String::from(type_name::<T>()),
238            error: format!("{error:?}"),
239        })?;
240        let vec = array
241            .into_iter()
242            .map(|value| <T as Post>::from_js_value(value))
243            .collect::<Result<_, _>>()?;
244        Ok(vec)
245    }
246
247    fn to_js_value(self) -> Result<JsValue, Error> {
248        let array: Array = self
249            .into_iter()
250            .map(T::to_js_value)
251            .collect::<Result<_, _>>()?;
252        Ok(array.into())
253    }
254}
255
256build_post_for_tuple!(7);
257
258pub trait Transfer {
259    fn get_transferable(js_value: &JsValue) -> Option<Array>;
260}
261
262impl<T> Transfer for T {
263    default fn get_transferable(_js_value: &JsValue) -> Option<Array> {
264        None
265    }
266}
267
268impl<T, E> Transfer for Result<T, E> {
269    fn get_transferable(js_value: &JsValue) -> Option<Array> {
270        let array: &Array = js_value.unchecked_ref();
271        let tag = array.get(0).as_string().unwrap();
272        match tag.as_str() {
273            "Ok" => T::get_transferable(&array.get(1)),
274            "Err" => E::get_transferable(&array.get(1)),
275            _ => None,
276        }
277    }
278}
279
280impl<T> Transfer for Box<T>
281where
282    T: Transfer,
283{
284    fn get_transferable(js_value: &JsValue) -> Option<Array> {
285        T::get_transferable(js_value)
286    }
287}
288
289impl<T> Transfer for Vec<T>
290where
291    T: Transfer,
292{
293    fn get_transferable(js_value: &JsValue) -> Option<Array> {
294        let as_array: &Array = js_value.dyn_ref()?;
295        as_array
296            .iter()
297            .filter_map(|value| T::get_transferable(&value))
298            .reduce(|mut acc, e| {
299                acc.extend(e);
300                acc
301            })
302    }
303}
304
305build_transfer_for_tuple!(7);
306
307impl Transfer for MessagePort {
308    fn get_transferable(js_value: &JsValue) -> Option<Array> {
309        Some(Array::of1(js_value))
310    }
311}
312
313impl Transfer for Uint32Array {
314    fn get_transferable(js_value: &JsValue) -> Option<Array> {
315        let as_self: &Self = js_value.dyn_ref().unwrap();
316        Some(Array::of1(&as_self.buffer()))
317    }
318}