wasm_bridge_js/conversions/
to_js_value.rs

1use js_sys::{
2    Array, BigInt64Array, BigUint64Array, Float32Array, Float64Array, Int16Array, Int32Array,
3    Int8Array, Object, Reflect, Uint16Array, Uint32Array, Uint8Array,
4};
5use wasm_bindgen::{
6    convert::{IntoWasmAbi, ReturnWasmAbi},
7    JsValue,
8};
9
10use crate::{helpers::static_str_to_js, Val};
11
12pub trait ToJsValue: Sized {
13    type ReturnAbi: ReturnWasmAbi + IntoWasmAbi;
14
15    fn to_js_value(&self) -> JsValue;
16
17    /// When this is returned from a closure
18    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue>;
19
20    /// Number of function arguments when this type is used as a function input type
21    fn number_of_args() -> u32 {
22        1
23    }
24
25    /// Convert to function arguments when calling a function with this value
26    fn to_function_args(&self) -> Array {
27        Array::of1(&self.to_js_value())
28    }
29
30    /// When converting Vec<Self> to JsValue, create array or Int32Array for example
31    fn create_array_of_size(size: u32) -> JsValue {
32        Array::new_with_length(size).into()
33    }
34}
35
36impl ToJsValue for () {
37    type ReturnAbi = Self;
38
39    fn to_js_value(&self) -> JsValue {
40        JsValue::undefined()
41    }
42
43    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
44        Ok(())
45    }
46
47    fn number_of_args() -> u32 {
48        0
49    }
50
51    fn to_function_args(&self) -> Array {
52        Array::new()
53    }
54}
55
56macro_rules! to_js_value_single {
57    ($ty: ty, $array: ty) => {
58        impl ToJsValue for $ty {
59            type ReturnAbi = Self;
60
61            fn to_js_value(&self) -> JsValue {
62                (*self).into()
63            }
64
65            fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
66                Ok(self)
67            }
68
69            fn create_array_of_size(size: u32) -> JsValue {
70                <$array>::new_with_length(size).into()
71            }
72        }
73    };
74}
75
76to_js_value_single!(bool, Array);
77
78to_js_value_single!(i8, Int8Array);
79to_js_value_single!(i16, Int16Array);
80to_js_value_single!(i32, Int32Array);
81to_js_value_single!(i64, BigInt64Array);
82
83to_js_value_single!(u8, Uint8Array);
84to_js_value_single!(u16, Uint16Array);
85to_js_value_single!(u32, Uint32Array);
86to_js_value_single!(u64, BigUint64Array);
87
88to_js_value_single!(f32, Float32Array);
89to_js_value_single!(f64, Float64Array);
90
91impl ToJsValue for char {
92    type ReturnAbi = Self;
93
94    fn to_js_value(&self) -> JsValue {
95        // TODO: not really great copy
96        self.to_string().into()
97    }
98
99    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
100        Ok(self)
101    }
102}
103
104impl<'a> ToJsValue for &'a str {
105    type ReturnAbi = Self;
106
107    fn to_js_value(&self) -> JsValue {
108        (*self).into()
109    }
110
111    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
112        Ok(self)
113    }
114}
115
116impl ToJsValue for String {
117    type ReturnAbi = Self;
118
119    fn to_js_value(&self) -> JsValue {
120        self.into()
121    }
122
123    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
124        Ok(self)
125    }
126}
127
128// TODO: inspect OptionIntoWasmAbi and see if it's better
129impl<T: ToJsValue> ToJsValue for Option<T> {
130    type ReturnAbi = JsValue;
131
132    fn to_js_value(&self) -> JsValue {
133        match self {
134            Self::Some(value) => value.to_js_value(),
135            None => JsValue::undefined(),
136        }
137    }
138
139    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
140        Ok(self.to_js_value())
141    }
142}
143
144impl<T: ToJsValue, E: ToJsValue> ToJsValue for Result<T, E> {
145    type ReturnAbi = T::ReturnAbi;
146
147    fn to_js_value(&self) -> JsValue {
148        let result: JsValue = Object::new().into();
149        let (tag, val) = match self {
150            Ok(value) => ("ok", value.to_js_value()),
151            Err(err) => ("err", err.to_js_value()),
152        };
153        Reflect::set(&result, static_str_to_js("tag"), &tag.into()).unwrap();
154        Reflect::set(&result, static_str_to_js("val"), &val).unwrap();
155        result
156    }
157
158    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
159        match self {
160            // TODO: what about result<result<...>>?
161            Ok(value) => Ok(value.into_return_abi()?),
162            Err(err) => Err(err.to_js_value()),
163        }
164    }
165}
166
167impl<'a, T: ToJsValue> ToJsValue for &'a T {
168    type ReturnAbi = T::ReturnAbi;
169
170    fn to_js_value(&self) -> JsValue {
171        T::to_js_value(self)
172    }
173
174    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
175        unimplemented!("References should never be returned")
176    }
177}
178
179// TODO: unify references...
180impl<'a, T: ToJsValue> ToJsValue for &'a [T] {
181    type ReturnAbi = JsValue;
182
183    fn to_js_value(&self) -> JsValue {
184        let array = T::create_array_of_size(self.len() as _);
185        self.iter().enumerate().for_each(|(index, item)| {
186            // TODO: set_index is probably faster to Int32Array and "friends"
187            Reflect::set_u32(&array, index as _, &item.to_js_value()).expect("array is array");
188        });
189        array
190    }
191
192    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
193        Ok(self.to_js_value())
194    }
195}
196
197impl<T: ToJsValue> ToJsValue for Vec<T> {
198    type ReturnAbi = JsValue;
199
200    fn to_js_value(&self) -> JsValue {
201        let as_slice: &[T] = self;
202        as_slice.to_js_value()
203    }
204
205    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
206        Ok(self.to_js_value())
207    }
208}
209
210impl ToJsValue for JsValue {
211    type ReturnAbi = Self;
212
213    fn to_js_value(&self) -> JsValue {
214        self.clone()
215    }
216
217    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
218        Ok(self)
219    }
220}
221
222impl ToJsValue for Val {
223    type ReturnAbi = JsValue;
224
225    fn to_js_value(&self) -> JsValue {
226        match self {
227            Val::I32(val) => val.to_js_value(),
228            Val::I64(val) => val.to_js_value(),
229            Val::F32(bits) => f32::from_bits(*bits).to_js_value(),
230            Val::F64(bits) => f64::from_bits(*bits).to_js_value(),
231        }
232    }
233
234    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
235        Ok(self.to_js_value())
236    }
237}
238
239impl<T: ToJsValue> ToJsValue for (T,) {
240    type ReturnAbi = T::ReturnAbi;
241
242    fn to_js_value(&self) -> JsValue {
243        self.0.to_js_value()
244    }
245
246    fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
247        self.0.into_return_abi()
248    }
249
250    fn number_of_args() -> u32 {
251        T::number_of_args()
252    }
253
254    fn to_function_args(&self) -> Array {
255        self.0.to_function_args()
256    }
257}
258
259macro_rules! to_js_value_many {
260    ($count: literal, $(($index: tt, $name: ident)),*) => {
261        impl<$($name: ToJsValue),*> ToJsValue for ($($name, )*) {
262            type ReturnAbi = JsValue;
263
264            fn to_js_value(&self) -> JsValue {
265                self.to_function_args().into()
266            }
267
268            fn into_return_abi(self) -> Result<Self::ReturnAbi, JsValue> {
269                Ok(self.to_js_value())
270            }
271
272            fn number_of_args() -> u32 {
273                $count
274            }
275
276            fn to_function_args(&self) -> Array {
277                [$( &self.$index.to_js_value(), )*].iter().collect()
278            }
279        }
280    };
281}
282
283#[rustfmt::skip]
284to_js_value_many!( 2, (0, T0), (1, T1));
285#[rustfmt::skip]
286to_js_value_many!( 3, (0, T0), (1, T1), (2, T2));
287#[rustfmt::skip]
288to_js_value_many!( 4, (0, T0), (1, T1), (2, T2), (3, T3));
289#[rustfmt::skip]
290to_js_value_many!( 5, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4));
291#[rustfmt::skip]
292to_js_value_many!( 6, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5));
293#[rustfmt::skip]
294to_js_value_many!( 7, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6));
295#[rustfmt::skip]
296to_js_value_many!( 8, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7));
297
298// Limit to tuples of size 8
299// #[rustfmt::skip]
300// to_js_value_many!( 9, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8));
301// #[rustfmt::skip]
302// to_js_value_many!(10, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9));
303// #[rustfmt::skip]
304// to_js_value_many!(11, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10));
305// #[rustfmt::skip]
306// to_js_value_many!(12, (0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10), (11, T11));