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 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 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 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 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 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 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
308pub struct JsPromise {
310 context: *mut JSContext,
311 func: Vec<JSValue>,
312 raw_js_value: RawJSValue,
313 settled: bool,
314}
315
316impl JsPromise {
317
318 pub fn new(context: &mut Context) -> JsPromise {
320 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 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 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 pub fn js_value(&self) -> JsValue {
376 JsValue::Raw(self.raw_js_value.clone())
377 }
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#[derive(PartialEq, Eq, Debug)]
528pub enum ValueError {
529 InvalidString(std::str::Utf8Error),
531 StringWithZeroBytes(std::ffi::NulError),
533 Internal(String),
535 UnexpectedType,
537 #[doc(hidden)]
538 __NonExhaustive,
539}
540
541impl 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}