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 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 q::JS_NULL,
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
287pub struct JsPromise {
289 context: *mut JSContext,
290 func: Vec<JSValue>,
291 raw_js_value: RawJSValue,
292 settled: bool,
293}
294
295impl JsPromise {
296
297 pub fn new(context: &mut Context) -> JsPromise {
299 let mut func: Vec<JSValue> = Vec::with_capacity(2);
301 let value = unsafe {
302 JS_NewPromiseCapability(context.wrapper.context, func.as_mut_ptr())
303 };
304 unsafe {
305 func.set_len(2);
306 }
307 let raw_js_value = RawJSValue::new(context.wrapper.context, &value);
308 unsafe {
309 JS_FreeValue(context.wrapper.context, value);
310 }
311 Self {
312 func,
313 raw_js_value,
314 context: context.wrapper.context,
315 settled: false,
316 }
317 }
318
319 pub fn resolve(&mut self, value: JsValue) {
321 if !self.mark_settled() {
322 return;
323 }
324 unsafe {
325 let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
326 let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
327 let res = JS_Call(self.context, self.func[0], undef, 1, &mut val as *mut JSValue);
328 JS_FreeValue(self.context, val);
329 JS_FreeValue(self.context, res);
330 JS_FreeValue(self.context, self.func[0]);
331 JS_FreeValue(self.context, self.func[1]);
332 }
333 }
334
335 pub fn reject(&mut self, value: JsValue) {
337 if !self.mark_settled() {
338 return;
339 }
340 unsafe {
341 let undef = crate::bindings::convert::serialize_value(self.context, JsValue::Undefined).unwrap();
342 let mut val = crate::bindings::convert::serialize_value(self.context, value).unwrap();
343 let res = JS_Call(self.context, self.func[1], undef, 1, &mut val as *mut JSValue);
344 JS_FreeValue(self.context, val);
345 JS_FreeValue(self.context, res);
346 JS_FreeValue(self.context, self.func[0]);
347 JS_FreeValue(self.context, self.func[1]);
348 }
349 }
350
351
352
353 pub fn js_value(&self) -> JsValue {
355 JsValue::Raw(self.raw_js_value.clone())
356 }
358
359 fn mark_settled(&mut self) -> bool {
360 if !self.settled {
361 self.settled = true;
362 true
363 } else {
364 false
365 }
366 }
367
368}
369
370value_impl_from! {
371 (
372 bool => Bool,
373 i32 => Int,
374 f64 => Float,
375 String => String,
376 )
377 (
378 i8 => |x| i32::from(x) => Int,
379 i16 => |x| i32::from(x) => Int,
380 u8 => |x| i32::from(x) => Int,
381 u16 => |x| i32::from(x) => Int,
382 u32 => |x| f64::from(x) => Float,
383 )
384}
385
386#[cfg(feature = "bigint")]
387value_impl_from! {
388 ()
389 (
390 i64 => |x| x.into() => BigInt,
391 u64 => |x| num_bigint::BigInt::from(x).into() => BigInt,
392 i128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
393 u128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
394 num_bigint::BigInt => |x| x.into() => BigInt,
395 )
396}
397
398#[cfg(feature = "bigint")]
399impl std::convert::TryFrom<JsValue> for i64 {
400 type Error = ValueError;
401
402 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
403 match value {
404 JsValue::Int(int) => Ok(int as i64),
405 JsValue::BigInt(bigint) => bigint.as_i64().ok_or(ValueError::UnexpectedType),
406 _ => Err(ValueError::UnexpectedType),
407 }
408 }
409}
410
411#[cfg(feature = "bigint")]
412macro_rules! value_bigint_impl_tryfrom {
413 (
414 ($($t:ty => $to_type:ident, )*)
415 ) => {
416 $(
417 impl std::convert::TryFrom<JsValue> for $t {
418 type Error = ValueError;
419
420 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
421 use num_traits::ToPrimitive;
422
423 match value {
424 JsValue::Int(int) => Ok(int as $t),
425 JsValue::BigInt(bigint) => bigint
426 .into_bigint()
427 .$to_type()
428 .ok_or(ValueError::UnexpectedType),
429 _ => Err(ValueError::UnexpectedType),
430 }
431 }
432 }
433 )*
434 }
435}
436
437#[cfg(feature = "bigint")]
438value_bigint_impl_tryfrom! {
439 (
440 u64 => to_u64,
441 i128 => to_i128,
442 u128 => to_u128,
443 )
444}
445
446#[cfg(feature = "bigint")]
447impl std::convert::TryFrom<JsValue> for num_bigint::BigInt {
448 type Error = ValueError;
449
450 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
451 match value {
452 JsValue::Int(int) => Ok(num_bigint::BigInt::from(int)),
453 JsValue::BigInt(bigint) => Ok(bigint.into_bigint()),
454 _ => Err(ValueError::UnexpectedType),
455 }
456 }
457}
458
459impl<T> From<Vec<T>> for JsValue
460where
461 T: Into<JsValue>,
462{
463 fn from(values: Vec<T>) -> Self {
464 let items = values.into_iter().map(|x| x.into()).collect();
465 JsValue::Array(items)
466 }
467}
468
469impl<T> TryFrom<JsValue> for Vec<T>
470where
471 T: TryFrom<JsValue>,
472{
473 type Error = ValueError;
474
475 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
476 match value {
477 JsValue::Array(items) => items
478 .into_iter()
479 .map(|item| item.try_into().map_err(|_| ValueError::UnexpectedType))
480 .collect(),
481 _ => Err(ValueError::UnexpectedType),
482 }
483 }
484}
485
486impl<'a> From<&'a str> for JsValue {
487 fn from(val: &'a str) -> Self {
488 JsValue::String(val.into())
489 }
490}
491
492impl<T> From<Option<T>> for JsValue
493where
494 T: Into<JsValue>,
495{
496 fn from(opt: Option<T>) -> Self {
497 if let Some(value) = opt {
498 value.into()
499 } else {
500 JsValue::Null
501 }
502 }
503}
504
505#[derive(PartialEq, Eq, Debug)]
507pub enum ValueError {
508 InvalidString(std::str::Utf8Error),
510 StringWithZeroBytes(std::ffi::NulError),
512 Internal(String),
514 UnexpectedType,
516 #[doc(hidden)]
517 __NonExhaustive,
518}
519
520impl From<std::convert::Infallible> for ValueError {
523 fn from(_: std::convert::Infallible) -> Self {
524 unreachable!()
525 }
526}
527
528impl fmt::Display for ValueError {
529 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
530 use ValueError::*;
531 match self {
532 InvalidString(e) => write!(
533 f,
534 "Value conversion failed - invalid non-utf8 string: {}",
535 e
536 ),
537 StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
538 Internal(e) => write!(f, "Value conversion failed - internal error: {}", e),
539 UnexpectedType => write!(f, "Could not convert - received unexpected type"),
540 __NonExhaustive => unreachable!(),
541 }
542 }
543}
544
545impl error::Error for ValueError {}
546
547#[cfg(test)]
548mod tests {
549 #[allow(unused_imports)]
550 use super::*;
551
552 #[cfg(feature = "bigint")]
553 #[test]
554 fn test_bigint_from_i64() {
555 let int = 1234i64;
556 let value = JsValue::from(int);
557 if let JsValue::BigInt(value) = value {
558 assert_eq!(value.as_i64(), Some(int));
559 } else {
560 panic!("Expected JsValue::BigInt");
561 }
562 }
563
564 #[cfg(feature = "bigint")]
565 #[test]
566 fn test_bigint_from_bigint() {
567 let bigint = num_bigint::BigInt::from(std::i128::MAX);
568 let value = JsValue::from(bigint.clone());
569 if let JsValue::BigInt(value) = value {
570 assert_eq!(value.into_bigint(), bigint);
571 } else {
572 panic!("Expected JsValue::BigInt");
573 }
574 }
575
576 #[cfg(feature = "bigint")]
577 #[test]
578 fn test_bigint_i64_bigint_eq() {
579 let value_i64 = JsValue::BigInt(1234i64.into());
580 let value_bigint = JsValue::BigInt(num_bigint::BigInt::from(1234i64).into());
581 assert_eq!(value_i64, value_bigint);
582 }
583}