rquickjs_core/value/convert/
into.rs

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/// Convert the either into JS
148#[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/// Convert the either into JS
164#[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        // TODO: determine we should panic her or just unpack the value.
222        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        // TODO: determine we should panic her or just unpack the value.
233        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        // TODO: determine we should panic her or just unpack the value.
244        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        // TODO: determine we should panic her or just unpack the value.
255        self.read().expect("lock was poisoned").into_js(ctx)
256    }
257}
258
259macro_rules! into_js_impls {
260    // for cells
261    (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    // for tuple types
275    (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    // for list-like Rust types
293    (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    // for map-like Rust types
322    (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    // for primitive types using `new` function
353    (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    // for primitive types with two alternatives
372    // (ex. u32 may try convert as i32 or else as f64)
373    (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    /// Convert from Rust vector to JS array
443    Vec,
444    /// Convert from Rust vector deque to JS array
445    VecDeque,
446    /// Convert from Rust linked list to JS array
447    LinkedList,
448    /// Convert from Rust hash set to JS array
449    #[cfg(feature = "std")]
450    HashSet {S},
451    /// Convert from hashbrown hash set to JS array
452    HashbrownSet {S},
453    /// Convert from Rust btree set to JS array
454    BTreeSet,
455    /// Convert from Rust index set to JS array
456    #[cfg(feature = "indexmap")]
457    #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "indexmap")))]
458    IndexSet {S},
459}
460
461into_js_impls! {
462    map:
463    /// Convert from Rust hash map to JS object
464    #[cfg(feature = "std")]
465    HashMap {S},
466    /// Convert from hashbrown hash map to JS object
467    HashbrownMap {S},
468    /// Convert from Rust btree map to JS object
469    BTreeMap,
470    /// Convert from Rust index map to JS object
471    #[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            // since unix epoch
500            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            // before unix epoch
514            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        // wasm32-wasip1 and wasm32-wasip2 do not support SystemTime before the Unix epoch.
590        // The subtraction from the Unix epoch would panic with: overflow when subtracting duration from instant
591        #[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}