1use crate::{
2 convert::{IteratorJs, List},
3 value::Constructor,
4 Array, CString, Ctx, Error, IntoAtom, IntoJs, Object, Result, StdResult, StdString, String,
5 Value,
6};
7use alloc::{
8 boxed::Box,
9 collections::{BTreeMap, BTreeSet, LinkedList, VecDeque},
10 string::ToString as _,
11 vec::Vec,
12};
13use core::cell::{Cell, RefCell};
14use hashbrown::{HashMap as HashbrownMap, HashSet as HashbrownSet};
15
16#[cfg(feature = "std")]
17use std::{
18 collections::{HashMap, HashSet},
19 sync::{Mutex, RwLock},
20 time::SystemTime,
21};
22
23#[cfg(feature = "either")]
24use either::{Either, Left, Right};
25
26#[cfg(feature = "indexmap")]
27use indexmap::{IndexMap, IndexSet};
28
29impl<'js> IntoJs<'js> for Value<'js> {
30 fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
31 Ok(self)
32 }
33}
34
35impl<'js> IntoJs<'js> for &Value<'js> {
36 fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
37 Ok(self.clone())
38 }
39}
40
41impl<'js> IntoJs<'js> for StdString {
42 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
43 self.as_str().into_js(ctx)
44 }
45}
46
47impl<'js> IntoJs<'js> for &StdString {
48 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
49 self.as_str().into_js(ctx)
50 }
51}
52
53impl<'js> IntoJs<'js> for &str {
54 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
55 String::from_str(ctx.clone(), self).map(|String(value)| value)
56 }
57}
58
59impl<'js> IntoJs<'js> for char {
60 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
61 String::from_str(ctx.clone(), self.to_string().as_str()).map(|String(value)| value)
62 }
63}
64
65impl<'js> IntoJs<'js> for CString<'js> {
66 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
67 String::from_str(ctx.clone(), self.as_str()).map(|String(value)| value)
68 }
69}
70
71impl<'js> IntoJs<'js> for &CString<'js> {
72 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
73 String::from_str(ctx.clone(), self.as_str()).map(|String(value)| value)
74 }
75}
76
77impl<'js, T> IntoJs<'js> for &[T]
78where
79 for<'a> &'a T: IntoJs<'js>,
80{
81 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
82 self.iter()
83 .collect_js(ctx)
84 .map(|Array(value)| value.into_value())
85 }
86}
87
88impl<'js> IntoJs<'js> for () {
89 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
90 Ok(Value::new_undefined(ctx.clone()))
91 }
92}
93
94impl<'js> IntoJs<'js> for &() {
95 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
96 Ok(Value::new_undefined(ctx.clone()))
97 }
98}
99
100impl<'js, T> IntoJs<'js> for Option<T>
101where
102 T: IntoJs<'js>,
103{
104 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
105 Ok(match self {
106 Some(value) => value.into_js(ctx)?,
107 _ => Value::new_undefined(ctx.clone()),
108 })
109 }
110}
111
112impl<'js, T> IntoJs<'js> for &Option<T>
113where
114 for<'a> &'a T: IntoJs<'js>,
115{
116 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
117 Ok(match self {
118 Some(value) => value.into_js(ctx)?,
119 _ => Value::new_undefined(ctx.clone()),
120 })
121 }
122}
123
124impl<'js, T, E> IntoJs<'js> for StdResult<T, E>
125where
126 T: IntoJs<'js>,
127 Error: From<E>,
128{
129 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
130 self.map_err(Error::from)
131 .and_then(|value| value.into_js(ctx))
132 }
133}
134
135impl<'js, T, E> IntoJs<'js> for &StdResult<T, E>
136where
137 for<'a> &'a T: IntoJs<'js>,
138 for<'a> Error: From<&'a E>,
139{
140 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
141 self.as_ref()
142 .map_err(Error::from)
143 .and_then(|value| value.into_js(ctx))
144 }
145}
146
147#[cfg(feature = "either")]
149#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "either")))]
150impl<'js, L, R> IntoJs<'js> for Either<L, R>
151where
152 L: IntoJs<'js>,
153 R: IntoJs<'js>,
154{
155 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
156 match self {
157 Left(value) => value.into_js(ctx),
158 Right(value) => value.into_js(ctx),
159 }
160 }
161}
162
163#[cfg(feature = "either")]
165#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "either")))]
166impl<'js, L, R> IntoJs<'js> for &Either<L, R>
167where
168 for<'a> &'a L: IntoJs<'js>,
169 for<'a> &'a R: IntoJs<'js>,
170{
171 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
172 match self {
173 Left(value) => value.into_js(ctx),
174 Right(value) => value.into_js(ctx),
175 }
176 }
177}
178
179impl<'js, T> IntoJs<'js> for &Box<T>
180where
181 for<'r> &'r T: IntoJs<'js>,
182{
183 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
184 self.as_ref().into_js(ctx)
185 }
186}
187
188impl<'js, T> IntoJs<'js> for Box<T>
189where
190 T: IntoJs<'js>,
191{
192 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
193 (*self).into_js(ctx)
194 }
195}
196
197impl<'js, T> IntoJs<'js> for &Cell<T>
198where
199 T: IntoJs<'js> + Copy,
200{
201 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
202 self.get().into_js(ctx)
203 }
204}
205
206impl<'js, T> IntoJs<'js> for &RefCell<T>
207where
208 for<'r> &'r T: IntoJs<'js>,
209{
210 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
211 self.borrow().into_js(ctx)
212 }
213}
214
215#[cfg(feature = "std")]
216impl<'js, T> IntoJs<'js> for Mutex<T>
217where
218 T: IntoJs<'js>,
219{
220 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
221 self.into_inner().expect("mutex was poisoned").into_js(ctx)
223 }
224}
225
226#[cfg(feature = "std")]
227impl<'js, T> IntoJs<'js> for &Mutex<T>
228where
229 for<'r> &'r T: IntoJs<'js>,
230{
231 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
232 self.lock().expect("mutex was poisoned").into_js(ctx)
234 }
235}
236
237#[cfg(feature = "std")]
238impl<'js, T> IntoJs<'js> for RwLock<T>
239where
240 T: IntoJs<'js>,
241{
242 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
243 self.into_inner().expect("lock was poisoned").into_js(ctx)
245 }
246}
247
248#[cfg(feature = "std")]
249impl<'js, T> IntoJs<'js> for &RwLock<T>
250where
251 for<'r> &'r T: IntoJs<'js>,
252{
253 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
254 self.read().expect("lock was poisoned").into_js(ctx)
256 }
257}
258
259macro_rules! into_js_impls {
260 (cell: $($type:ident,)*) => {
262 $(
263 impl<'js, T> IntoJs<'js> for $type<T>
264 where
265 T: IntoJs<'js>,
266 {
267 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
268 self.into_inner().into_js(ctx)
269 }
270 }
271 )*
272 };
273
274 (tup: $($($type:ident)*,)*) => {
276 $(
277 impl<'js, $($type,)*> IntoJs<'js> for List<($($type,)*)>
278 where
279 $($type: IntoJs<'js>,)*
280 {
281 #[allow(non_snake_case)]
282 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
283 let ($($type,)*) = self.0;
284 let array = Array::new(ctx.clone())?;
285 $(array.set(into_js_impls!(@idx $type), $type)?;)*
286 Ok(array.into_value())
287 }
288 }
289 )*
290 };
291
292 (list: $($(#[$meta:meta])* $type:ident $({$param:ident})*,)*) => {
294 $(
295 $(#[$meta])*
296 impl<'js, T $(,$param)*> IntoJs<'js> for $type<T $(,$param)*>
297 where
298 T: IntoJs<'js>,
299 {
300 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
301 self.into_iter()
302 .collect_js(ctx)
303 .map(|Array(value)| value.into_value())
304 }
305 }
306
307 $(#[$meta])*
308 impl<'js, T $(,$param)*> IntoJs<'js> for &$type<T $(,$param)*>
309 where
310 for<'a> &'a T: IntoJs<'js>,
311 {
312 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
313 self.into_iter()
314 .collect_js(ctx)
315 .map(|Array(value)| value.into_value())
316 }
317 }
318 )*
319 };
320
321 (map: $($(#[$meta:meta])* $type:ident $({$param:ident})*,)*) => {
323 $(
324 $(#[$meta])*
325 impl<'js, K, V $(,$param)*> IntoJs<'js> for $type<K, V $(,$param)*>
326 where
327 K: IntoAtom<'js>,
328 V: IntoJs<'js>,
329 {
330 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
331 self.into_iter()
332 .collect_js(ctx)
333 .map(|Object(value)| value)
334 }
335 }
336
337 $(#[$meta])*
338 impl<'js, K, V $(,$param)*> IntoJs<'js> for &$type<K, V $(,$param)*>
339 where
340 for<'a> &'a K: IntoAtom<'js>,
341 for<'a> &'a V: IntoJs<'js>,
342 {
343 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
344 self.into_iter()
345 .collect_js(ctx)
346 .map(|Object(value)| value)
347 }
348 }
349 )*
350 };
351
352 (val: $($new:ident: $($type:ident)*,)*) => {
354 $(
355 $(
356 impl<'js> IntoJs<'js> for $type {
357 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
358 Ok(Value::$new(ctx.clone(), self as _))
359 }
360 }
361
362 impl<'js> IntoJs<'js> for &$type {
363 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
364 (*self).into_js(ctx)
365 }
366 }
367 )*
368 )*
369 };
370
371 (val: $($alt1:ident $alt2:ident => $($type:ty)*,)*) => {
374 $(
375 $(
376 impl<'js> IntoJs<'js> for $type {
377 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
378 let val = self as $alt1;
379 if val as $type == self {
380 val.into_js(ctx)
381 } else {
382 (self as $alt2).into_js(ctx)
383 }
384 }
385 }
386
387 impl<'js> IntoJs<'js> for &$type {
388 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
389 (*self).into_js(ctx)
390 }
391 }
392 )*
393 )*
394 };
395
396 (@idx A) => { 0 };
397 (@idx B) => { 1 };
398 (@idx C) => { 2 };
399 (@idx D) => { 3 };
400 (@idx E) => { 4 };
401 (@idx F) => { 5 };
402 (@idx G) => { 6 };
403 (@idx H) => { 7 };
404 (@idx I) => { 8 };
405 (@idx J) => { 9 };
406 (@idx K) => { 10 };
407 (@idx L) => { 11 };
408 (@idx M) => { 12 };
409 (@idx N) => { 13 };
410 (@idx O) => { 14 };
411 (@idx P) => { 15 };
412}
413
414into_js_impls! {
415 cell:
416 Cell,
417 RefCell,
418}
419
420into_js_impls! {
421 tup:
422 A,
423 A B,
424 A B C,
425 A B C D,
426 A B C D E,
427 A B C D E F,
428 A B C D E F G,
429 A B C D E F G H,
430 A B C D E F G H I,
431 A B C D E F G H I J,
432 A B C D E F G H I J K,
433 A B C D E F G H I J K L,
434 A B C D E F G H I J K L M,
435 A B C D E F G H I J K L M N,
436 A B C D E F G H I J K L M N O,
437 A B C D E F G H I J K L M N O P,
438}
439
440into_js_impls! {
441 list:
442 Vec,
444 VecDeque,
446 LinkedList,
448 #[cfg(feature = "std")]
450 HashSet {S},
451 HashbrownSet {S},
453 BTreeSet,
455 #[cfg(feature = "indexmap")]
457 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "indexmap")))]
458 IndexSet {S},
459}
460
461into_js_impls! {
462 map:
463 #[cfg(feature = "std")]
465 HashMap {S},
466 HashbrownMap {S},
468 BTreeMap,
470 #[cfg(feature = "indexmap")]
472 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "indexmap")))]
473 IndexMap {S},
474}
475
476into_js_impls! {
477 val:
478 new_bool: bool,
479 new_int: i8 i16 i32 u8 u16,
480 new_float: f32 f64,
481}
482
483into_js_impls! {
484 val:
485 i32 f64 => i64 u32 u64 usize isize,
486}
487
488#[allow(dead_code)]
489fn millis_to_date<'js>(ctx: &Ctx<'js>, millis: i64) -> Result<Value<'js>> {
490 let date_ctor: Constructor = ctx.globals().get("Date")?;
491
492 date_ctor.construct((millis,))
493}
494
495#[cfg(feature = "std")]
496impl<'js> IntoJs<'js> for SystemTime {
497 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
498 let millis = match self.duration_since(SystemTime::UNIX_EPOCH) {
499 Ok(duration) => {
501 let millis = duration.as_millis();
502
503 if millis > i64::MAX as _ {
504 return Err(Error::new_into_js_message(
505 "SystemTime",
506 "Date",
507 "Timestamp too big",
508 ));
509 }
510
511 millis as i64
512 }
513 Err(error) => {
515 let millis = error.duration().as_millis();
516
517 if millis > -(i64::MIN as i128) as _ {
518 return Err(Error::new_into_js_message(
519 "SystemTime",
520 "Date",
521 "Timestamp too small",
522 ));
523 }
524
525 (-(millis as i128)) as i64
526 }
527 };
528
529 millis_to_date(ctx, millis)
530 }
531}
532
533#[cfg(feature = "chrono")]
534impl<'js, Tz: chrono::TimeZone> IntoJs<'js> for chrono::DateTime<Tz> {
535 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
536 millis_to_date(ctx, self.timestamp_millis())
537 }
538}
539
540#[cfg(test)]
541mod test {
542
543 #[test]
544 fn char_to_js() {
545 use crate::{Context, IntoJs, Runtime};
546 let runtime = Runtime::new().unwrap();
547 let ctx = Context::full(&runtime).unwrap();
548
549 let c = 'a';
550
551 ctx.with(|ctx| {
552 let globs = ctx.globals();
553 globs.set("char", c.into_js(&ctx).unwrap()).unwrap();
554 let res: char = ctx.eval("globalThis.char").unwrap();
555 assert_eq!(c, res);
556
557 let rt = ctx.eval::<char, _>("''");
558 assert!(rt.is_err());
559 let rt = ctx.eval::<char, _>("'a'");
560 assert!(rt.is_ok());
561 let rt = ctx.eval::<char, _>("'ab'");
562 assert!(rt.is_err());
563 });
564 }
565
566 #[test]
567 fn system_time_to_js() {
568 use crate::{Context, IntoJs, Runtime};
569 #[cfg(not(target_arch = "wasm32"))]
570 use std::time::Duration;
571 use std::time::SystemTime;
572
573 let runtime = Runtime::new().unwrap();
574 let ctx = Context::full(&runtime).unwrap();
575
576 let ts = SystemTime::now();
577 let millis = ts
578 .duration_since(SystemTime::UNIX_EPOCH)
579 .unwrap()
580 .as_millis();
581
582 ctx.with(|ctx| {
583 let globs = ctx.globals();
584 globs.set("ts", ts.into_js(&ctx).unwrap()).unwrap();
585 let res: i64 = ctx.eval("ts.getTime()").unwrap();
586 assert_eq!(millis, res as _);
587 });
588
589 #[cfg(not(target_arch = "wasm32"))]
592 {
593 let ts = SystemTime::UNIX_EPOCH - Duration::from_millis(123456);
594 let millis = SystemTime::UNIX_EPOCH
595 .duration_since(ts)
596 .unwrap()
597 .as_millis();
598
599 ctx.with(|ctx| {
600 let globs = ctx.globals();
601 globs.set("ts", ts.into_js(&ctx).unwrap()).unwrap();
602 let res: i64 = ctx.eval("ts.getTime()").unwrap();
603 assert_eq!(-(millis as i64), res as _);
604 });
605 }
606 }
607
608 #[cfg(feature = "chrono")]
609 #[test]
610 fn chrono_to_js() {
611 use crate::{Context, IntoJs, Runtime};
612 use chrono::Utc;
613
614 let runtime = Runtime::new().unwrap();
615 let ctx = Context::full(&runtime).unwrap();
616
617 let ts = Utc::now();
618 let millis = ts.timestamp_millis();
619
620 ctx.with(|ctx| {
621 let globs = ctx.globals();
622 globs.set("ts", ts.into_js(&ctx).unwrap()).unwrap();
623 let res: i64 = ctx.eval("ts.getTime()").unwrap();
624 assert_eq!(millis, res);
625 });
626 }
627}