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