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