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