boa_engine/value/conversions/
try_into_js.rs

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