wasm_bridge_js/conversions/
from_js_value.rs

1use anyhow::Context;
2use js_sys::Reflect;
3use wasm_bindgen::{convert::FromWasmAbi, JsValue};
4
5use crate::{
6    helpers::{map_js_error, static_str_to_js},
7    *,
8};
9
10pub trait FromJsValue: Sized {
11    type WasmAbi: FromWasmAbi;
12
13    fn from_js_value(value: &JsValue) -> Result<Self>;
14
15    // When type is the (direct) result of an exported function
16    fn from_fn_result(result: &Result<JsValue, JsValue>) -> Result<Self> {
17        Self::from_js_value(
18            result
19                .as_ref()
20                .map_err(map_js_error("Exported function threw an exception"))?,
21        )
22    }
23
24    // When type is an argument of an imported function
25    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self>;
26}
27
28impl FromJsValue for () {
29    type WasmAbi = JsValue;
30
31    fn from_js_value(value: &JsValue) -> Result<Self> {
32        if value.is_undefined() || value.is_null() {
33            Ok(())
34        } else {
35            Err(map_js_error("Expected null or undefined")(value))
36        }
37    }
38
39    fn from_wasm_abi(_abi: Self::WasmAbi) -> Result<Self> {
40        Ok(())
41    }
42}
43
44impl FromJsValue for bool {
45    type WasmAbi = Self;
46
47    fn from_js_value(value: &JsValue) -> Result<Self> {
48        match value.as_bool() {
49            Some(value) => Ok(value),
50            None => Err(map_js_error("Expected a boolean value")(value)),
51        }
52    }
53
54    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
55        Ok(abi)
56    }
57}
58
59macro_rules! from_js_value_signed {
60    ($name: ty) => {
61        impl FromJsValue for $name {
62            type WasmAbi = Self;
63
64            fn from_js_value(value: &JsValue) -> Result<Self> {
65                match value.as_f64() {
66                    Some(number) => Ok(number as _),
67                    None => Err(map_js_error("Expected a number")(value)),
68                }
69            }
70
71            fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
72                Ok(abi)
73            }
74        }
75    };
76}
77
78from_js_value_signed!(i8);
79from_js_value_signed!(i16);
80from_js_value_signed!(i32);
81
82impl FromJsValue for i64 {
83    type WasmAbi = Self;
84
85    fn from_js_value(value: &JsValue) -> Result<Self> {
86        value
87            .clone()
88            .try_into()
89            .map_err(map_js_error("Expected a bigint"))
90    }
91
92    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
93        Ok(abi)
94    }
95}
96macro_rules! from_js_value_unsigned {
97    ($name: ty, $signed: ty) => {
98        impl FromJsValue for $name {
99            type WasmAbi = Self;
100
101            fn from_js_value(value: &JsValue) -> Result<Self> {
102                // TODO: Add a check that the value didn't overflow?
103                match value.as_f64() {
104                    // Value might be bigger than $name::MAX / 2 or smaller than 0
105                    Some(number) if number < 0.0 => Ok(number as $signed as _),
106                    Some(number) => Ok(number as _),
107                    None => Err(map_js_error("Expected a number")(value)),
108                }
109            }
110
111            fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
112                Ok(abi)
113            }
114        }
115    };
116}
117
118from_js_value_unsigned!(u8, i8);
119from_js_value_unsigned!(u16, i16);
120from_js_value_unsigned!(u32, i32);
121
122impl FromJsValue for u64 {
123    type WasmAbi = Self;
124
125    fn from_js_value(value: &JsValue) -> Result<Self> {
126        // Value is BigInt, but it might be positive over u64::MAX / 2, or negative
127        u64::try_from(value.clone())
128            .or_else(|_| i64::try_from(value.clone()).map(|value| value as u64))
129            .map_err(map_js_error("Expected a bigint"))
130    }
131
132    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
133        Ok(abi)
134    }
135}
136
137// TODO: not the best name, but it works
138from_js_value_signed!(f32);
139from_js_value_signed!(f64);
140
141impl FromJsValue for char {
142    type WasmAbi = Self;
143
144    fn from_js_value(value: &JsValue) -> Result<Self> {
145        match value.as_string() {
146            Some(text) if !text.is_empty() => Ok(text.chars().next().unwrap()),
147            _ => Err(map_js_error("Expected a single-character string")(value)),
148        }
149    }
150
151    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
152        Ok(abi)
153    }
154}
155
156impl FromJsValue for String {
157    type WasmAbi = Self;
158
159    fn from_js_value(value: &JsValue) -> Result<Self, crate::Error> {
160        match value.as_string() {
161            Some(value) => Ok(value),
162            None => Err(map_js_error("Expected a string")(value)),
163        }
164    }
165
166    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
167        Ok(abi)
168    }
169}
170
171impl<T: FromJsValue> FromJsValue for Option<T> {
172    type WasmAbi = JsValue; // TODO: better ABI?
173
174    fn from_js_value(value: &JsValue) -> Result<Self> {
175        if value.is_undefined() || value.is_null() {
176            Ok(None)
177        } else {
178            Ok(Some(T::from_js_value(value)?))
179        }
180    }
181
182    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
183        Self::from_js_value(&abi)
184    }
185}
186
187impl<T: FromJsValue, E: FromJsValue> FromJsValue for Result<T, E> {
188    type WasmAbi = JsValue;
189
190    fn from_js_value(value: &JsValue) -> Result<Self> {
191        // TODO: better error handling
192        let tag = Reflect::get(value, static_str_to_js("tag"))
193            .map_err(map_js_error("Get tag from result"))?
194            .as_string()
195            .context("Result tag should be string")?;
196
197        let val = Reflect::get(value, static_str_to_js("val"))
198            .map_err(map_js_error("Get val from result"))?;
199
200        if tag == "ok" {
201            Ok(Ok(T::from_js_value(&val)?))
202        } else if tag == "err" {
203            Ok(Err(E::from_js_value(&val)?))
204        } else {
205            Err(map_js_error("Unknown result tag")(value))
206        }
207    }
208
209    fn from_fn_result(result: &Result<JsValue, JsValue>) -> Result<Self> {
210        Ok(match result {
211            Ok(val) => Ok(T::from_js_value(val)?),
212            Err(err) => {
213                let payload = Reflect::get(err, static_str_to_js("payload"))
214                    .map_err(map_js_error("Get result error payload"))?;
215                Err(E::from_js_value(&payload)?)
216            }
217        })
218    }
219
220    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
221        Self::from_js_value(&abi)
222    }
223}
224
225impl<T: FromJsValue> FromJsValue for Vec<T> {
226    type WasmAbi = JsValue;
227
228    fn from_js_value(value: &JsValue) -> Result<Self> {
229        let length = Reflect::get(value, static_str_to_js("length"))
230            .map_err(map_js_error("Get length of array"))?
231            .as_f64()
232            .context("Array length should be a number")? as u32;
233
234        let mut result = Vec::with_capacity(length as usize);
235
236        for index in 0..length {
237            let item = Reflect::get_u32(value, index)
238                .map_err(map_js_error("Get array value at an index"))?;
239            result.push(T::from_js_value(&item)?);
240        }
241
242        Ok(result)
243    }
244
245    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
246        Self::from_js_value(&abi)
247    }
248}
249
250impl FromJsValue for JsValue {
251    type WasmAbi = Self;
252
253    fn from_js_value(value: &JsValue) -> Result<Self> {
254        Ok(value.clone())
255    }
256
257    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
258        Ok(abi)
259    }
260}
261
262impl FromJsValue for Val {
263    type WasmAbi = JsValue;
264
265    fn from_js_value(value: &JsValue) -> Result<Self> {
266        if let Some(number) = value.as_f64() {
267            // TODO: BIG problem ... this could be I32, F32 or I64, and we don't really know which one ...
268            Ok(number.into())
269        } else if value.is_bigint() {
270            // TODO: u64 is used, because it's more "robust" ... make i64 robust as well instead?
271            Ok(Val::I64(u64::from_js_value(value)? as _))
272        } else {
273            Err(map_js_error("Unsupported 'Val' value")(value))
274        }
275    }
276
277    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
278        Self::from_js_value(&abi)
279    }
280}
281
282impl<T: FromJsValue> FromJsValue for (T,) {
283    type WasmAbi = T::WasmAbi;
284
285    fn from_js_value(value: &JsValue) -> Result<Self> {
286        Ok((T::from_js_value(value)?,))
287    }
288
289    fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
290        Ok((T::from_wasm_abi(abi)?,))
291    }
292
293    fn from_fn_result(result: &Result<JsValue, JsValue>) -> Result<Self> {
294        Ok((T::from_fn_result(result)?,))
295    }
296}
297
298macro_rules! from_js_value_many {
299    ($(($index: tt, $name: ident)),*) => {
300        impl<$($name: FromJsValue),*> FromJsValue for ($($name, )*) {
301            type WasmAbi = JsValue;
302
303            fn from_js_value(results: &JsValue) -> Result<Self> {
304                Ok(( $($name::from_js_value(&Reflect::get_u32(results, $index).map_err(map_js_error("Get tuple value"))?)?,)* ))
305            }
306
307            fn from_wasm_abi(abi: Self::WasmAbi) -> Result<Self> {
308                Self::from_js_value(&abi)
309            }
310        }
311    };
312}
313
314#[rustfmt::skip]
315from_js_value_many!((0, T0), (1, T1));
316#[rustfmt::skip]
317from_js_value_many!((0, T0), (1, T1), (2, T2));
318#[rustfmt::skip]
319from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3));
320#[rustfmt::skip]
321from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4));
322#[rustfmt::skip]
323from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5));
324#[rustfmt::skip]
325from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6));
326#[rustfmt::skip]
327from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7));
328
329// Limit to tuples of size 8
330// #[rustfmt::skip]
331// from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8));
332// #[rustfmt::skip]
333// from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9));
334// #[rustfmt::skip]
335// from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10));
336// #[rustfmt::skip]
337// from_js_value_many!((0, T0), (1, T1), (2, T2), (3, T3), (4, T4), (5, T5), (6, T6), (7, T7), (8, T8), (9, T9), (10, T10), (11, T11));