deft_quick_js/value/
mod.rs

1#[cfg(feature = "bigint")]
2pub(crate) mod bigint;
3
4use std::convert::{TryFrom, TryInto};
5use std::{collections::HashMap, error, fmt};
6use std::any::Any;
7use std::cell::RefCell;
8use std::rc::Rc;
9use libquickjs_sys as q;
10
11#[cfg(feature = "bigint")]
12pub use bigint::BigInt;
13use libquickjs_sys::{JS_Call, JS_FreeValue, JS_NewPromiseCapability, JSContext, JSValue};
14use crate::{Context, ExecutionError};
15use crate::bindings::convert::{deserialize_object, deserialize_value, serialize_value};
16use crate::bindings::{make_cstring, TAG_EXCEPTION};
17use crate::bindings::value::JsTag;
18use crate::ValueError::UnexpectedType;
19
20/// Raw js value
21#[derive(PartialEq, Debug)]
22pub struct RawJSValue {
23    /// the js context
24    ctx: *mut JSContext,
25    /// The raw js value
26    js_value: *mut JSValue,
27}
28
29impl RawJSValue {
30
31    /// Create a raw js value
32    pub fn new(ctx: *mut JSContext, value: &JSValue) -> Self {
33        unsafe {
34            libquickjs_sys::JS_DupValue(ctx, *value);
35        }
36        let ptr = Box::into_raw(Box::new(*value));
37        Self {
38            ctx,
39            js_value: ptr,
40        }
41    }
42
43    /// Create JSValue
44    pub fn create_js_value(&self) -> JSValue {
45        unsafe {
46            let v = *self.js_value;
47            libquickjs_sys::JS_DupValue(self.ctx, v);
48            v
49        }
50    }
51
52}
53
54impl Clone for RawJSValue {
55    fn clone(&self) -> Self {
56        unsafe {
57            Self::new(self.ctx, &*self.js_value)
58        }
59    }
60}
61
62impl Drop for RawJSValue {
63    fn drop(&mut self) {
64        unsafe {
65            let v = unsafe { Box::from_raw(self.js_value) };
66            libquickjs_sys::JS_FreeValue(self.ctx, *v.as_ref());
67            //TODO free js_value?
68            // Box::from_raw(self.js_value);
69        }
70    }
71}
72
73#[derive(Debug, Clone)]
74pub struct ResourceValue {
75    pub resource: Rc<RefCell<dyn Any>>
76}
77
78
79impl ResourceValue {
80
81    pub fn with<T: Any,R, F: FnOnce(&mut T) -> R>(&self, callback: F) -> Option<R> {
82        let mut b = self.resource.borrow_mut();
83        if let Some(e) = b.downcast_mut::<T>() {
84            Some(callback(e))
85        } else {
86            None
87        }
88    }
89
90}
91
92
93/// A value that can be (de)serialized to/from the quickjs runtime.
94#[derive(Clone, Debug)]
95#[allow(missing_docs)]
96pub enum JsValue {
97    Undefined,
98    Null,
99    Bool(bool),
100    Int(i32),
101    Float(f64),
102    String(String),
103    Array(Vec<JsValue>),
104    Object(HashMap<String, JsValue>),
105    Resource(ResourceValue),
106    Raw(RawJSValue),
107    Exception(RawJSValue),
108    /// chrono::Datetime<Utc> / JS Date integration.
109    /// Only available with the optional `chrono` feature.
110    #[cfg(feature = "chrono")]
111    Date(chrono::DateTime<chrono::Utc>),
112    /// num_bigint::BigInt / JS BigInt integration
113    /// Only available with the optional `bigint` feature
114    #[cfg(feature = "bigint")]
115    BigInt(crate::BigInt),
116    #[doc(hidden)]
117    __NonExhaustive,
118}
119
120impl JsValue {
121    pub fn create_object(context: *mut JSContext, map: HashMap<String, JsValue>) -> Result<Self, ValueError> {
122        let obj = unsafe { q::JS_NewObject(context) };
123        if q::JS_IsException(obj) {
124            return Err(ValueError::Internal("Could not create object".into()));
125        }
126
127        for (key, value) in map {
128            let ckey = make_cstring(key)?;
129
130            let qvalue = serialize_value(context, value).map_err(|e| {
131                // Free the object if a property failed.
132                unsafe {
133                    q::JS_FreeValue(context, obj);
134                }
135                e
136            })?;
137
138            let ret = unsafe {
139                q::JS_DefinePropertyValueStr(
140                    context,
141                    obj,
142                    ckey.as_ptr(),
143                    qvalue,
144                    q::JS_PROP_C_W_E as i32,
145                )
146            };
147            unsafe {
148                q::JS_FreeValue(context, qvalue);
149            }
150            if ret < 0 {
151                // Free the object if a property failed.
152                unsafe {
153                    q::JS_FreeValue(context, obj);
154                }
155                return Err(ValueError::Internal(
156                    "Could not add add property to object".into(),
157                ));
158            }
159        }
160
161        Ok(JsValue::Raw(RawJSValue {
162            ctx: context,
163            js_value: Box::into_raw(Box::new(obj)),
164        }))
165    }
166
167    /// Cast value to a str.
168    ///
169    /// Returns `Some(&str)` if value is a `JsValue::String`, None otherwise.
170    pub fn as_str(&self) -> Option<&str> {
171        match self {
172            JsValue::String(ref s) => Some(s.as_str()),
173            _ => None,
174        }
175    }
176
177    /// Convert to `String`.
178    pub fn into_string(self) -> Option<String> {
179        match self {
180            JsValue::String(s) => Some(s),
181            _ => None,
182        }
183    }
184
185    pub fn new_resource<T: Any>(value: T) -> Self {
186        Self::Resource(ResourceValue {
187            resource: Rc::new(RefCell::new(value)),
188        })
189    }
190
191    pub fn as_resource<T: Any,R, F: FnOnce(&mut T) -> R>(&self, callback: F) -> Option<R> {
192        if let JsValue::Resource(res) = self {
193            res.with(|t| {
194                callback(t)
195            })
196        } else {
197            None
198        }
199    }
200
201    pub fn get_properties(&self) -> Option<HashMap<String, JsValue>> {
202        if let JsValue::Raw(raw) = self {
203            if let Ok(r) = deserialize_object(raw.ctx, unsafe {&*raw.js_value}) {
204                Some(r)
205            } else {
206                None
207            }
208        } else {
209            None
210        }
211    }
212
213    pub fn call_as_function(
214        &self,
215        args: Vec<JsValue>,
216    ) -> Result<JsValue, ExecutionError> {
217        if let JsValue::Raw(raw) = self {
218            // args.in
219            let mut qargs = Vec::with_capacity(args.len());
220            for arg in args {
221                qargs.push(serialize_value(raw.ctx, arg.clone())?);
222            }
223            let qres_raw = unsafe {
224                JS_Call(
225                    raw.ctx,
226                    *raw.js_value,
227                    q::JS_NULL,
228                    qargs.len() as i32,
229                    qargs.as_mut_ptr(),
230                )
231            };
232            let r = deserialize_value(raw.ctx, &qres_raw);
233            unsafe {
234                for q in qargs {
235                    JS_FreeValue(raw.ctx, q);
236                }
237                JS_FreeValue(raw.ctx, qres_raw);
238            }
239            Ok(r?)
240        } else {
241            Err(ExecutionError::Conversion(UnexpectedType))
242        }
243    }
244
245}
246
247macro_rules! value_impl_from {
248    (
249        (
250            $(  $t1:ty => $var1:ident, )*
251        )
252        (
253            $( $t2:ty => |$exprname:ident| $expr:expr => $var2:ident, )*
254        )
255    ) => {
256        $(
257            impl From<$t1> for JsValue {
258                fn from(value: $t1) -> Self {
259                    JsValue::$var1(value)
260                }
261            }
262
263            impl std::convert::TryFrom<JsValue> for $t1 {
264                type Error = ValueError;
265
266                fn try_from(value: JsValue) -> Result<Self, Self::Error> {
267                    match value {
268                        JsValue::$var1(inner) => Ok(inner),
269                        _ => Err(ValueError::UnexpectedType)
270                    }
271
272                }
273            }
274        )*
275        $(
276            impl From<$t2> for JsValue {
277                fn from(value: $t2) -> Self {
278                    let $exprname = value;
279                    let inner = $expr;
280                    JsValue::$var2(inner)
281                }
282            }
283        )*
284    }
285}
286
287/// Js promise
288pub struct JsPromise {
289    context: *mut JSContext,
290    func: Vec<JSValue>,
291    raw_js_value: RawJSValue,
292    settled: bool,
293}
294
295impl JsPromise {
296
297    /// Create a new JsPromise
298    pub fn new(context: &mut Context) -> JsPromise {
299        // let context = context;
300        let mut func: Vec<JSValue> = Vec::with_capacity(2);
301        let value = unsafe {
302            JS_NewPromiseCapability(context.wrapper.context, func.as_mut_ptr())
303        };
304        unsafe {
305            func.set_len(2);
306        }
307        let raw_js_value = RawJSValue::new(context.wrapper.context, &value);
308        unsafe {
309            JS_FreeValue(context.wrapper.context, value);
310        }
311        Self {
312            func,
313            raw_js_value,
314            context: context.wrapper.context,
315            settled: false,
316        }
317    }
318
319    /// Resolve the promise
320    pub fn resolve(&mut self, value: JsValue) {
321        if !self.mark_settled() {
322            return;
323        }
324        unsafe {
325            let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
326            let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
327            let res = JS_Call(self.context, self.func[0], undef, 1, &mut val as *mut JSValue);
328            JS_FreeValue(self.context, val);
329            JS_FreeValue(self.context, res);
330            JS_FreeValue(self.context, self.func[0]);
331            JS_FreeValue(self.context, self.func[1]);
332        }
333    }
334
335    /// Reject the promise
336    pub fn reject(&mut self, value: JsValue) {
337        if !self.mark_settled() {
338            return;
339        }
340        unsafe {
341            let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
342            let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
343            let res = JS_Call(self.context, self.func[1], undef, 1, &mut val as *mut JSValue);
344            JS_FreeValue(self.context, val);
345            JS_FreeValue(self.context, res);
346            JS_FreeValue(self.context, self.func[0]);
347            JS_FreeValue(self.context, self.func[1]);
348        }
349    }
350
351
352
353    /// Js value
354    pub fn js_value(&self) -> JsValue {
355        JsValue::Raw(self.raw_js_value.clone())
356        //self.value.clone()
357    }
358
359    fn mark_settled(&mut self) -> bool {
360        if !self.settled {
361            self.settled = true;
362            true
363        } else {
364            false
365        }
366    }
367
368}
369
370value_impl_from! {
371    (
372        bool => Bool,
373        i32 => Int,
374        f64 => Float,
375        String => String,
376    )
377    (
378        i8 => |x| i32::from(x) => Int,
379        i16 => |x| i32::from(x) => Int,
380        u8 => |x| i32::from(x) => Int,
381        u16 => |x| i32::from(x) => Int,
382        u32 => |x| f64::from(x) => Float,
383    )
384}
385
386#[cfg(feature = "bigint")]
387value_impl_from! {
388    ()
389    (
390        i64 => |x| x.into() => BigInt,
391        u64 => |x| num_bigint::BigInt::from(x).into() => BigInt,
392        i128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
393        u128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
394        num_bigint::BigInt => |x| x.into() => BigInt,
395    )
396}
397
398#[cfg(feature = "bigint")]
399impl std::convert::TryFrom<JsValue> for i64 {
400    type Error = ValueError;
401
402    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
403        match value {
404            JsValue::Int(int) => Ok(int as i64),
405            JsValue::BigInt(bigint) => bigint.as_i64().ok_or(ValueError::UnexpectedType),
406            _ => Err(ValueError::UnexpectedType),
407        }
408    }
409}
410
411#[cfg(feature = "bigint")]
412macro_rules! value_bigint_impl_tryfrom {
413    (
414        ($($t:ty => $to_type:ident, )*)
415    ) => {
416        $(
417            impl std::convert::TryFrom<JsValue> for $t {
418                type Error = ValueError;
419
420                fn try_from(value: JsValue) -> Result<Self, Self::Error> {
421                    use num_traits::ToPrimitive;
422
423                    match value {
424                        JsValue::Int(int) => Ok(int as $t),
425                        JsValue::BigInt(bigint) => bigint
426                            .into_bigint()
427                            .$to_type()
428                            .ok_or(ValueError::UnexpectedType),
429                        _ => Err(ValueError::UnexpectedType),
430                    }
431                }
432            }
433        )*
434    }
435}
436
437#[cfg(feature = "bigint")]
438value_bigint_impl_tryfrom! {
439    (
440        u64 => to_u64,
441        i128 => to_i128,
442        u128 => to_u128,
443    )
444}
445
446#[cfg(feature = "bigint")]
447impl std::convert::TryFrom<JsValue> for num_bigint::BigInt {
448    type Error = ValueError;
449
450    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
451        match value {
452            JsValue::Int(int) => Ok(num_bigint::BigInt::from(int)),
453            JsValue::BigInt(bigint) => Ok(bigint.into_bigint()),
454            _ => Err(ValueError::UnexpectedType),
455        }
456    }
457}
458
459impl<T> From<Vec<T>> for JsValue
460where
461    T: Into<JsValue>,
462{
463    fn from(values: Vec<T>) -> Self {
464        let items = values.into_iter().map(|x| x.into()).collect();
465        JsValue::Array(items)
466    }
467}
468
469impl<T> TryFrom<JsValue> for Vec<T>
470where
471    T: TryFrom<JsValue>,
472{
473    type Error = ValueError;
474
475    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
476        match value {
477            JsValue::Array(items) => items
478                .into_iter()
479                .map(|item| item.try_into().map_err(|_| ValueError::UnexpectedType))
480                .collect(),
481            _ => Err(ValueError::UnexpectedType),
482        }
483    }
484}
485
486impl<'a> From<&'a str> for JsValue {
487    fn from(val: &'a str) -> Self {
488        JsValue::String(val.into())
489    }
490}
491
492impl<T> From<Option<T>> for JsValue
493where
494    T: Into<JsValue>,
495{
496    fn from(opt: Option<T>) -> Self {
497        if let Some(value) = opt {
498            value.into()
499        } else {
500            JsValue::Null
501        }
502    }
503}
504
505/// Error during value conversion.
506#[derive(PartialEq, Eq, Debug)]
507pub enum ValueError {
508    /// Invalid non-utf8 string.
509    InvalidString(std::str::Utf8Error),
510    /// Encountered string with \0 bytes.
511    StringWithZeroBytes(std::ffi::NulError),
512    /// Internal error.
513    Internal(String),
514    /// Received an unexpected type that could not be converted.
515    UnexpectedType,
516    #[doc(hidden)]
517    __NonExhaustive,
518}
519
520// TODO: remove this once either the Never type get's stabilized or the compiler
521// can properly handle Infallible.
522impl From<std::convert::Infallible> for ValueError {
523    fn from(_: std::convert::Infallible) -> Self {
524        unreachable!()
525    }
526}
527
528impl fmt::Display for ValueError {
529    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
530        use ValueError::*;
531        match self {
532            InvalidString(e) => write!(
533                f,
534                "Value conversion failed - invalid non-utf8 string: {}",
535                e
536            ),
537            StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
538            Internal(e) => write!(f, "Value conversion failed - internal error: {}", e),
539            UnexpectedType => write!(f, "Could not convert - received unexpected type"),
540            __NonExhaustive => unreachable!(),
541        }
542    }
543}
544
545impl error::Error for ValueError {}
546
547#[cfg(test)]
548mod tests {
549    #[allow(unused_imports)]
550    use super::*;
551
552    #[cfg(feature = "bigint")]
553    #[test]
554    fn test_bigint_from_i64() {
555        let int = 1234i64;
556        let value = JsValue::from(int);
557        if let JsValue::BigInt(value) = value {
558            assert_eq!(value.as_i64(), Some(int));
559        } else {
560            panic!("Expected JsValue::BigInt");
561        }
562    }
563
564    #[cfg(feature = "bigint")]
565    #[test]
566    fn test_bigint_from_bigint() {
567        let bigint = num_bigint::BigInt::from(std::i128::MAX);
568        let value = JsValue::from(bigint.clone());
569        if let JsValue::BigInt(value) = value {
570            assert_eq!(value.into_bigint(), bigint);
571        } else {
572            panic!("Expected JsValue::BigInt");
573        }
574    }
575
576    #[cfg(feature = "bigint")]
577    #[test]
578    fn test_bigint_i64_bigint_eq() {
579        let value_i64 = JsValue::BigInt(1234i64.into());
580        let value_bigint = JsValue::BigInt(num_bigint::BigInt::from(1234i64).into());
581        assert_eq!(value_i64, value_bigint);
582    }
583}