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            if ret < 0 {
148                // Free the object if a property failed.
149                unsafe {
150                    q::JS_FreeValue(context, obj);
151                }
152                return Err(ValueError::Internal(
153                    "Could not add add property to object".into(),
154                ));
155            }
156        }
157
158        Ok(JsValue::Raw(RawJSValue {
159            ctx: context,
160            js_value: Box::into_raw(Box::new(obj)),
161        }))
162    }
163
164    /// Cast value to a str.
165    ///
166    /// Returns `Some(&str)` if value is a `JsValue::String`, None otherwise.
167    pub fn as_str(&self) -> Option<&str> {
168        match self {
169            JsValue::String(ref s) => Some(s.as_str()),
170            _ => None,
171        }
172    }
173
174    /// Convert to `String`.
175    pub fn into_string(self) -> Option<String> {
176        match self {
177            JsValue::String(s) => Some(s),
178            _ => None,
179        }
180    }
181
182    pub fn new_resource<T: Any>(value: T) -> Self {
183        Self::Resource(ResourceValue {
184            resource: Rc::new(RefCell::new(value)),
185        })
186    }
187
188    pub fn as_resource<T: Any,R, F: FnOnce(&mut T) -> R>(&self, callback: F) -> Option<R> {
189        if let JsValue::Resource(res) = self {
190            res.with(|t| {
191                callback(t)
192            })
193        } else {
194            None
195        }
196    }
197
198    pub fn get_properties(&self) -> Option<HashMap<String, JsValue>> {
199        if let JsValue::Raw(raw) = self {
200            if let Ok(r) = deserialize_object(raw.ctx, unsafe {&*raw.js_value}) {
201                Some(r)
202            } else {
203                None
204            }
205        } else {
206            None
207        }
208    }
209
210    pub fn call_as_function(
211        &self,
212        args: Vec<JsValue>,
213    ) -> Result<JsValue, ExecutionError> {
214        if let JsValue::Raw(raw) = self {
215            // args.in
216            let mut qargs = Vec::with_capacity(args.len());
217            for arg in args {
218                qargs.push(serialize_value(raw.ctx, arg.clone())?);
219            }
220            let qres_raw = unsafe {
221                JS_Call(
222                    raw.ctx,
223                    *raw.js_value,
224                    JSValue {
225                        u: libquickjs_sys::JSValueUnion { int32: 0 },
226                        tag: JsTag::Null as i64,
227                    },
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}
293
294impl JsPromise {
295
296    /// Create a new JsPromise
297    pub fn new(context: &mut Context) -> JsPromise {
298        // let context = context;
299        let mut func: Vec<JSValue> = Vec::with_capacity(2);
300        let value = unsafe {
301            JS_NewPromiseCapability(context.wrapper.context, func.as_mut_ptr())
302        };
303        unsafe {
304            func.set_len(2);
305        }
306        let raw_js_value = RawJSValue::new(context.wrapper.context, &value);
307        unsafe {
308            JS_FreeValue(context.wrapper.context, value);
309        }
310        Self {
311            func,
312            raw_js_value,
313            context: context.wrapper.context,
314        }
315    }
316
317    /// Resolve the promise
318    pub fn resolve(&mut self, value: JsValue) {
319        unsafe {
320            let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
321            let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
322            let res = JS_Call(self.context, self.func[0], undef, 1, &mut val as *mut JSValue);
323            JS_FreeValue(self.context, val);
324            JS_FreeValue(self.context, res);
325        }
326    }
327
328    /// Reject the promise
329    pub fn reject(&mut self, value: JsValue) {
330        unsafe {
331            let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
332            let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
333            let res = JS_Call(self.context, self.func[1], undef, 1, &mut val as *mut JSValue);
334            JS_FreeValue(self.context, val);
335            JS_FreeValue(self.context, res);
336        }
337    }
338
339
340
341    /// Js value
342    pub fn js_value(&self) -> JsValue {
343        JsValue::Raw(self.raw_js_value.clone())
344        //self.value.clone()
345    }
346
347}
348
349value_impl_from! {
350    (
351        bool => Bool,
352        i32 => Int,
353        f64 => Float,
354        String => String,
355    )
356    (
357        i8 => |x| i32::from(x) => Int,
358        i16 => |x| i32::from(x) => Int,
359        u8 => |x| i32::from(x) => Int,
360        u16 => |x| i32::from(x) => Int,
361        u32 => |x| f64::from(x) => Float,
362    )
363}
364
365#[cfg(feature = "bigint")]
366value_impl_from! {
367    ()
368    (
369        i64 => |x| x.into() => BigInt,
370        u64 => |x| num_bigint::BigInt::from(x).into() => BigInt,
371        i128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
372        u128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
373        num_bigint::BigInt => |x| x.into() => BigInt,
374    )
375}
376
377#[cfg(feature = "bigint")]
378impl std::convert::TryFrom<JsValue> for i64 {
379    type Error = ValueError;
380
381    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
382        match value {
383            JsValue::Int(int) => Ok(int as i64),
384            JsValue::BigInt(bigint) => bigint.as_i64().ok_or(ValueError::UnexpectedType),
385            _ => Err(ValueError::UnexpectedType),
386        }
387    }
388}
389
390#[cfg(feature = "bigint")]
391macro_rules! value_bigint_impl_tryfrom {
392    (
393        ($($t:ty => $to_type:ident, )*)
394    ) => {
395        $(
396            impl std::convert::TryFrom<JsValue> for $t {
397                type Error = ValueError;
398
399                fn try_from(value: JsValue) -> Result<Self, Self::Error> {
400                    use num_traits::ToPrimitive;
401
402                    match value {
403                        JsValue::Int(int) => Ok(int as $t),
404                        JsValue::BigInt(bigint) => bigint
405                            .into_bigint()
406                            .$to_type()
407                            .ok_or(ValueError::UnexpectedType),
408                        _ => Err(ValueError::UnexpectedType),
409                    }
410                }
411            }
412        )*
413    }
414}
415
416#[cfg(feature = "bigint")]
417value_bigint_impl_tryfrom! {
418    (
419        u64 => to_u64,
420        i128 => to_i128,
421        u128 => to_u128,
422    )
423}
424
425#[cfg(feature = "bigint")]
426impl std::convert::TryFrom<JsValue> for num_bigint::BigInt {
427    type Error = ValueError;
428
429    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
430        match value {
431            JsValue::Int(int) => Ok(num_bigint::BigInt::from(int)),
432            JsValue::BigInt(bigint) => Ok(bigint.into_bigint()),
433            _ => Err(ValueError::UnexpectedType),
434        }
435    }
436}
437
438impl<T> From<Vec<T>> for JsValue
439where
440    T: Into<JsValue>,
441{
442    fn from(values: Vec<T>) -> Self {
443        let items = values.into_iter().map(|x| x.into()).collect();
444        JsValue::Array(items)
445    }
446}
447
448impl<T> TryFrom<JsValue> for Vec<T>
449where
450    T: TryFrom<JsValue>,
451{
452    type Error = ValueError;
453
454    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
455        match value {
456            JsValue::Array(items) => items
457                .into_iter()
458                .map(|item| item.try_into().map_err(|_| ValueError::UnexpectedType))
459                .collect(),
460            _ => Err(ValueError::UnexpectedType),
461        }
462    }
463}
464
465impl<'a> From<&'a str> for JsValue {
466    fn from(val: &'a str) -> Self {
467        JsValue::String(val.into())
468    }
469}
470
471impl<T> From<Option<T>> for JsValue
472where
473    T: Into<JsValue>,
474{
475    fn from(opt: Option<T>) -> Self {
476        if let Some(value) = opt {
477            value.into()
478        } else {
479            JsValue::Null
480        }
481    }
482}
483
484/// Error during value conversion.
485#[derive(PartialEq, Eq, Debug)]
486pub enum ValueError {
487    /// Invalid non-utf8 string.
488    InvalidString(std::str::Utf8Error),
489    /// Encountered string with \0 bytes.
490    StringWithZeroBytes(std::ffi::NulError),
491    /// Internal error.
492    Internal(String),
493    /// Received an unexpected type that could not be converted.
494    UnexpectedType,
495    #[doc(hidden)]
496    __NonExhaustive,
497}
498
499// TODO: remove this once either the Never type get's stabilized or the compiler
500// can properly handle Infallible.
501impl From<std::convert::Infallible> for ValueError {
502    fn from(_: std::convert::Infallible) -> Self {
503        unreachable!()
504    }
505}
506
507impl fmt::Display for ValueError {
508    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509        use ValueError::*;
510        match self {
511            InvalidString(e) => write!(
512                f,
513                "Value conversion failed - invalid non-utf8 string: {}",
514                e
515            ),
516            StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
517            Internal(e) => write!(f, "Value conversion failed - internal error: {}", e),
518            UnexpectedType => write!(f, "Could not convert - received unexpected type"),
519            __NonExhaustive => unreachable!(),
520        }
521    }
522}
523
524impl error::Error for ValueError {}
525
526#[cfg(test)]
527mod tests {
528    #[allow(unused_imports)]
529    use super::*;
530
531    #[cfg(feature = "bigint")]
532    #[test]
533    fn test_bigint_from_i64() {
534        let int = 1234i64;
535        let value = JsValue::from(int);
536        if let JsValue::BigInt(value) = value {
537            assert_eq!(value.as_i64(), Some(int));
538        } else {
539            panic!("Expected JsValue::BigInt");
540        }
541    }
542
543    #[cfg(feature = "bigint")]
544    #[test]
545    fn test_bigint_from_bigint() {
546        let bigint = num_bigint::BigInt::from(std::i128::MAX);
547        let value = JsValue::from(bigint.clone());
548        if let JsValue::BigInt(value) = value {
549            assert_eq!(value.into_bigint(), bigint);
550        } else {
551            panic!("Expected JsValue::BigInt");
552        }
553    }
554
555    #[cfg(feature = "bigint")]
556    #[test]
557    fn test_bigint_i64_bigint_eq() {
558        let value_i64 = JsValue::BigInt(1234i64.into());
559        let value_bigint = JsValue::BigInt(num_bigint::BigInt::from(1234i64).into());
560        assert_eq!(value_i64, value_bigint);
561    }
562}