Skip to main content

wry_bindgen/
try_from_js.rs

1//! Conversions between Rust values and `JsValue`.
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5
6use crate::{JsCast, JsValue, convert};
7
8macro_rules! cast {
9    (($from:ty => $to:ty) $val:expr) => {{ $crate::__rt::wbg_cast::<$from, $to>($val) }};
10}
11
12macro_rules! to_js_value {
13    ($ty:ty) => {
14        impl From<$ty> for $crate::JsValue {
15            fn from(val: $ty) -> Self {
16                cast! {($ty => $crate::JsValue) val}
17            }
18        }
19    };
20}
21
22macro_rules! from_js_value {
23    ($ty:ty) => {
24        impl From<$crate::JsValue> for $ty {
25            fn from(val: $crate::JsValue) -> Self {
26                cast! {($crate::JsValue => $ty) val}
27            }
28        }
29    };
30}
31
32impl TryFrom<JsValue> for u64 {
33    type Error = JsValue;
34
35    fn try_from(v: JsValue) -> Result<Self, JsValue> {
36        <Self as convert::TryFromJsValue>::try_from_js_value(v)
37    }
38}
39
40impl TryFrom<JsValue> for i64 {
41    type Error = JsValue;
42
43    fn try_from(v: JsValue) -> Result<Self, JsValue> {
44        <Self as convert::TryFromJsValue>::try_from_js_value(v)
45    }
46}
47
48impl TryFrom<JsValue> for f64 {
49    type Error = JsValue;
50
51    fn try_from(val: JsValue) -> Result<Self, Self::Error> {
52        f64::try_from(&val)
53    }
54}
55
56impl TryFrom<&JsValue> for f64 {
57    type Error = JsValue;
58
59    /// Applies the unary `+` JS operator, matching wasm-bindgen: the coerced number on
60    /// success (NaN for e.g. "hi"), or the thrown error value (e.g. for a Symbol).
61    fn try_from(val: &JsValue) -> Result<Self, Self::Error> {
62        let jsval = crate::js_helpers::js_try_into_number(val);
63        jsval.as_f64().ok_or(jsval)
64    }
65}
66
67impl TryFrom<JsValue> for i128 {
68    type Error = JsValue;
69
70    fn try_from(v: JsValue) -> Result<Self, JsValue> {
71        <Self as convert::TryFromJsValue>::try_from_js_value(v)
72    }
73}
74
75impl TryFrom<JsValue> for u128 {
76    type Error = JsValue;
77
78    fn try_from(v: JsValue) -> Result<Self, JsValue> {
79        <Self as convert::TryFromJsValue>::try_from_js_value(v)
80    }
81}
82
83impl TryFrom<JsValue> for String {
84    type Error = JsValue;
85
86    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
87        value.as_string().ok_or(value)
88    }
89}
90
91impl convert::TryFromJsValue for String {
92    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
93        value.as_string()
94    }
95}
96
97impl convert::TryFromJsValue for bool {
98    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
99        value.as_bool()
100    }
101}
102
103impl convert::TryFromJsValue for char {
104    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
105        let s = value.as_string()?;
106        let mut chars = s.chars();
107        let c = chars.next()?;
108        if chars.next().is_none() {
109            Some(c)
110        } else {
111            None
112        }
113    }
114}
115
116impl convert::TryFromJsValue for () {
117    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
118        if value.is_undefined() { Some(()) } else { None }
119    }
120}
121
122impl<T: convert::TryFromJsValue> convert::TryFromJsValue for Option<T> {
123    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
124        if value.is_undefined() {
125            Some(None)
126        } else {
127            T::try_from_js_value_ref(value).map(Some)
128        }
129    }
130}
131
132impl<T: convert::TryFromJsValue> convert::TryFromJsValue for Vec<T> {
133    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
134        if !value.is_array() {
135            return None;
136        }
137        let length = crate::js_helpers::js_reflect_get(value, &JsValue::from_str("length"));
138        let len = length.as_f64()? as u32;
139        let mut out = Vec::with_capacity(len as usize);
140        for i in 0..len {
141            let element = crate::js_helpers::js_reflect_get(value, &JsValue::from_f64(i as f64));
142            out.push(T::try_from_js_value(element).ok()?);
143        }
144        Some(out)
145    }
146}
147
148/// ECMAScript `ToUint32` of a JS number: a non-number is rejected, otherwise the
149/// value is truncated toward zero and reduced modulo 2^32. Mirrors
150/// wasm-bindgen's `to_uint_32` so the narrowing integer casts below follow the
151/// same WebAssembly `ToWebAssemblyValue` wrapping semantics rather than range
152/// checking (e.g. `i8::try_from_js_value(128.0)` wraps to `-128`).
153fn to_uint_32(v: &JsValue) -> Option<u32> {
154    v.as_f64().map(|n| {
155        if n.is_infinite() {
156            0
157        } else {
158            (n as i64) as u32
159        }
160    })
161}
162
163macro_rules! try_from_js_value_int {
164    ($($ty:ty),* $(,)?) => {
165        $(
166            impl convert::TryFromJsValue for $ty {
167                fn try_from_js_value_ref(val: &JsValue) -> Option<$ty> {
168                    to_uint_32(val).map(|n| n as $ty)
169                }
170            }
171        )*
172    };
173}
174
175try_from_js_value_int!(i8, u8, i16, u16, i32, u32);
176
177impl convert::TryFromJsValue for f32 {
178    fn try_from_js_value_ref(val: &JsValue) -> Option<f32> {
179        val.as_f64().map(|n| n as f32)
180    }
181}
182
183impl convert::TryFromJsValue for f64 {
184    fn try_from_js_value_ref(val: &JsValue) -> Option<f64> {
185        val.as_f64()
186    }
187}
188
189impl convert::TryFromJsValue for i64 {
190    fn try_from_js_value_ref(val: &JsValue) -> Option<i64> {
191        // The intrinsic already yields the signed low 64 bits; reject values
192        // that didn't fit via the bigint round-trip (the comparison is `===`).
193        let as_self = crate::js_helpers::js_bigint_get_as_i64(val)?;
194        if val == &as_self { Some(as_self) } else { None }
195    }
196}
197
198impl convert::TryFromJsValue for u64 {
199    fn try_from_js_value_ref(val: &JsValue) -> Option<u64> {
200        // Bit-reinterpret the signed low 64 bits as unsigned, then reject values
201        // that didn't fit via the bigint round-trip below.
202        let as_self = crate::js_helpers::js_bigint_get_as_i64(val)?.cast_unsigned();
203        if val == &as_self { Some(as_self) } else { None }
204    }
205}
206
207macro_rules! num128_from_js {
208    ($($ty:ty, $hi_ty:ty;)*) => ($(
209        impl convert::TryFromJsValue for $ty {
210            fn try_from_js_value_ref(v: &JsValue) -> Option<$ty> {
211                // Low 64 bits, interpreted as unsigned for both i128 and u128.
212                let lo = crate::js_helpers::js_bigint_get_as_i64(v)?.cast_unsigned();
213                // `v` is now known to be a bigint, so the shift can't throw.
214                let hi = v >> JsValue::from(64_u64);
215                // Range-check the high half against its 64-bit type, then widen
216                // both halves losslessly and recombine.
217                <$hi_ty as convert::TryFromJsValue>::try_from_js_value_ref(&hi)
218                    .map(|hi| (<$ty>::from(hi) << 64) | <$ty>::from(lo))
219            }
220        }
221    )*)
222}
223
224num128_from_js! {
225    i128, i64;
226    u128, u64;
227}
228
229impl convert::TryFromJsValue for isize {
230    fn try_from_js_value_ref(val: &JsValue) -> Option<isize> {
231        val.as_f64().map(|n| n as isize)
232    }
233}
234
235impl convert::TryFromJsValue for usize {
236    fn try_from_js_value_ref(val: &JsValue) -> Option<usize> {
237        val.as_f64().map(|n| n as usize)
238    }
239}
240
241to_js_value!(i8);
242from_js_value!(i8);
243to_js_value!(i16);
244from_js_value!(i16);
245to_js_value!(i32);
246from_js_value!(i32);
247to_js_value!(i64);
248to_js_value!(i128);
249to_js_value!(u8);
250from_js_value!(u8);
251to_js_value!(u16);
252from_js_value!(u16);
253to_js_value!(u32);
254from_js_value!(u32);
255to_js_value!(u64);
256to_js_value!(u128);
257to_js_value!(f32);
258from_js_value!(f32);
259to_js_value!(f64);
260to_js_value!(usize);
261from_js_value!(usize);
262to_js_value!(isize);
263from_js_value!(isize);
264impl<'a> From<&'a str> for JsValue {
265    fn from(s: &'a str) -> JsValue {
266        cast! {(String => JsValue) s.to_string()}
267    }
268}
269impl<'a> From<&'a String> for JsValue {
270    fn from(s: &'a String) -> JsValue {
271        cast! {(String => JsValue) s.clone()}
272    }
273}
274impl<'a, T> From<&'a T> for JsValue
275where
276    T: JsCast,
277{
278    fn from(s: &'a T) -> JsValue {
279        s.as_ref().clone()
280    }
281}
282impl From<String> for JsValue {
283    fn from(s: String) -> JsValue {
284        cast! {(String => JsValue) s}
285    }
286}
287to_js_value!(());
288from_js_value!(());
289
290impl<T> From<Option<T>> for JsValue
291where
292    T: Into<JsValue>,
293{
294    fn from(s: Option<T>) -> JsValue {
295        match s {
296            Some(s) => s.into(),
297            None => JsValue::undefined(),
298        }
299    }
300}