use std::collections::HashMap;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::hash::Hash;
#[cfg(feature = "chrono")]
use chrono::{DateTime, Utc};
use libquickjs_ng_sys as q;
#[cfg(feature = "bigint")]
use crate::utils::create_bigint;
#[cfg(feature = "chrono")]
use crate::utils::create_date;
use crate::utils::{
add_array_element, add_object_property, create_bool, create_empty_array, create_empty_object,
create_float, create_function, create_int, create_null, create_string,
};
use crate::OwnedJsPromise;
use crate::{ExecutionError, ValueError};
use super::tag::JsTag;
use super::JsCompiledFunction;
use super::JsFunction;
use super::JsModule;
use super::OwnedJsArray;
use super::OwnedJsObject;
pub struct OwnedJsValue {
context: *mut q::JSContext,
pub(crate) value: q::JSValue,
}
unsafe impl Send for OwnedJsValue {}
unsafe impl Sync for OwnedJsValue {}
impl PartialEq for OwnedJsValue {
fn eq(&self, other: &Self) -> bool {
unsafe { q::JS_Ext_GetPtr(self.value) == q::JS_Ext_GetPtr(other.value) }
}
}
impl OwnedJsValue {
#[inline]
pub fn context(&self) -> *mut q::JSContext {
self.context
}
#[inline]
pub fn new(context: *mut q::JSContext, value: q::JSValue) -> Self {
Self { context, value }
}
#[inline]
pub fn own(context: *mut q::JSContext, value: &q::JSValue) -> Self {
unsafe { q::JS_DupValue(context, *value) };
Self::new(context, *value)
}
#[inline]
pub fn tag(&self) -> JsTag {
JsTag::from_c(&self.value)
}
pub unsafe fn as_inner(&self) -> &q::JSValue {
&self.value
}
pub unsafe fn extract(self) -> q::JSValue {
let v = self.value;
std::mem::forget(self);
v
}
pub fn replace(&mut self, new: q::JSValue) {
unsafe {
q::JS_FreeValue(self.context, self.value);
}
self.value = new;
}
#[inline]
pub fn is_null(&self) -> bool {
self.tag().is_null()
}
#[inline]
pub fn is_undefined(&self) -> bool {
self.tag() == JsTag::Undefined
}
#[inline]
pub fn is_bool(&self) -> bool {
self.tag() == JsTag::Bool
}
#[inline]
pub fn is_int(&self) -> bool {
self.tag() == JsTag::Int
}
#[inline]
pub fn is_float(&self) -> bool {
self.tag() == JsTag::Float64
}
#[inline]
pub fn is_exception(&self) -> bool {
self.tag() == JsTag::Exception
}
#[inline]
pub fn is_object(&self) -> bool {
self.tag() == JsTag::Object
}
#[inline]
pub fn is_array(&self) -> bool {
unsafe { q::JS_IsArray(self.context, self.value) == 1 }
}
#[inline]
pub fn is_function(&self) -> bool {
unsafe { q::JS_IsFunction(self.context, self.value) == 1 }
}
#[inline]
pub fn is_promise(&self) -> bool {
unsafe { q::JS_Ext_IsPromise(self.context, self.value) == 1 }
}
#[inline]
pub fn is_module(&self) -> bool {
self.tag().is_module()
}
#[inline]
pub fn is_string(&self) -> bool {
self.tag() == JsTag::String
}
#[inline]
pub fn is_compiled_function(&self) -> bool {
self.tag() == JsTag::FunctionBytecode
}
#[inline]
fn check_tag(&self, expected: JsTag) -> Result<(), ValueError> {
if self.tag() == expected {
Ok(())
} else {
Err(ValueError::UnexpectedType)
}
}
pub fn to_bool(&self) -> Result<bool, ValueError> {
self.check_tag(JsTag::Bool)?;
let val = unsafe { q::JS_Ext_GetBool(self.value) };
Ok(val == 1)
}
pub fn to_int(&self) -> Result<i32, ValueError> {
self.check_tag(JsTag::Int)?;
let val = unsafe { q::JS_Ext_GetInt(self.value) };
Ok(val)
}
pub fn to_float(&self) -> Result<f64, ValueError> {
self.check_tag(JsTag::Float64)?;
let val = unsafe { q::JS_Ext_GetFloat64(self.value) };
Ok(val)
}
pub fn to_string(&self) -> Result<String, ValueError> {
self.check_tag(JsTag::String)?;
let ptr = unsafe { q::JS_ToCStringLen2(self.context, std::ptr::null_mut(), self.value, 0) };
if ptr.is_null() {
return Err(ValueError::Internal(
"Could not convert string: got a null pointer".into(),
));
}
let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
let s = cstr
.to_str()
.map_err(ValueError::InvalidString)?
.to_string();
unsafe { q::JS_FreeCString(self.context, ptr) };
Ok(s)
}
pub fn to_array(&self) -> Result<OwnedJsArray, ValueError> {
OwnedJsArray::try_from_value(self.clone())
}
pub fn try_into_object(self) -> Result<OwnedJsObject, ValueError> {
OwnedJsObject::try_from_value(self)
}
#[cfg(feature = "chrono")]
pub fn to_date(&self) -> Result<chrono::DateTime<chrono::Utc>, ValueError> {
use chrono::offset::TimeZone;
use crate::utils::js_date_constructor;
let date_constructor = js_date_constructor(self.context);
let is_date = unsafe { q::JS_IsInstanceOf(self.context, self.value, date_constructor) > 0 };
if is_date {
let getter = unsafe {
q::JS_GetPropertyStr(
self.context,
self.value,
std::ffi::CStr::from_bytes_with_nul(b"getTime\0")
.unwrap()
.as_ptr(),
)
};
let tag = unsafe { q::JS_Ext_ValueGetTag(getter) };
assert_eq!(tag, q::JS_TAG_OBJECT);
let timestamp_raw =
unsafe { q::JS_Call(self.context, getter, self.value, 0, std::ptr::null_mut()) };
unsafe {
q::JS_FreeValue(self.context, getter);
q::JS_FreeValue(self.context, date_constructor);
};
let tag = unsafe { q::JS_Ext_ValueGetTag(timestamp_raw) };
if tag == q::JS_TAG_FLOAT64 {
let f = unsafe { q::JS_Ext_GetFloat64(timestamp_raw) } as i64;
let datetime = chrono::Utc.timestamp_millis_opt(f).unwrap();
Ok(datetime)
} else if tag == q::JS_TAG_INT {
let f = unsafe { q::JS_Ext_GetInt(timestamp_raw) } as i64;
let datetime = chrono::Utc.timestamp_millis_opt(f).unwrap();
Ok(datetime)
} else {
Err(ValueError::Internal(
"Could not convert 'Date' instance to timestamp".into(),
))
}
} else {
unsafe { q::JS_FreeValue(self.context, date_constructor) };
Err(ValueError::UnexpectedType)
}
}
#[cfg(feature = "bigint")]
pub fn to_bigint(&self) -> Result<crate::BigInt, ValueError> {
use crate::value::BigInt;
use crate::value::BigIntOrI64;
let mut int: i64 = 0;
let ret = unsafe { q::JS_ToBigInt64(self.context, &mut int, self.value) };
if ret == 0 {
Ok(BigInt {
inner: BigIntOrI64::Int(int),
})
} else {
let ptr =
unsafe { q::JS_ToCStringLen2(self.context, std::ptr::null_mut(), self.value, 0) };
if ptr.is_null() {
return Err(ValueError::Internal(
"Could not convert BigInt to string: got a null pointer".into(),
));
}
let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
let bigint = num_bigint::BigInt::parse_bytes(cstr.to_bytes(), 10).unwrap();
unsafe { q::JS_FreeCString(self.context, ptr) };
Ok(BigInt {
inner: BigIntOrI64::BigInt(bigint),
})
}
}
pub fn try_into_function(self) -> Result<JsFunction, ValueError> {
JsFunction::try_from_value(self)
}
pub fn try_into_promise(self) -> Result<OwnedJsPromise, ValueError> {
OwnedJsPromise::try_from_value(self)
}
pub fn try_into_compiled_function(self) -> Result<JsCompiledFunction, ValueError> {
JsCompiledFunction::try_from_value(self)
}
pub fn try_into_module(self) -> Result<JsModule, ValueError> {
JsModule::try_from_value(self)
}
pub fn js_to_string(&self) -> Result<String, ExecutionError> {
let value = if self.is_string() {
self.to_string()?
} else {
let raw = unsafe { q::JS_ToString(self.context, self.value) };
let value = OwnedJsValue::new(self.context, raw);
if !value.is_string() {
return Err(ExecutionError::Internal(
"Could not convert value to string".into(),
));
}
value.to_string()?
};
Ok(value)
}
pub fn to_json_string(&self, space: u8) -> Result<String, ExecutionError> {
let replacer = unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_NULL, 0) };
let space = unsafe { q::JS_Ext_NewInt32(self.context, space as i32) };
let raw = unsafe { q::JS_JSONStringify(self.context, self.value, replacer, space) };
let value = OwnedJsValue::new(self.context, raw);
unsafe {
q::JS_FreeValue(self.context, replacer);
q::JS_FreeValue(self.context, space);
}
if !value.is_string() {
return Err(ExecutionError::Internal(
"Could not convert value to string".to_string(),
));
}
let value = value.to_string()?;
Ok(value)
}
#[cfg(test)]
pub(crate) fn get_ref_count(&self) -> i32 {
unsafe { q::JS_Ext_GetRefCount(self.value) }
}
}
impl Drop for OwnedJsValue {
fn drop(&mut self) {
unsafe {
q::JS_FreeValue(self.context, self.value);
}
}
}
impl Clone for OwnedJsValue {
fn clone(&self) -> Self {
unsafe { q::JS_DupValue(self.context, self.value) };
Self {
context: self.context,
value: self.value,
}
}
}
impl std::fmt::Debug for OwnedJsValue {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}(_)", self.tag())
}
}
impl TryFrom<OwnedJsValue> for bool {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_bool()
}
}
impl TryFrom<OwnedJsValue> for i32 {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_int()
}
}
impl TryFrom<OwnedJsValue> for f64 {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_float()
}
}
impl TryFrom<OwnedJsValue> for String {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_string()
}
}
#[cfg(feature = "chrono")]
impl TryFrom<OwnedJsValue> for DateTime<Utc> {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_date()
}
}
#[cfg(feature = "bigint")]
impl TryFrom<OwnedJsValue> for crate::BigInt {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_bigint()
}
}
#[cfg(feature = "bigint")]
impl TryFrom<OwnedJsValue> for i64 {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_bigint().map(|v| v.as_i64().unwrap())
}
}
#[cfg(feature = "bigint")]
impl TryFrom<OwnedJsValue> for u64 {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
use num_traits::ToPrimitive;
let bigint = value.to_bigint()?;
bigint
.into_bigint()
.to_u64()
.ok_or(ValueError::BigIntOverflow)
}
}
#[cfg(feature = "bigint")]
impl TryFrom<OwnedJsValue> for i128 {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
use num_traits::ToPrimitive;
let bigint = value.to_bigint()?;
bigint
.into_bigint()
.to_i128()
.ok_or(ValueError::BigIntOverflow)
}
}
#[cfg(feature = "bigint")]
impl TryFrom<OwnedJsValue> for u128 {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
use num_traits::ToPrimitive;
let bigint = value.to_bigint()?;
bigint
.into_bigint()
.to_u128()
.ok_or(ValueError::BigIntOverflow)
}
}
#[cfg(feature = "bigint")]
impl TryFrom<OwnedJsValue> for num_bigint::BigInt {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
value.to_bigint().map(|v| v.into_bigint())
}
}
impl<T: TryFrom<OwnedJsValue, Error = ValueError>> TryFrom<OwnedJsValue> for Vec<T> {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
let arr = value.to_array()?;
let mut ret: Vec<T> = vec![];
for i in 0..arr.length() {
let item = arr.get_index(i as u32).unwrap();
if let Some(item) = item {
let item = item.try_into()?;
ret.push(item);
}
}
Ok(ret)
}
}
impl<K: From<String> + PartialEq + Eq + Hash, V: TryFrom<OwnedJsValue, Error = ValueError>>
TryFrom<OwnedJsValue> for HashMap<K, V>
{
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
let obj = value.try_into_object()?;
let mut ret: HashMap<K, V> = HashMap::new();
let mut iter = obj.properties_iter()?;
while let Some(Ok(key)) = iter.next() {
let key = key.to_string()?;
let item = obj.property(&key).unwrap();
if let Some(item) = item {
let item = item.try_into()?;
ret.insert(key.into(), item);
}
}
Ok(ret)
}
}
impl TryFrom<OwnedJsValue> for JsFunction {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
JsFunction::try_from_value(value)
}
}
impl TryFrom<OwnedJsValue> for OwnedJsPromise {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
OwnedJsPromise::try_from_value(value)
}
}
impl TryFrom<OwnedJsValue> for OwnedJsArray {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
OwnedJsArray::try_from_value(value)
}
}
impl TryFrom<OwnedJsValue> for OwnedJsObject {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
OwnedJsObject::try_from_value(value)
}
}
impl TryFrom<OwnedJsValue> for JsCompiledFunction {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
JsCompiledFunction::try_from_value(value)
}
}
impl TryFrom<OwnedJsValue> for JsModule {
type Error = ValueError;
fn try_from(value: OwnedJsValue) -> Result<Self, Self::Error> {
JsModule::try_from_value(value)
}
}
pub trait ToOwnedJsValue {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue;
}
impl ToOwnedJsValue for bool {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_bool(context, self);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for i32 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_int(context, self);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for i8 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_int(context, self as i32);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for i16 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_int(context, self as i32);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for u8 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_int(context, self as i32);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for u16 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_int(context, self as i32);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for f64 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_float(context, self);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for u32 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_float(context, self as f64);
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for &str {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_string(context, self).unwrap();
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for String {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_string(context, &self).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "chrono")]
impl ToOwnedJsValue for DateTime<Utc> {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_date(context, self).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "bigint")]
impl ToOwnedJsValue for crate::BigInt {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_bigint(context, self).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "bigint")]
impl ToOwnedJsValue for num_bigint::BigInt {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_bigint(context, self.into()).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "bigint")]
impl ToOwnedJsValue for i64 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_bigint(context, self.into()).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "bigint")]
impl ToOwnedJsValue for u64 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let bigint: num_bigint::BigInt = self.into();
let val = create_bigint(context, bigint.into()).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "bigint")]
impl ToOwnedJsValue for i128 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let bigint: num_bigint::BigInt = self.into();
let val = create_bigint(context, bigint.into()).unwrap();
OwnedJsValue::new(context, val)
}
}
#[cfg(feature = "bigint")]
impl ToOwnedJsValue for u128 {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let bigint: num_bigint::BigInt = self.into();
let val = create_bigint(context, bigint.into()).unwrap();
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for JsFunction {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = create_function(context, self).unwrap();
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for OwnedJsPromise {
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let val = unsafe { self.into_value().extract() };
OwnedJsValue::new(context, val)
}
}
impl ToOwnedJsValue for OwnedJsValue {
fn to_owned(self, _: *mut q::JSContext) -> OwnedJsValue {
self
}
}
impl<T> ToOwnedJsValue for Vec<T>
where
T: ToOwnedJsValue,
{
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let arr = create_empty_array(context).unwrap();
self.into_iter().enumerate().for_each(|(idx, val)| {
let val: OwnedJsValue = (context, val).into();
add_array_element(context, arr, idx as u32, unsafe { val.extract() }).unwrap();
});
OwnedJsValue::new(context, arr)
}
}
impl<K, V> ToOwnedJsValue for HashMap<K, V>
where
K: Into<String>,
V: ToOwnedJsValue,
{
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
let obj = create_empty_object(context).unwrap();
self.into_iter().for_each(|(key, val)| {
let val: OwnedJsValue = (context, val).into();
add_object_property(context, obj, key.into().as_str(), unsafe { val.extract() })
.unwrap();
});
OwnedJsValue::new(context, obj)
}
}
impl<T> ToOwnedJsValue for Option<T>
where
T: ToOwnedJsValue,
{
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
if let Some(val) = self {
(context, val).into()
} else {
OwnedJsValue::new(context, create_null())
}
}
}
impl<T> ToOwnedJsValue for &T
where
T: ToOwnedJsValue,
{
fn to_owned(self, context: *mut q::JSContext) -> OwnedJsValue {
(context, self).into()
}
}
impl<T> From<(*mut q::JSContext, T)> for OwnedJsValue
where
T: ToOwnedJsValue,
{
fn from((context, value): (*mut q::JSContext, T)) -> Self {
value.to_owned(context)
}
}