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#[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}