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 obj.tag == TAG_EXCEPTION {
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                    JSValue {
228                        u: libquickjs_sys::JSValueUnion { int32: 0 },
229                        tag: JsTag::Null as i64,
230                    },
231                    qargs.len() as i32,
232                    qargs.as_mut_ptr(),
233                )
234            };
235            let r = deserialize_value(raw.ctx, &qres_raw);
236            unsafe {
237                for q in qargs {
238                    JS_FreeValue(raw.ctx, q);
239                }
240                JS_FreeValue(raw.ctx, qres_raw);
241            }
242            Ok(r?)
243        } else {
244            Err(ExecutionError::Conversion(UnexpectedType))
245        }
246    }
247
248}
249
250macro_rules! value_impl_from {
251    (
252        (
253            $(  $t1:ty => $var1:ident, )*
254        )
255        (
256            $( $t2:ty => |$exprname:ident| $expr:expr => $var2:ident, )*
257        )
258    ) => {
259        $(
260            impl From<$t1> for JsValue {
261                fn from(value: $t1) -> Self {
262                    JsValue::$var1(value)
263                }
264            }
265
266            impl std::convert::TryFrom<JsValue> for $t1 {
267                type Error = ValueError;
268
269                fn try_from(value: JsValue) -> Result<Self, Self::Error> {
270                    match value {
271                        JsValue::$var1(inner) => Ok(inner),
272                        _ => Err(ValueError::UnexpectedType)
273                    }
274
275                }
276            }
277        )*
278        $(
279            impl From<$t2> for JsValue {
280                fn from(value: $t2) -> Self {
281                    let $exprname = value;
282                    let inner = $expr;
283                    JsValue::$var2(inner)
284                }
285            }
286        )*
287    }
288}
289
290/// Js promise
291pub struct JsPromise {
292    context: *mut JSContext,
293    func: Vec<JSValue>,
294    raw_js_value: RawJSValue,
295    settled: bool,
296}
297
298impl JsPromise {
299
300    /// Create a new JsPromise
301    pub fn new(context: &mut Context) -> JsPromise {
302        // let context = context;
303        let mut func: Vec<JSValue> = Vec::with_capacity(2);
304        let value = unsafe {
305            JS_NewPromiseCapability(context.wrapper.context, func.as_mut_ptr())
306        };
307        unsafe {
308            func.set_len(2);
309        }
310        let raw_js_value = RawJSValue::new(context.wrapper.context, &value);
311        unsafe {
312            JS_FreeValue(context.wrapper.context, value);
313        }
314        Self {
315            func,
316            raw_js_value,
317            context: context.wrapper.context,
318            settled: false,
319        }
320    }
321
322    /// Resolve the promise
323    pub fn resolve(&mut self, value: JsValue) {
324        if !self.mark_settled() {
325            return;
326        }
327        unsafe {
328            let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
329            let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
330            let res = JS_Call(self.context, self.func[0], undef, 1, &mut val as *mut JSValue);
331            JS_FreeValue(self.context, val);
332            JS_FreeValue(self.context, res);
333            JS_FreeValue(self.context, self.func[0]);
334            JS_FreeValue(self.context, self.func[1]);
335        }
336    }
337
338    /// Reject the promise
339    pub fn reject(&mut self, value: JsValue) {
340        if !self.mark_settled() {
341            return;
342        }
343        unsafe {
344            let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
345            let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
346            let res = JS_Call(self.context, self.func[1], undef, 1, &mut val as *mut JSValue);
347            JS_FreeValue(self.context, val);
348            JS_FreeValue(self.context, res);
349            JS_FreeValue(self.context, self.func[0]);
350            JS_FreeValue(self.context, self.func[1]);
351        }
352    }
353
354
355
356    /// Js value
357    pub fn js_value(&self) -> JsValue {
358        JsValue::Raw(self.raw_js_value.clone())
359        //self.value.clone()
360    }
361
362    fn mark_settled(&mut self) -> bool {
363        if !self.settled {
364            self.settled = true;
365            true
366        } else {
367            false
368        }
369    }
370
371}
372
373value_impl_from! {
374    (
375        bool => Bool,
376        i32 => Int,
377        f64 => Float,
378        String => String,
379    )
380    (
381        i8 => |x| i32::from(x) => Int,
382        i16 => |x| i32::from(x) => Int,
383        u8 => |x| i32::from(x) => Int,
384        u16 => |x| i32::from(x) => Int,
385        u32 => |x| f64::from(x) => Float,
386    )
387}
388
389#[cfg(feature = "bigint")]
390value_impl_from! {
391    ()
392    (
393        i64 => |x| x.into() => BigInt,
394        u64 => |x| num_bigint::BigInt::from(x).into() => BigInt,
395        i128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
396        u128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
397        num_bigint::BigInt => |x| x.into() => BigInt,
398    )
399}
400
401#[cfg(feature = "bigint")]
402impl std::convert::TryFrom<JsValue> for i64 {
403    type Error = ValueError;
404
405    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
406        match value {
407            JsValue::Int(int) => Ok(int as i64),
408            JsValue::BigInt(bigint) => bigint.as_i64().ok_or(ValueError::UnexpectedType),
409            _ => Err(ValueError::UnexpectedType),
410        }
411    }
412}
413
414#[cfg(feature = "bigint")]
415macro_rules! value_bigint_impl_tryfrom {
416    (
417        ($($t:ty => $to_type:ident, )*)
418    ) => {
419        $(
420            impl std::convert::TryFrom<JsValue> for $t {
421                type Error = ValueError;
422
423                fn try_from(value: JsValue) -> Result<Self, Self::Error> {
424                    use num_traits::ToPrimitive;
425
426                    match value {
427                        JsValue::Int(int) => Ok(int as $t),
428                        JsValue::BigInt(bigint) => bigint
429                            .into_bigint()
430                            .$to_type()
431                            .ok_or(ValueError::UnexpectedType),
432                        _ => Err(ValueError::UnexpectedType),
433                    }
434                }
435            }
436        )*
437    }
438}
439
440#[cfg(feature = "bigint")]
441value_bigint_impl_tryfrom! {
442    (
443        u64 => to_u64,
444        i128 => to_i128,
445        u128 => to_u128,
446    )
447}
448
449#[cfg(feature = "bigint")]
450impl std::convert::TryFrom<JsValue> for num_bigint::BigInt {
451    type Error = ValueError;
452
453    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
454        match value {
455            JsValue::Int(int) => Ok(num_bigint::BigInt::from(int)),
456            JsValue::BigInt(bigint) => Ok(bigint.into_bigint()),
457            _ => Err(ValueError::UnexpectedType),
458        }
459    }
460}
461
462impl<T> From<Vec<T>> for JsValue
463where
464    T: Into<JsValue>,
465{
466    fn from(values: Vec<T>) -> Self {
467        let items = values.into_iter().map(|x| x.into()).collect();
468        JsValue::Array(items)
469    }
470}
471
472impl<T> TryFrom<JsValue> for Vec<T>
473where
474    T: TryFrom<JsValue>,
475{
476    type Error = ValueError;
477
478    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
479        match value {
480            JsValue::Array(items) => items
481                .into_iter()
482                .map(|item| item.try_into().map_err(|_| ValueError::UnexpectedType))
483                .collect(),
484            _ => Err(ValueError::UnexpectedType),
485        }
486    }
487}
488
489impl<'a> From<&'a str> for JsValue {
490    fn from(val: &'a str) -> Self {
491        JsValue::String(val.into())
492    }
493}
494
495impl<T> From<Option<T>> for JsValue
496where
497    T: Into<JsValue>,
498{
499    fn from(opt: Option<T>) -> Self {
500        if let Some(value) = opt {
501            value.into()
502        } else {
503            JsValue::Null
504        }
505    }
506}
507
508/// Error during value conversion.
509#[derive(PartialEq, Eq, Debug)]
510pub enum ValueError {
511    /// Invalid non-utf8 string.
512    InvalidString(std::str::Utf8Error),
513    /// Encountered string with \0 bytes.
514    StringWithZeroBytes(std::ffi::NulError),
515    /// Internal error.
516    Internal(String),
517    /// Received an unexpected type that could not be converted.
518    UnexpectedType,
519    #[doc(hidden)]
520    __NonExhaustive,
521}
522
523// TODO: remove this once either the Never type get's stabilized or the compiler
524// can properly handle Infallible.
525impl From<std::convert::Infallible> for ValueError {
526    fn from(_: std::convert::Infallible) -> Self {
527        unreachable!()
528    }
529}
530
531impl fmt::Display for ValueError {
532    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
533        use ValueError::*;
534        match self {
535            InvalidString(e) => write!(
536                f,
537                "Value conversion failed - invalid non-utf8 string: {}",
538                e
539            ),
540            StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
541            Internal(e) => write!(f, "Value conversion failed - internal error: {}", e),
542            UnexpectedType => write!(f, "Could not convert - received unexpected type"),
543            __NonExhaustive => unreachable!(),
544        }
545    }
546}
547
548impl error::Error for ValueError {}
549
550#[cfg(test)]
551mod tests {
552    #[allow(unused_imports)]
553    use super::*;
554
555    #[cfg(feature = "bigint")]
556    #[test]
557    fn test_bigint_from_i64() {
558        let int = 1234i64;
559        let value = JsValue::from(int);
560        if let JsValue::BigInt(value) = value {
561            assert_eq!(value.as_i64(), Some(int));
562        } else {
563            panic!("Expected JsValue::BigInt");
564        }
565    }
566
567    #[cfg(feature = "bigint")]
568    #[test]
569    fn test_bigint_from_bigint() {
570        let bigint = num_bigint::BigInt::from(std::i128::MAX);
571        let value = JsValue::from(bigint.clone());
572        if let JsValue::BigInt(value) = value {
573            assert_eq!(value.into_bigint(), bigint);
574        } else {
575            panic!("Expected JsValue::BigInt");
576        }
577    }
578
579    #[cfg(feature = "bigint")]
580    #[test]
581    fn test_bigint_i64_bigint_eq() {
582        let value_i64 = JsValue::BigInt(1234i64.into());
583        let value_bigint = JsValue::BigInt(num_bigint::BigInt::from(1234i64).into());
584        assert_eq!(value_i64, value_bigint);
585    }
586}