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#[derive(PartialEq, Debug)]
22pub struct RawJSValue {
23 ctx: *mut JSContext,
25 js_value: *mut JSValue,
27}
28
29impl RawJSValue {
30
31 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 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 }
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#[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 #[cfg(feature = "chrono")]
111 Date(chrono::DateTime<chrono::Utc>),
112 #[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 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 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 as_str(&self) -> Option<&str> {
171 match self {
172 JsValue::String(ref s) => Some(s.as_str()),
173 _ => None,
174 }
175 }
176
177 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 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
290pub struct JsPromise {
292 context: *mut JSContext,
293 func: Vec<JSValue>,
294 raw_js_value: RawJSValue,
295 settled: bool,
296}
297
298impl JsPromise {
299
300 pub fn new(context: &mut Context) -> JsPromise {
302 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 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 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 pub fn js_value(&self) -> JsValue {
358 JsValue::Raw(self.raw_js_value.clone())
359 }
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#[derive(PartialEq, Eq, Debug)]
510pub enum ValueError {
511 InvalidString(std::str::Utf8Error),
513 StringWithZeroBytes(std::ffi::NulError),
515 Internal(String),
517 UnexpectedType,
519 #[doc(hidden)]
520 __NonExhaustive,
521}
522
523impl 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}