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 if ret < 0 {
148 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 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 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 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
287pub struct JsPromise {
289 context: *mut JSContext,
290 func: Vec<JSValue>,
291 raw_js_value: RawJSValue,
292}
293
294impl JsPromise {
295
296 pub fn new(context: &mut Context) -> JsPromise {
298 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 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 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 pub fn js_value(&self) -> JsValue {
343 JsValue::Raw(self.raw_js_value.clone())
344 }
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#[derive(PartialEq, Eq, Debug)]
486pub enum ValueError {
487 InvalidString(std::str::Utf8Error),
489 StringWithZeroBytes(std::ffi::NulError),
491 Internal(String),
493 UnexpectedType,
495 #[doc(hidden)]
496 __NonExhaustive,
497}
498
499impl 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}