1use crate::{
2 convert::List, Array, CString, Ctx, Error, FromAtom, FromJs, Object, Result, StdString, String,
3 Type, Value,
4};
5use alloc::{
6 boxed::Box,
7 collections::{BTreeMap, BTreeSet, LinkedList, VecDeque},
8 rc::Rc,
9 sync::Arc,
10 vec::Vec,
11};
12#[allow(unused_imports)]
13use core::{
14 cell::{Cell, RefCell},
15 hash::{BuildHasher, Hash},
16 time::Duration,
17};
18use hashbrown::{HashMap as HashbrownMap, HashSet as HashbrownSet};
19
20#[cfg(feature = "std")]
21use std::{
22 collections::{HashMap, HashSet},
23 sync::{Mutex, RwLock},
24 time::SystemTime,
25};
26
27#[cfg(feature = "either")]
28use either::{Either, Left, Right};
29
30#[cfg(feature = "indexmap")]
31use indexmap::{IndexMap, IndexSet};
32
33impl<'js> FromJs<'js> for Value<'js> {
34 fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
35 Ok(value)
36 }
37}
38
39impl<'js> FromJs<'js> for StdString {
40 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
41 String::from_value(value).and_then(|string| string.to_string())
42 }
43}
44
45impl<'js> FromJs<'js> for char {
46 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
47 let type_name = value.type_name();
48 let s = String::from_value(value)?.to_string()?;
49
50 let mut chars = s.chars();
51 let (c, more) = (chars.next(), chars.next());
52
53 match (c, more) {
54 (Some(c), None) => Ok(c),
55 _ => Err(Error::FromJs {
56 from: type_name,
57 to: "char",
58 message: Some("The length of the string converted to char must be 1".into()),
59 }),
60 }
61 }
62}
63
64impl<'js> FromJs<'js> for CString<'js> {
65 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
66 String::from_value(value)?.to_cstring()
67 }
68}
69
70impl<'js> FromJs<'js> for () {
72 fn from_js(_: &Ctx<'js>, _: Value<'js>) -> Result<Self> {
73 Ok(())
74 }
75}
76
77impl<'js, T> FromJs<'js> for Option<T>
79where
80 T: FromJs<'js>,
81{
82 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
83 if value.type_of().is_void() {
84 Ok(None)
85 } else {
86 T::from_js(ctx, value).map(Some)
87 }
88 }
89}
90
91impl<'js, T> FromJs<'js> for Result<T>
93where
94 T: FromJs<'js>,
95{
96 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
99 unsafe {
100 match ctx.handle_exception(value.into_js_value()) {
101 Ok(val) => T::from_js(ctx, Value::from_js_value(ctx.clone(), val)).map(Ok),
102 Err(error) => Ok(Err(error)),
103 }
104 }
105 }
106}
107
108#[cfg(feature = "either")]
110#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "either")))]
111impl<'js, L, R> FromJs<'js> for Either<L, R>
112where
113 L: FromJs<'js>,
114 R: FromJs<'js>,
115{
116 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
117 L::from_js(ctx, value.clone()).map(Left).or_else(|error| {
118 if error.is_from_js() {
119 R::from_js(ctx, value).map(Right)
120 } else {
121 Err(error)
122 }
123 })
124 }
125}
126
127fn tuple_match_size(actual: usize, expected: usize) -> Result<()> {
128 if actual == expected {
129 Ok(())
130 } else {
131 Err(Error::new_from_js_message(
132 "array",
133 "tuple",
134 if actual < expected {
135 "Not enough values"
136 } else {
137 "Too many values"
138 },
139 ))
140 }
141}
142
143fn number_match_range<T: PartialOrd>(
144 val: T,
145 min: T,
146 max: T,
147 from: &'static str,
148 to: &'static str,
149) -> Result<()> {
150 if val < min {
151 Err(Error::new_from_js_message(from, to, "Underflow"))
152 } else if val > max {
153 Err(Error::new_from_js_message(from, to, "Overflow"))
154 } else {
155 Ok(())
156 }
157}
158
159macro_rules! from_js_impls {
160 (ref: $($(#[$meta:meta])* $type:ident,)*) => {
162 $(
163 $(#[$meta])*
164 impl<'js, T> FromJs<'js> for $type<T>
165 where
166 T: FromJs<'js>,
167 {
168 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
169 T::from_js(ctx, value).map($type::new)
170 }
171 }
172 )*
173 };
174
175 (tup: $($($type:ident)*,)*) => {
177 $(
178 impl<'js, $($type,)*> FromJs<'js> for List<($($type,)*)>
179 where
180 $($type: FromJs<'js>,)*
181 {
182 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
183 let array = Array::from_value(value)?;
184
185 let tuple_len = 0 $(+ from_js_impls!(@one $type))*;
186 let array_len = array.len();
187 tuple_match_size(array_len, tuple_len)?;
188
189 Ok(List((
190 $(array.get::<$type>(from_js_impls!(@idx $type))?,)*
191 )))
192 }
193 }
194 )*
195 };
196
197 (list: $($(#[$meta:meta])* $type:ident $({$param:ident: $($pguard:tt)*})* $(($($guard:tt)*))*,)*) => {
199 $(
200 $(#[$meta])*
201 impl<'js, T $(,$param)*> FromJs<'js> for $type<T $(,$param)*>
202 where
203 T: FromJs<'js> $(+ $($guard)*)*,
204 $($param: $($pguard)*,)*
205 {
206 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
207 let array = Array::from_value(value)?;
208 array.iter().collect::<Result<_>>()
209 }
210 }
211 )*
212 };
213
214 (map: $($(#[$meta:meta])* $type:ident $({$param:ident: $($pguard:tt)*})* $(($($guard:tt)*))*,)*) => {
216 $(
217 $(#[$meta])*
218 impl<'js, K, V $(,$param)*> FromJs<'js> for $type<K, V $(,$param)*>
219 where
220 K: FromAtom<'js> $(+ $($guard)*)*,
221 V: FromJs<'js>,
222 $($param: $($pguard)*,)*
223 {
224 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
225 let object = Object::from_value(value)?;
226 object.props().collect::<Result<_>>()
227 }
228 }
229 )*
230 };
231
232 (val: $($type:ty => $($jstype:ident $getfn:ident)*,)*) => {
235 $(
236 impl<'js> FromJs<'js> for $type {
237 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
238 let type_ = value.type_of();
239 match type_ {
240 $(Type::$jstype => Ok(unsafe { value.$getfn() } as _),)*
241 _ => Err(Error::new_from_js(type_.as_str(), stringify!($type))),
242 }
243 }
244 }
245 )*
246 };
247
248 (val: $($base:ident: $($type:ident)*,)*) => {
250 $(
251 $(
252 impl<'js> FromJs<'js> for $type {
253 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
254 let num = <$base>::from_js(ctx, value)?;
255 number_match_range(num, $type::MIN as $base, $type::MAX as $base, stringify!($base), stringify!($type))?;
256 Ok(num as $type)
257 }
258 }
259 )*
260 )*
261 };
262
263 (@one $($t:tt)*) => { 1 };
264
265 (@idx A) => { 0 };
266 (@idx B) => { 1 };
267 (@idx C) => { 2 };
268 (@idx D) => { 3 };
269 (@idx E) => { 4 };
270 (@idx F) => { 5 };
271 (@idx G) => { 6 };
272 (@idx H) => { 7 };
273 (@idx I) => { 8 };
274 (@idx J) => { 9 };
275 (@idx K) => { 10 };
276 (@idx L) => { 11 };
277 (@idx M) => { 12 };
278 (@idx N) => { 13 };
279 (@idx O) => { 14 };
280 (@idx P) => { 15 };
281}
282
283from_js_impls! {
284 val:
285 i32: i8 u8 i16 u16,
286 f64: u32 u64 i64 usize isize,
287}
288
289from_js_impls! {
290 val:
291 bool => Bool get_bool,
292 i32 => Float get_float Int get_int,
293 f64 => Float get_float Int get_int,
294}
295
296from_js_impls! {
297 ref:
298 Box,
299 Rc,
300 Arc,
301 Cell,
302 RefCell,
303 #[cfg(feature = "std")]
304 Mutex,
305 #[cfg(feature = "std")]
306 RwLock,
307}
308
309from_js_impls! {
310 tup:
311 A,
312 A B,
313 A B C,
314 A B C D,
315 A B C D E,
316 A B C D E F,
317 A B C D E F G,
318 A B C D E F G H,
319 A B C D E F G H I,
320 A B C D E F G H I J,
321 A B C D E F G H I J K,
322 A B C D E F G H I J K L,
323 A B C D E F G H I J K L M,
324 A B C D E F G H I J K L M N,
325 A B C D E F G H I J K L M N O,
326 A B C D E F G H I J K L M N O P,
327}
328
329from_js_impls! {
330 list:
331 Vec,
333 VecDeque,
335 LinkedList,
337 #[cfg(feature = "std")]
339 HashSet {S: Default + BuildHasher} (Eq + Hash),
340 HashbrownSet {S: Default + BuildHasher} (Eq + Hash),
342 BTreeSet (Eq + Ord),
344 #[cfg(feature = "indexmap")]
346 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "indexmap")))]
347 IndexSet {S: Default + BuildHasher} (Eq + Hash),
348}
349
350from_js_impls! {
351 map:
352 #[cfg(feature = "std")]
354 HashMap {S: Default + BuildHasher} (Eq + Hash),
355 HashbrownMap {S: Default + BuildHasher} (Eq + Hash),
357 BTreeMap (Eq + Ord),
359 #[cfg(feature = "indexmap")]
361 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "indexmap")))]
362 IndexMap {S: Default + BuildHasher} (Eq + Hash),
363}
364
365impl<'js> FromJs<'js> for f32 {
366 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
367 f64::from_js(ctx, value).map(|value| value as _)
368 }
369}
370
371#[allow(dead_code)]
372fn date_to_millis<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result<i64> {
373 let global = ctx.globals();
374 let date_ctor: Object = global.get("Date")?;
375
376 let value = Object::from_value(value)?;
377
378 if !value.is_instance_of(&date_ctor) {
379 return Err(Error::new_from_js("Object", "Date"));
380 }
381
382 let get_time_fn: crate::Function = value.get("getTime")?;
383
384 get_time_fn.call((crate::function::This(value),))
385}
386
387#[cfg(feature = "std")]
388impl<'js> FromJs<'js> for SystemTime {
389 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<SystemTime> {
390 let millis = date_to_millis(ctx, value)?;
391
392 if millis >= 0 {
393 SystemTime::UNIX_EPOCH
395 .checked_add(Duration::from_millis(millis as _))
396 .ok_or_else(|| {
397 Error::new_from_js_message("Date", "SystemTime", "Timestamp too big")
398 })
399 } else {
400 SystemTime::UNIX_EPOCH
402 .checked_sub(Duration::from_millis((-millis) as _))
403 .ok_or_else(|| {
404 Error::new_from_js_message("Date", "SystemTime", "Timestamp too small")
405 })
406 }
407 }
408}
409
410macro_rules! chrono_from_js_impls {
411 ($($type:ident;)+) => {
412 $(
413 #[cfg(feature = "chrono")]
414 impl<'js> FromJs<'js> for chrono::DateTime<chrono::$type> {
415 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<chrono::DateTime<chrono::$type>> {
416 use chrono::TimeZone;
417
418 let millis = date_to_millis(ctx, value)?;
419
420 chrono::$type.timestamp_millis_opt(millis).single()
421 .ok_or_else(|| {
422 Error::new_from_js_message("Date", "chrono::DateTime", "Invalid timestamp")
423 })
424 }
425 }
426 )+
427 };
428}
429
430chrono_from_js_impls! {
431 Utc;
432 Local;
433}
434
435#[cfg(test)]
436mod test {
437 #[cfg(target_arch = "wasm32")]
438 use super::Error;
439
440 #[test]
441 fn js_to_system_time() {
442 use crate::{Context, Runtime};
443 use std::time::{Duration, SystemTime};
444
445 let runtime = Runtime::new().unwrap();
446 let ctx = Context::full(&runtime).unwrap();
447
448 ctx.with(|ctx| {
449 let res: SystemTime = ctx.eval("new Date(123456789)").unwrap();
450 assert_eq!(
451 Duration::from_millis(123456789),
452 res.duration_since(SystemTime::UNIX_EPOCH).unwrap()
453 );
454
455 #[cfg(not(target_arch = "wasm32"))]
456 {
457 let res: SystemTime = ctx.eval("new Date(-123456789)").unwrap();
458 assert_eq!(
459 Duration::from_millis(123456789),
460 SystemTime::UNIX_EPOCH.duration_since(res).unwrap()
461 );
462 }
463
464 #[cfg(target_arch = "wasm32")]
466 {
467 let res: Error = ctx
468 .eval::<SystemTime, &str>("new Date(-123456789)")
469 .unwrap_err();
470 assert_eq!(
471 "Error converting from js 'Date' into type 'SystemTime': Timestamp too small",
472 res.to_string()
473 );
474 }
475 });
476 }
477
478 #[cfg(feature = "chrono")]
479 #[test]
480 fn js_to_chrono() {
481 use crate::{Context, Runtime};
482 use chrono::{DateTime, Utc};
483
484 let runtime = Runtime::new().unwrap();
485 let ctx = Context::full(&runtime).unwrap();
486
487 ctx.with(|ctx| {
488 let res: DateTime<Utc> = ctx.eval("new Date(123456789)").unwrap();
489 assert_eq!(123456789, res.timestamp_millis());
490 });
491
492 ctx.with(|ctx| {
493 let res: DateTime<Utc> = ctx
494 .eval("new Date('Fri Jun 03 2022 23:16:50 GMT+0300')")
495 .unwrap();
496 assert_eq!(1654287410000, res.timestamp_millis());
497 });
498
499 ctx.with(|ctx| {
500 let res: DateTime<Utc> = ctx
501 .eval("new Date('Fri Jun 03 2022 23:16:50 GMT-0300')")
502 .unwrap();
503 assert_eq!(1654309010000, res.timestamp_millis());
504 });
505 }
506}