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        val.as_f64().ok_or(val)
53    }
54}
55
56impl TryFrom<&JsValue> for f64 {
57    type Error = JsValue;
58
59    fn try_from(val: &JsValue) -> Result<Self, Self::Error> {
60        val.as_f64().ok_or_else(|| val.clone())
61    }
62}
63
64impl TryFrom<JsValue> for i128 {
65    type Error = JsValue;
66
67    fn try_from(v: JsValue) -> Result<Self, JsValue> {
68        <Self as convert::TryFromJsValue>::try_from_js_value(v)
69    }
70}
71
72impl TryFrom<JsValue> for u128 {
73    type Error = JsValue;
74
75    fn try_from(v: JsValue) -> Result<Self, JsValue> {
76        <Self as convert::TryFromJsValue>::try_from_js_value(v)
77    }
78}
79
80impl TryFrom<JsValue> for String {
81    type Error = JsValue;
82
83    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
84        value.as_string().ok_or(value)
85    }
86}
87
88impl convert::TryFromJsValue for String {
89    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
90        value.as_string()
91    }
92}
93
94impl convert::TryFromJsValue for bool {
95    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
96        value.as_bool()
97    }
98}
99
100impl convert::TryFromJsValue for char {
101    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
102        let s = value.as_string()?;
103        let mut chars = s.chars();
104        let c = chars.next()?;
105        if chars.next().is_none() {
106            Some(c)
107        } else {
108            None
109        }
110    }
111}
112
113impl convert::TryFromJsValue for () {
114    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
115        if value.is_undefined() { Some(()) } else { None }
116    }
117}
118
119impl<T: convert::TryFromJsValue> convert::TryFromJsValue for Option<T> {
120    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
121        if value.is_undefined() {
122            Some(None)
123        } else {
124            T::try_from_js_value_ref(value).map(Some)
125        }
126    }
127}
128
129impl<T: convert::TryFromJsValue> convert::TryFromJsValue for Vec<T> {
130    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
131        if !value.is_array() {
132            return None;
133        }
134        let length = crate::js_helpers::js_reflect_get(value, &JsValue::from_str("length"));
135        let len = length.as_f64()? as u32;
136        let mut out = Vec::with_capacity(len as usize);
137        for i in 0..len {
138            let element = crate::js_helpers::js_reflect_get(value, &JsValue::from_f64(i as f64));
139            out.push(T::try_from_js_value(element).ok()?);
140        }
141        Some(out)
142    }
143}
144
145fn js_number_is_integer_in_range(number: f64, min: f64, max: f64) -> bool {
146    number.is_finite() && number.fract() == 0.0 && (min..=max).contains(&number)
147}
148
149macro_rules! try_from_js_value_signed_int {
150    ($($ty:ty),* $(,)?) => {
151        $(
152            impl convert::TryFromJsValue for $ty {
153                fn try_from_js_value_ref(val: &JsValue) -> Option<$ty> {
154                    let number = val.as_f64()?;
155                    if js_number_is_integer_in_range(number, <$ty>::MIN as f64, <$ty>::MAX as f64) {
156                        Some(number as $ty)
157                    } else {
158                        None
159                    }
160                }
161            }
162        )*
163    };
164}
165
166macro_rules! try_from_js_value_unsigned_int {
167    ($($ty:ty),* $(,)?) => {
168        $(
169            impl convert::TryFromJsValue for $ty {
170                fn try_from_js_value_ref(val: &JsValue) -> Option<$ty> {
171                    let number = val.as_f64()?;
172                    if js_number_is_integer_in_range(number, 0.0, <$ty>::MAX as f64) {
173                        Some(number as $ty)
174                    } else {
175                        None
176                    }
177                }
178            }
179        )*
180    };
181}
182
183try_from_js_value_signed_int!(i8, i16, i32);
184try_from_js_value_unsigned_int!(u8, u16, u32);
185
186impl convert::TryFromJsValue for f32 {
187    fn try_from_js_value_ref(val: &JsValue) -> Option<f32> {
188        val.as_f64().map(|n| n as f32)
189    }
190}
191
192impl convert::TryFromJsValue for f64 {
193    fn try_from_js_value_ref(val: &JsValue) -> Option<f64> {
194        val.as_f64()
195    }
196}
197
198impl convert::TryFromJsValue for i64 {
199    fn try_from_js_value_ref(val: &JsValue) -> Option<i64> {
200        crate::js_helpers::js_bigint_get_as_i64(val)
201    }
202}
203
204impl convert::TryFromJsValue for u64 {
205    fn try_from_js_value_ref(val: &JsValue) -> Option<u64> {
206        crate::js_helpers::js_bigint_to_string(val)?.parse().ok()
207    }
208}
209
210impl convert::TryFromJsValue for i128 {
211    fn try_from_js_value_ref(v: &JsValue) -> Option<i128> {
212        crate::js_helpers::js_bigint_to_string(v)?.parse().ok()
213    }
214}
215
216impl convert::TryFromJsValue for u128 {
217    fn try_from_js_value_ref(v: &JsValue) -> Option<u128> {
218        crate::js_helpers::js_bigint_to_string(v)?.parse().ok()
219    }
220}
221
222impl convert::TryFromJsValue for isize {
223    fn try_from_js_value_ref(val: &JsValue) -> Option<isize> {
224        val.as_f64().map(|n| n as isize)
225    }
226}
227
228impl convert::TryFromJsValue for usize {
229    fn try_from_js_value_ref(val: &JsValue) -> Option<usize> {
230        val.as_f64().map(|n| n as usize)
231    }
232}
233
234to_js_value!(i8);
235from_js_value!(i8);
236to_js_value!(i16);
237from_js_value!(i16);
238to_js_value!(i32);
239from_js_value!(i32);
240to_js_value!(i64);
241to_js_value!(i128);
242to_js_value!(u8);
243from_js_value!(u8);
244to_js_value!(u16);
245from_js_value!(u16);
246to_js_value!(u32);
247from_js_value!(u32);
248to_js_value!(u64);
249to_js_value!(u128);
250to_js_value!(f32);
251from_js_value!(f32);
252to_js_value!(f64);
253to_js_value!(usize);
254from_js_value!(usize);
255to_js_value!(isize);
256from_js_value!(isize);
257impl<'a> From<&'a str> for JsValue {
258    fn from(s: &'a str) -> JsValue {
259        cast! {(String => JsValue) s.to_string()}
260    }
261}
262impl<'a> From<&'a String> for JsValue {
263    fn from(s: &'a String) -> JsValue {
264        cast! {(String => JsValue) s.clone()}
265    }
266}
267impl<'a, T> From<&'a T> for JsValue
268where
269    T: JsCast,
270{
271    fn from(s: &'a T) -> JsValue {
272        s.as_ref().clone()
273    }
274}
275impl From<String> for JsValue {
276    fn from(s: String) -> JsValue {
277        cast! {(String => JsValue) s}
278    }
279}
280to_js_value!(());
281from_js_value!(());
282
283impl<T> From<Option<T>> for JsValue
284where
285    T: Into<JsValue>,
286{
287    fn from(s: Option<T>) -> JsValue {
288        match s {
289            Some(s) => s.into(),
290            None => JsValue::undefined(),
291        }
292    }
293}