1use crate::class::Class;
2use crate::{Context, JsNativeError, JsResult, JsString, JsValue};
3
4pub trait TryIntoJs: Sized {
6 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 #[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}