Skip to main content

boa_engine/value/conversions/
try_into_js.rs

1use crate::class::Class;
2use crate::{Context, JsNativeError, JsResult, JsString, JsValue};
3
4/// This trait adds a conversions from a Rust Type into [`JsValue`].
5pub trait TryIntoJs: Sized {
6    /// This function tries to convert a `Self` into [`JsValue`].
7    fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue>;
8}
9
10impl<T> TryIntoJs for T
11where
12    T: Class + Clone,
13{
14    fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
15        T::from_data(self.clone(), context).map(JsValue::from)
16    }
17}
18
19impl TryIntoJs for bool {
20    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
21        Ok(JsValue::from(*self))
22    }
23}
24
25impl TryIntoJs for &str {
26    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
27        Ok(JsValue::from(JsString::from(*self)))
28    }
29}
30impl TryIntoJs for String {
31    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
32        Ok(JsValue::from(JsString::from(self.as_str())))
33    }
34}
35
36macro_rules! impl_try_into_js_by_from {
37    ($t:ty) => {
38        impl TryIntoJs for $t {
39            fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
40                Ok(JsValue::from(self.clone()))
41            }
42        }
43    };
44    [$($ts:ty),+] => {
45        $(impl_try_into_js_by_from!($ts);)+
46    }
47}
48impl_try_into_js_by_from![i8, u8, i16, u16, i32, u32, f32, f64];
49impl_try_into_js_by_from![
50    JsValue,
51    JsString,
52    crate::JsBigInt,
53    crate::JsObject,
54    crate::JsSymbol,
55    crate::object::JsArray,
56    crate::object::JsArrayBuffer,
57    crate::object::JsDataView,
58    crate::object::JsDate,
59    crate::object::JsFunction,
60    crate::object::JsGenerator,
61    crate::object::JsMapIterator,
62    crate::object::JsMap,
63    crate::object::JsSetIterator,
64    crate::object::JsSet,
65    crate::object::JsSharedArrayBuffer,
66    crate::object::JsInt8Array,
67    crate::object::JsInt16Array,
68    crate::object::JsInt32Array,
69    crate::object::JsUint8Array,
70    crate::object::JsUint16Array,
71    crate::object::JsUint32Array,
72    crate::object::JsFloat32Array,
73    crate::object::JsFloat64Array
74];
75
76const MAX_SAFE_INTEGER_I64: i64 = (1 << 53) - 1;
77const MIN_SAFE_INTEGER_I64: i64 = -MAX_SAFE_INTEGER_I64;
78
79fn err_outside_safe_range() -> crate::JsError {
80    JsNativeError::typ()
81        .with_message("cannot convert value into JsValue: the value is outside the safe range")
82        .into()
83}
84fn convert_safe_i64(value: i64) -> JsValue {
85    i32::try_from(value).map_or(JsValue::from(value as f64), JsValue::new)
86}
87
88impl TryIntoJs for i64 {
89    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
90        let value = *self;
91        if (MIN_SAFE_INTEGER_I64..MAX_SAFE_INTEGER_I64).contains(&value) {
92            Ok(convert_safe_i64(value))
93        } else {
94            Err(err_outside_safe_range())
95        }
96    }
97}
98impl TryIntoJs for u64 {
99    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
100        let value = *self;
101        if (MAX_SAFE_INTEGER_I64 as u64) < value {
102            Err(err_outside_safe_range())
103        } else {
104            Ok(convert_safe_i64(value as i64))
105        }
106    }
107}
108impl TryIntoJs for isize {
109    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
110        let value = *self as i64;
111        if (MIN_SAFE_INTEGER_I64..MAX_SAFE_INTEGER_I64).contains(&value) {
112            Ok(convert_safe_i64(value))
113        } else {
114            Err(err_outside_safe_range())
115        }
116    }
117}
118impl TryIntoJs for usize {
119    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
120        let value = *self;
121        if (MAX_SAFE_INTEGER_I64 as usize) < value {
122            Err(err_outside_safe_range())
123        } else {
124            Ok(convert_safe_i64(value as i64))
125        }
126    }
127}
128impl TryIntoJs for i128 {
129    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
130        let value = *self;
131        if value < i128::from(MIN_SAFE_INTEGER_I64) || i128::from(MAX_SAFE_INTEGER_I64) < value {
132            Err(err_outside_safe_range())
133        } else {
134            Ok(convert_safe_i64(value as i64))
135        }
136    }
137}
138impl TryIntoJs for u128 {
139    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
140        let value = *self;
141        if (MAX_SAFE_INTEGER_I64 as u128) < value {
142            Err(err_outside_safe_range())
143        } else {
144            Ok(convert_safe_i64(value as i64))
145        }
146    }
147}
148
149impl<T> TryIntoJs for Option<T>
150where
151    T: TryIntoJs,
152{
153    fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
154        match self {
155            Some(x) => x.try_into_js(context),
156            None => Ok(JsValue::undefined()),
157        }
158    }
159}
160
161impl<T> TryIntoJs for Vec<T>
162where
163    T: TryIntoJs,
164{
165    fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
166        let arr = crate::object::JsArray::new(context)?;
167        for value in self {
168            let value = value.try_into_js(context)?;
169            arr.push(value, context)?;
170        }
171        Ok(arr.into())
172    }
173}
174
175macro_rules! impl_try_into_js_for_tuples {
176    ($($names:ident : $ts:ident),+) => {
177        impl<$($ts: TryIntoJs,)+> TryIntoJs for ($($ts,)+) {
178            fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
179                let ($($names,)+) = self;
180                let arr = crate::object::JsArray::new(context)?;
181                $(arr.push($names.try_into_js(context)?, context)?;)+
182                Ok(arr.into())
183            }
184        }
185    };
186}
187
188impl_try_into_js_for_tuples!(a: A);
189impl_try_into_js_for_tuples!(a: A, b: B);
190impl_try_into_js_for_tuples!(a: A, b: B, c: C);
191impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D);
192impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E);
193impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F);
194impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
195impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
196impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
197impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
198impl_try_into_js_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K);
199
200impl TryIntoJs for () {
201    fn try_into_js(&self, _context: &mut Context) -> JsResult<JsValue> {
202        Ok(JsValue::null())
203    }
204}
205
206impl<T, S> TryIntoJs for std::collections::HashSet<T, S>
207where
208    T: TryIntoJs,
209{
210    fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
211        let set = crate::object::JsSet::new(context);
212        for value in self {
213            let value = value.try_into_js(context)?;
214            set.add(value, context)?;
215        }
216        Ok(set.into())
217    }
218}
219
220impl<K, V, S> TryIntoJs for std::collections::HashMap<K, V, S>
221where
222    K: TryIntoJs,
223    V: TryIntoJs,
224{
225    fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
226        let map = crate::object::JsMap::new(context);
227        for (key, value) in self {
228            let key = key.try_into_js(context)?;
229            let value = value.try_into_js(context)?;
230            map.set(key, value, context)?;
231        }
232        Ok(map.into())
233    }
234}
235
236#[cfg(test)]
237mod try_into_js_tests {
238    use crate::value::{TryFromJs, TryIntoJs};
239    use crate::{Context, JsResult};
240
241    #[test]
242    fn big_int_err() {
243        fn assert<T: TryIntoJs>(int: &T, context: &mut Context) {
244            let expect_err = int.try_into_js(context);
245            assert!(expect_err.is_err());
246        }
247
248        let mut context = Context::default();
249        let context = &mut context;
250
251        let int = (1 << 55) + 17i64;
252        assert(&int, context);
253
254        let int = (1 << 55) + 17u64;
255        assert(&int, context);
256
257        let int = (1 << 55) + 17u128;
258        assert(&int, context);
259
260        let int = (1 << 55) + 17i128;
261        assert(&int, context);
262    }
263
264    #[test]
265    fn int_tuple() -> JsResult<()> {
266        let mut context = Context::default();
267        let context = &mut context;
268
269        let tuple_initial = (
270            -42i8,
271            42u8,
272            1764i16,
273            7641u16,
274            -((1 << 27) + 13),
275            (1 << 27) + 72u32,
276            (1 << 49) + 1793i64,
277            (1 << 49) + 1793u64,
278            -((1 << 49) + 7193i128),
279            (1 << 49) + 9173u128,
280        );
281
282        // it will rewrite without reading, so it's just for auto type resolving.
283        #[allow(unused_assignments)]
284        let mut tuple_after_transform = tuple_initial;
285
286        let js_value = tuple_initial.try_into_js(context)?;
287        tuple_after_transform = TryFromJs::try_from_js(&js_value, context)?;
288
289        assert_eq!(tuple_initial, tuple_after_transform);
290        Ok(())
291    }
292
293    #[test]
294    fn string() -> JsResult<()> {
295        let mut context = Context::default();
296        let context = &mut context;
297
298        let s_init = "String".to_string();
299        let js_value = s_init.try_into_js(context)?;
300        let s: String = TryFromJs::try_from_js(&js_value, context)?;
301        assert_eq!(s_init, s);
302        Ok(())
303    }
304
305    #[test]
306    fn vec() -> JsResult<()> {
307        let mut context = Context::default();
308        let context = &mut context;
309
310        let vec_init = vec![(-4i64, 2u64), (15, 15), (32, 23)];
311        let js_value = vec_init.try_into_js(context)?;
312        println!("JsValue: {}", js_value.display());
313        let vec: Vec<(i64, u64)> = TryFromJs::try_from_js(&js_value, context)?;
314        assert_eq!(vec_init, vec);
315        Ok(())
316    }
317}