1use std::collections::HashMap;
2use std::convert::TryFrom;
3use std::convert::TryInto;
4use std::hash::Hash;
5
6#[cfg(feature = "chrono")]
7use chrono::{DateTime, Utc};
8use libquickjspp_sys as q;
9
10#[cfg(feature = "bigint")]
11use crate::utils::create_bigint;
12#[cfg(feature = "chrono")]
13use crate::utils::create_date;
14use crate::utils::{
15 add_array_element, add_object_property, create_bool, create_empty_array, create_empty_object,
16 create_float, create_function, create_int, create_null, create_string,
17};
18use crate::{ExecutionError, ValueError};
19
20use super::tag::JsTag;
21use super::JsCompiledFunction;
22use super::JsFunction;
23use super::JsModule;
24use super::OwnedJsArray;
25use super::OwnedJsObject;
26
27#[derive(PartialEq)]
31pub struct OwnedJsValue {
32 context: *mut q::JSContext,
33 pub(crate) value: q::JSValue,
35}
36
37impl OwnedJsValue {
38 #[inline]
39 pub fn context(&self) -> *mut q::JSContext {
40 self.context
41 }
42
43 #[inline]
47 pub fn new(context: *mut q::JSContext, value: q::JSValue) -> Self {
48 Self { context, value }
49 }
50
51 #[inline]
54 pub fn own(context: *mut q::JSContext, value: &q::JSValue) -> Self {
55 unsafe { q::JS_DupValue(context, *value) };
56 Self::new(context, *value)
57 }
58
59 #[inline]
60 pub fn tag(&self) -> JsTag {
61 JsTag::from_c(&self.value)
62 }
63
64 pub unsafe fn as_inner(&self) -> &q::JSValue {
68 &self.value
69 }
70
71 pub unsafe fn extract(self) -> q::JSValue {
75 let v = self.value;
76 std::mem::forget(self);
77 v
78 }
79
80 pub fn replace(&mut self, new: q::JSValue) {
83 unsafe {
84 q::JS_FreeValue(self.context, self.value);
85 }
86 self.value = new;
87 }
88
89 #[inline]
91 pub fn is_null(&self) -> bool {
92 self.tag().is_null()
93 }
94
95 #[inline]
97 pub fn is_undefined(&self) -> bool {
98 self.tag() == JsTag::Undefined
99 }
100
101 #[inline]
103 pub fn is_bool(&self) -> bool {
104 self.tag() == JsTag::Bool
105 }
106
107 #[inline]
109 pub fn is_int(&self) -> bool {
110 self.tag() == JsTag::Int
111 }
112
113 #[inline]
115 pub fn is_float(&self) -> bool {
116 self.tag() == JsTag::Float64
117 }
118
119 #[inline]
121 pub fn is_exception(&self) -> bool {
122 self.tag() == JsTag::Exception
123 }
124
125 #[inline]
127 pub fn is_object(&self) -> bool {
128 self.tag() == JsTag::Object
129 }
130
131 #[inline]
133 pub fn is_array(&self) -> bool {
134 unsafe { q::JS_IsArray(self.context, self.value) == 1 }
135 }
136
137 #[inline]
139 pub fn is_function(&self) -> bool {
140 unsafe { q::JS_IsFunction(self.context, self.value) == 1 }
141 }
142
143 #[inline]
145 pub fn is_module(&self) -> bool {
146 self.tag().is_module()
147 }
148
149 #[inline]
151 pub fn is_string(&self) -> bool {
152 self.tag() == JsTag::String
153 }
154
155 #[inline]
157 pub fn is_compiled_function(&self) -> bool {
158 self.tag() == JsTag::FunctionBytecode
159 }
160
161 #[inline]
162 fn check_tag(&self, expected: JsTag) -> Result<(), ValueError> {
163 if self.tag() == expected {
164 Ok(())
165 } else {
166 Err(ValueError::UnexpectedType)
167 }
168 }
169
170 pub fn to_bool(&self) -> Result<bool, ValueError> {
172 self.check_tag(JsTag::Bool)?;
173 let val = unsafe { q::JS_VALUE_GET_BOOL(self.value) };
174 Ok(val)
175 }
176
177 pub fn to_int(&self) -> Result<i32, ValueError> {
179 self.check_tag(JsTag::Int)?;
180 let val = unsafe { q::JS_VALUE_GET_INT(self.value) };
181 Ok(val)
182 }
183
184 pub fn to_float(&self) -> Result<f64, ValueError> {
186 self.check_tag(JsTag::Float64)?;
187 let val = unsafe { q::JS_VALUE_GET_FLOAT64(self.value) };
188 Ok(val)
189 }
190
191 pub fn to_string(&self) -> Result<String, ValueError> {
193 self.check_tag(JsTag::String)?;
194 let ptr = unsafe { q::JS_ToCStringLen2(self.context, std::ptr::null_mut(), self.value, 0) };
195
196 if ptr.is_null() {
197 return Err(ValueError::Internal(
198 "Could not convert string: got a null pointer".into(),
199 ));
200 }
201
202 let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
203
204 let s = cstr
205 .to_str()
206 .map_err(ValueError::InvalidString)?
207 .to_string();
208
209 unsafe { q::JS_FreeCString(self.context, ptr) };
211
212 Ok(s)
213 }
214
215 pub fn to_array(&self) -> Result<OwnedJsArray, ValueError> {
216 OwnedJsArray::try_from_value(self.clone())
217 }
218
219 pub fn try_into_object(self) -> Result<OwnedJsObject, ValueError> {
221 OwnedJsObject::try_from_value(self)
222 }
223
224 #[cfg(feature = "chrono")]
225 pub fn to_date(&self) -> Result<chrono::DateTime<chrono::Utc>, ValueError> {
226 use chrono::offset::TimeZone;
227
228 use crate::utils::js_date_constructor;
229
230 let date_constructor = js_date_constructor(self.context);
231 let is_date = unsafe { q::JS_IsInstanceOf(self.context, self.value, date_constructor) > 0 };
232
233 if is_date {
234 let getter = unsafe {
235 q::JS_GetPropertyStr(
236 self.context,
237 self.value,
238 std::ffi::CStr::from_bytes_with_nul(b"getTime\0")
239 .unwrap()
240 .as_ptr(),
241 )
242 };
243 let tag = unsafe { q::JS_ValueGetTag(getter) };
244 assert_eq!(tag, q::JS_TAG_OBJECT);
245
246 let timestamp_raw =
247 unsafe { q::JS_Call(self.context, getter, self.value, 0, std::ptr::null_mut()) };
248
249 unsafe {
250 q::JS_FreeValue(self.context, getter);
251 q::JS_FreeValue(self.context, date_constructor);
252 };
253
254 let tag = unsafe { q::JS_ValueGetTag(timestamp_raw) };
255 let res = if tag == q::JS_TAG_FLOAT64 {
256 let f = unsafe { q::JS_VALUE_GET_FLOAT64(timestamp_raw) } as i64;
257 let datetime = chrono::Utc.timestamp_millis_opt(f).unwrap();
258 Ok(datetime)
259 } else if tag == q::JS_TAG_INT {
260 let f = unsafe { q::JS_VALUE_GET_INT(timestamp_raw) } as i64;
261 let datetime = chrono::Utc.timestamp_millis_opt(f).unwrap();
262 Ok(datetime)
263 } else {
264 Err(ValueError::Internal(
265 "Could not convert 'Date' instance to timestamp".into(),
266 ))
267 };
268 return res;
269 } else {
270 unsafe { q::JS_FreeValue(self.context, date_constructor) };
271 Err(ValueError::UnexpectedType)
272 }
273 }
274
275 #[cfg(feature = "bigint")]
276 pub fn to_bigint(&self) -> Result<crate::BigInt, ValueError> {
277 use crate::value::BigInt;
278 use crate::value::BigIntOrI64;
279
280 let mut int: i64 = 0;
281 let ret = unsafe { q::JS_ToBigInt64(self.context, &mut int, self.value) };
282 if ret == 0 {
283 Ok(BigInt {
284 inner: BigIntOrI64::Int(int),
285 })
286 } else {
287 let ptr =
288 unsafe { q::JS_ToCStringLen2(self.context, std::ptr::null_mut(), self.value, 0) };
289
290 if ptr.is_null() {
291 return Err(ValueError::Internal(
292 "Could not convert BigInt to string: got a null pointer".into(),
293 ));
294 }
295
296 let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
297 let bigint = num_bigint::BigInt::parse_bytes(cstr.to_bytes(), 10).unwrap();
298
299 unsafe { q::JS_FreeCString(self.context, ptr) };
301
302 Ok(BigInt {
303 inner: BigIntOrI64::BigInt(bigint),
304 })
305 }
306 }
307
308 pub fn try_into_function(self) -> Result<JsFunction, ValueError> {
310 JsFunction::try_from_value(self)
311 }
312
313 pub fn try_into_compiled_function(self) -> Result<JsCompiledFunction, ValueError> {
315 JsCompiledFunction::try_from_value(self)
316 }
317
318 pub fn try_into_module(self) -> Result<JsModule, ValueError> {
320 JsModule::try_from_value(self)
321 }
322
323 pub fn js_to_string(&self) -> Result<String, ExecutionError> {
325 let value = if self.is_string() {
326 self.to_string()?
327 } else {
328 let raw = unsafe { q::JS_ToString(self.context, self.value) };
329 let value = OwnedJsValue::new(self.context, raw);
330
331 if !value.is_string() {
332 return Err(ExecutionError::Internal(
333 "Could not convert value to string".into(),
334 ));
335 }
336 value.to_string()?
337 };
338
339 Ok(value)
340 }
341
342 pub fn to_json_string(&self, space: u8) -> Result<String, ExecutionError> {
344 let replacer = unsafe { q::JS_NewSpecialValue(q::JS_TAG_NULL, 0) };
345 let space = unsafe { q::JS_NewInt32(self.context, space as i32) };
346 let raw = unsafe { q::JS_JSONStringify(self.context, self.value, replacer, space) };
347
348 let value = OwnedJsValue::new(self.context, raw);
349
350 unsafe {
351 q::JS_FreeValue(self.context, replacer);
352 q::JS_FreeValue(self.context, space);
353 }
354
355 if !value.is_string() {
356 return Err(ExecutionError::Internal(
357 "Could not convert value to string".to_string(),
358 ));
359 }
360
361 let value = value.to_string()?;
362
363 Ok(value)
364 }
365
366 #[cfg(test)]
367 pub(crate) fn get_ref_count(&self) -> i32 {
368 let tag = unsafe { q::JS_ValueGetTag(self.value) };
369 if tag >= 8 {
370 let ptr = unsafe { q::JS_VALUE_GET_PTR(self.value) as *mut q::JSRefCountHeader };
373 let pref: &mut q::JSRefCountHeader = &mut unsafe { *ptr };
374 pref.ref_count
375 } else {
376 -1
377 }
378 }
379}
380
381impl Drop for OwnedJsValue {
382 fn drop(&mut self) {
383 unsafe {
384 q::JS_FreeValue(self.context, self.value);
385 }
386 }
387}
388
389impl Clone for OwnedJsValue {
390 fn clone(&self) -> Self {
391 unsafe { q::JS_DupValue(self.context, self.value) };
392 Self {
393 context: self.context,
394 value: self.value,
395 }
396 }
397}
398
399impl std::fmt::Debug for OwnedJsValue {
400 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
401 write!(f, "{:?}(_)", self.tag())
402 }
403}
404
405impl TryFrom<OwnedJsValue> for bool {
406 type Error = ValueError;
407
408 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
409 value.to_bool()
410 }
411}
412
413impl TryFrom<OwnedJsValue> for i32 {
414 type Error = ValueError;
415
416 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
417 value.to_int()
418 }
419}
420
421impl TryFrom<OwnedJsValue> for f64 {
422 type Error = ValueError;
423
424 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
425 value.to_float()
426 }
427}
428
429impl TryFrom<OwnedJsValue> for String {
430 type Error = ValueError;
431
432 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
433 value.to_string()
434 }
435}
436
437#[cfg(feature = "chrono")]
438impl TryFrom<OwnedJsValue> for DateTime<Utc> {
439 type Error = ValueError;
440
441 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
442 value.to_date()
443 }
444}
445
446#[cfg(feature = "bigint")]
447impl TryFrom<OwnedJsValue> for crate::BigInt {
448 type Error = ValueError;
449
450 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
451 value.to_bigint()
452 }
453}
454
455#[cfg(feature = "bigint")]
456impl TryFrom<OwnedJsValue> for i64 {
457 type Error = ValueError;
458
459 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
460 value.to_bigint().map(|v| v.as_i64().unwrap())
461 }
462}
463
464#[cfg(feature = "bigint")]
465impl TryFrom<OwnedJsValue> for u64 {
466 type Error = ValueError;
467
468 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
469 use num_traits::ToPrimitive;
470 let bigint = value.to_bigint()?;
471 bigint
472 .into_bigint()
473 .to_u64()
474 .ok_or(ValueError::BigIntOverflow)
475 }
476}
477
478#[cfg(feature = "bigint")]
479impl TryFrom<OwnedJsValue> for i128 {
480 type Error = ValueError;
481
482 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
483 use num_traits::ToPrimitive;
484 let bigint = value.to_bigint()?;
485 bigint
486 .into_bigint()
487 .to_i128()
488 .ok_or(ValueError::BigIntOverflow)
489 }
490}
491
492#[cfg(feature = "bigint")]
493impl TryFrom<OwnedJsValue> for u128 {
494 type Error = ValueError;
495
496 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
497 use num_traits::ToPrimitive;
498 let bigint = value.to_bigint()?;
499 bigint
500 .into_bigint()
501 .to_u128()
502 .ok_or(ValueError::BigIntOverflow)
503 }
504}
505
506#[cfg(feature = "bigint")]
507impl TryFrom<OwnedJsValue> for num_bigint::BigInt {
508 type Error = ValueError;
509
510 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
511 value.to_bigint().map(|v| v.into_bigint())
512 }
513}
514
515impl<T: TryFrom<OwnedJsValue, Error = ValueError>> TryFrom<OwnedJsValue> for Vec<T> {
516 type Error = ValueError;
517
518 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
519 let arr = value.to_array()?;
520 let mut ret: Vec<T> = vec![];
521 for i in 0..arr.length() {
522 let item = arr.get_index(i as u32).unwrap();
523 if let Some(item) = item {
524 let item = item.try_into()?;
525 ret.push(item);
526 }
527 }
528 Ok(ret)
529 }
530}
531
532impl<K: From<String> + PartialEq + Eq + Hash, V: TryFrom<OwnedJsValue, Error = ValueError>>
533 TryFrom<OwnedJsValue> for HashMap<K, V>
534{
535 type Error = ValueError;
536
537 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
538 let obj = value.try_into_object()?;
539 let mut ret: HashMap<K, V> = HashMap::new();
540 let mut iter = obj.properties_iter()?;
541 while let Some(Ok(key)) = iter.next() {
542 let key = key.to_string()?;
543 let item = obj.property(&key).unwrap();
544 if let Some(item) = item {
545 let item = item.try_into()?;
546 ret.insert(key.into(), item);
547 }
548 }
549 Ok(ret)
550 }
551}
552
553impl TryFrom<OwnedJsValue> for JsFunction {
554 type Error = ValueError;
555
556 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
557 JsFunction::try_from_value(value)
558 }
559}
560
561impl TryFrom<OwnedJsValue> for OwnedJsArray {
562 type Error = ValueError;
563
564 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
565 OwnedJsArray::try_from_value(value)
566 }
567}
568
569impl TryFrom<OwnedJsValue> for OwnedJsObject {
570 type Error = ValueError;
571
572 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
573 OwnedJsObject::try_from_value(value)
574 }
575}
576
577impl TryFrom<OwnedJsValue> for JsCompiledFunction {
578 type Error = ValueError;
579
580 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
581 JsCompiledFunction::try_from_value(value)
582 }
583}
584
585impl TryFrom<OwnedJsValue> for JsModule {
586 type Error = ValueError;
587
588 fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
589 JsModule::try_from_value(value)
590 }
591}
592
593pub trait ToOwnedJsValue {
598 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue;
599}
600
601impl ToOwnedJsValue for bool {
602 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
603 let val = create_bool(context, self);
604 OwnedJsValue::new(context, val)
605 }
606}
607
608impl ToOwnedJsValue for i32 {
609 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
610 let val = create_int(context, self);
611 OwnedJsValue::new(context, val)
612 }
613}
614
615impl ToOwnedJsValue for i8 {
616 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
617 let val = create_int(context, self as i32);
618 OwnedJsValue::new(context, val)
619 }
620}
621
622impl ToOwnedJsValue for i16 {
623 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
624 let val = create_int(context, self as i32);
625 OwnedJsValue::new(context, val)
626 }
627}
628
629impl ToOwnedJsValue for u8 {
630 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
631 let val = create_int(context, self as i32);
632 OwnedJsValue::new(context, val)
633 }
634}
635
636impl ToOwnedJsValue for u16 {
637 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
638 let val = create_int(context, self as i32);
639 OwnedJsValue::new(context, val)
640 }
641}
642
643impl ToOwnedJsValue for f64 {
644 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
645 let val = create_float(context, self);
646 OwnedJsValue::new(context, val)
647 }
648}
649
650impl ToOwnedJsValue for u32 {
651 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
652 let val = create_float(context, self as f64);
653 OwnedJsValue::new(context, val)
654 }
655}
656
657impl ToOwnedJsValue for &str {
658 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
659 let val = create_string(context, self).unwrap();
660 OwnedJsValue::new(context, val)
661 }
662}
663
664impl ToOwnedJsValue for String {
665 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
666 let val = create_string(context, &self).unwrap();
667 OwnedJsValue::new(context, val)
668 }
669}
670
671#[cfg(feature = "chrono")]
672impl ToOwnedJsValue for DateTime<Utc> {
673 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
674 let val = create_date(context, self).unwrap();
675 OwnedJsValue::new(context, val)
676 }
677}
678
679#[cfg(feature = "bigint")]
680impl ToOwnedJsValue for crate::BigInt {
681 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
682 let val = create_bigint(context, self).unwrap();
683 OwnedJsValue::new(context, val)
684 }
685}
686
687#[cfg(feature = "bigint")]
688impl ToOwnedJsValue for num_bigint::BigInt {
689 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
690 let val = create_bigint(context, self.into()).unwrap();
691 OwnedJsValue::new(context, val)
692 }
693}
694
695#[cfg(feature = "bigint")]
696impl ToOwnedJsValue for i64 {
697 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
698 let val = create_bigint(context, self.into()).unwrap();
699 OwnedJsValue::new(context, val)
700 }
701}
702
703#[cfg(feature = "bigint")]
704impl ToOwnedJsValue for u64 {
705 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
706 let bigint: num_bigint::BigInt = self.into();
707 let val = create_bigint(context, bigint.into()).unwrap();
708 OwnedJsValue::new(context, val)
709 }
710}
711
712#[cfg(feature = "bigint")]
713impl ToOwnedJsValue for i128 {
714 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
715 let bigint: num_bigint::BigInt = self.into();
716 let val = create_bigint(context, bigint.into()).unwrap();
717 OwnedJsValue::new(context, val)
718 }
719}
720
721#[cfg(feature = "bigint")]
722impl ToOwnedJsValue for u128 {
723 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
724 let bigint: num_bigint::BigInt = self.into();
725 let val = create_bigint(context, bigint.into()).unwrap();
726 OwnedJsValue::new(context, val)
727 }
728}
729
730impl ToOwnedJsValue for JsFunction {
731 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
732 let val = create_function(context, self).unwrap();
733 OwnedJsValue::new(context, val)
734 }
735}
736
737impl ToOwnedJsValue for OwnedJsValue {
739 fn to_owned(self, _: *mut q::JSContext) -> OwnedJsValue {
740 self
741 }
742}
743
744impl<T> ToOwnedJsValue for Vec<T>
745where
746 T: ToOwnedJsValue,
747{
748 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
749 let arr = create_empty_array(context).unwrap();
750 let _ = self.into_iter().enumerate().for_each(|(idx, val)| {
751 let val: OwnedJsValue = (context, val).into();
752 add_array_element(context, arr, idx as u32, unsafe { val.extract() }).unwrap();
753 });
754
755 OwnedJsValue::new(context, arr)
756 }
757}
758
759impl<K, V> ToOwnedJsValue for HashMap<K, V>
760where
761 K: Into<String>,
762 V: ToOwnedJsValue,
763{
764 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
765 let obj = create_empty_object(context).unwrap();
766 let _ = self.into_iter().for_each(|(key, val)| {
767 let val: OwnedJsValue = (context, val).into();
768 add_object_property(context, obj, key.into().as_str(), unsafe { val.extract() })
769 .unwrap();
770 });
771
772 OwnedJsValue::new(context, obj)
773 }
774}
775
776impl<T> ToOwnedJsValue for Option<T>
777where
778 T: ToOwnedJsValue,
779{
780 fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
781 if let Some(val) = self {
782 (context, val).into()
783 } else {
784 OwnedJsValue::new(context, create_null())
785 }
786 }
787}
788
789impl<T> From<(*mut q::JSContext, T)> for OwnedJsValue
790where
791 T: ToOwnedJsValue,
792{
793 fn from((context, value): (*mut q::JSContext, T)) -> Self {
794 value.to_owned(context)
795 }
796}