nodex_api/value/
mod.rs

1use crate::{api, env::NapiEnv, prelude::*};
2use std::mem::MaybeUninit;
3
4#[repr(C)]
5#[derive(Clone, Copy, Debug)]
6pub struct JsValue(pub(crate) NapiEnv, pub(crate) napi_value);
7
8impl JsValue {
9    /// `NapiEnv` of this `JsValue`
10    pub fn env(&self) -> NapiEnv {
11        self.0
12    }
13
14    /// raw napi_value of this `JsValue`
15    pub fn raw(&self) -> napi_value {
16        self.1
17    }
18
19    pub fn is_object(&self) -> NapiResult<bool> {
20        napi_is!(self, JsObject)
21    }
22
23    /// view it as an object, may fail if it is not an object value
24    pub fn as_object(&self) -> NapiResult<JsObject> {
25        napi_as!(self, JsObject, NapiStatus::ObjectExpected)
26    }
27
28    pub fn is_string(&self) -> NapiResult<bool> {
29        napi_is!(self, JsString)
30    }
31
32    /// view it as a string, may fail if it is not a string value
33    pub fn as_string(&self) -> NapiResult<JsString> {
34        napi_as!(self, JsString, NapiStatus::StringExpected)
35    }
36
37    pub fn is_symbol(&self) -> NapiResult<bool> {
38        napi_is!(self, JsSymbol)
39    }
40
41    /// view it as a symbol, may fail if it is not a symbol value
42    pub fn as_symbol(&self) -> NapiResult<JsSymbol> {
43        napi_as!(self, JsSymbol, NapiStatus::InvalidArg)
44    }
45
46    pub fn is_array(&self) -> NapiResult<bool> {
47        napi_is!(self, JsArray)
48    }
49
50    /// view it as an array, may fail if it is not an array value
51    pub fn as_array(&self) -> NapiResult<JsArray> {
52        napi_as!(self, JsArray, NapiStatus::ArrayExpected)
53    }
54
55    pub fn is_typedarray(&self) -> NapiResult<bool> {
56        napi_is!(self, JsTypedArray)
57    }
58    /// view it as a typed_array, may fail if it is not a typed_array value
59    pub fn as_typedarray(&self) -> NapiResult<JsTypedArray> {
60        napi_as!(self, JsTypedArray, NapiStatus::InvalidArg)
61    }
62
63    pub fn is_arraybuffer(&self) -> NapiResult<bool> {
64        napi_is!(self, JsArrayBuffer)
65    }
66
67    /// view it as an array_buffer, may fail if it is not an array_buffer value
68    pub fn as_arraybuffer(&self) -> NapiResult<JsArrayBuffer> {
69        napi_as!(self, JsArrayBuffer, NapiStatus::ArraybufferExpected)
70    }
71
72    pub fn is_buffer<const N: usize>(&self) -> NapiResult<bool> {
73        napi_is!(self, JsBuffer<N>)
74    }
75
76    /// view it as a buffer, may fail if it is not a buffer value
77    pub fn as_buffer<const N: usize>(&self) -> NapiResult<JsBuffer<N>> {
78        napi_as!(self, JsBuffer<N>, NapiStatus::InvalidArg)
79    }
80
81    pub fn is_dataview(&self) -> NapiResult<bool> {
82        napi_is!(self, JsDataView)
83    }
84
85    /// view it as a dataview, may fail if it is not a dataview value
86    pub fn as_dataview(&self) -> NapiResult<JsDataView> {
87        napi_as!(self, JsDataView, NapiStatus::InvalidArg)
88    }
89
90    pub fn is_external<T>(&self) -> NapiResult<bool> {
91        napi_is!(self, JsExternal<T>)
92    }
93
94    /// view it as an external, may fail if it is not an external value
95    pub fn as_external<T>(&self) -> NapiResult<JsExternal<T>> {
96        napi_as!(self, JsExternal<T>, NapiStatus::InvalidArg)
97    }
98
99    pub fn is_function(&self) -> NapiResult<bool> {
100        napi_is!(self, JsFunction)
101    }
102
103    /// view it as a number, may fail if it is not a number value
104    pub fn as_function(&self) -> NapiResult<JsFunction> {
105        napi_as!(self, JsFunction, NapiStatus::FunctionExpected)
106    }
107
108    pub fn is_number(&self) -> NapiResult<bool> {
109        napi_is!(self, JsNumber)
110    }
111
112    /// view it as a number, may fail if it is not a number value
113    pub fn as_number(&self) -> NapiResult<JsNumber> {
114        napi_as!(self, JsNumber, NapiStatus::NumberExpected)
115    }
116
117    pub fn is_bigint<T: Copy>(&self) -> NapiResult<bool> {
118        napi_is!(self, JsBigInt<T>)
119    }
120
121    /// view it as a bigint, may fail if it is not a bigint value
122    pub fn as_bigint<T: Copy>(&self) -> NapiResult<JsBigInt<T>> {
123        napi_as!(self, JsBigInt<T>, NapiStatus::BigintExpected)
124    }
125
126    pub fn is_boolean(&self) -> NapiResult<bool> {
127        napi_is!(self, JsBoolean)
128    }
129
130    /// view it as a boolean, may fail if it is not a boolean value
131    pub fn as_boolean(&self) -> NapiResult<JsBoolean> {
132        napi_as!(self, JsBoolean, NapiStatus::BooleanExpected)
133    }
134
135    #[cfg(feature = "v5")]
136    pub fn is_date(&self) -> NapiResult<bool> {
137        napi_is!(self, JsDate)
138    }
139
140    #[cfg(feature = "v5")]
141    /// view it as a date, may fail if it is not a date value
142    pub fn as_date(&self) -> NapiResult<JsDate> {
143        napi_as!(self, JsDate, NapiStatus::DateExpected)
144    }
145}
146
147impl NapiValueT for JsValue {
148    fn from_raw(env: NapiEnv, value: napi_value) -> JsValue {
149        JsValue(env, value)
150    }
151
152    fn value(&self) -> JsValue {
153        *self
154    }
155}
156
157impl NapiValueCheck for JsValue {
158    fn check(&self) -> NapiResult<bool> {
159        Ok(true)
160    }
161}
162
163pub trait NapiValueCheck {
164    fn check(&self) -> NapiResult<bool>;
165}
166
167/// The trait for js value, which just store napi_value raw pointer.
168pub trait NapiValueT: NapiValueCheck + Sized {
169    /// construct value from raw pointer
170    fn from_raw(env: NapiEnv, value: napi_value) -> Self;
171
172    /// inner value
173    fn value(&self) -> JsValue;
174
175    /// napi_value type cast
176    ///
177    /// ## Safety
178    ///
179    /// It just put the handle in new type, does not check the real type.
180    #[inline]
181    unsafe fn cast<T: NapiValueT>(&self) -> T {
182        T::from_raw(self.env(), self.raw())
183    }
184
185    /// Upcast to specified value
186    #[inline]
187    fn cast_checked<T: NapiValueT>(&self) -> NapiResult<T> {
188        if unsafe { self.cast::<T>() }.check()? {
189            Ok(T::from_raw(self.env(), self.raw()))
190        } else {
191            Err(NapiStatus::InvalidArg)
192        }
193    }
194
195    /// Returns napi_ok if the API succeeded.
196    /// - `napi_invalid_arg` if the type of value is not a known ECMAScript type and value is not an External value.
197    /// This API represents behavior similar to invoking the typeof Operator on the object as defined in
198    /// Section 12.5.5 of the ECMAScript Language Specification. However, there are some differences:
199    /// It has support for detecting an External value.
200    /// It detects null as a separate type, while ECMAScript typeof would detect object.
201    /// If value has a type that is invalid, an error is returned.
202    #[inline]
203    fn kind(&self) -> NapiResult<NapiValuetype> {
204        Ok(napi_call!(=napi_typeof, self.env(), self.raw()))
205    }
206
207    /// This API implements the abstract operation ToBoolean() as defined in Section 7.1.2 of the
208    /// ECMAScript Language Specification.
209    #[inline]
210    fn coerce_to_bool(&self) -> NapiResult<JsBoolean> {
211        Ok(JsBoolean::from_raw(
212            self.env(),
213            napi_call!(=napi_coerce_to_bool, self.env(), self.raw()),
214        ))
215    }
216
217    /// This API implements the abstract operation ToNumber() as defined in Section 7.1.3 of the
218    /// ECMAScript Language Specification. This function potentially runs JS code if the passed-in
219    /// value is an object.
220    #[inline]
221    fn coerce_coerce_to_number(&self) -> NapiResult<JsNumber> {
222        Ok(JsNumber::from_raw(
223            self.env(),
224            napi_call!(=napi_coerce_to_number, self.env(), self.raw()),
225        ))
226    }
227
228    /// This API implements the abstract operation ToObject() as defined in Section 7.1.13 of the
229    /// ECMAScript Language Specification.
230    #[inline]
231    fn coerce_to_object(&self) -> NapiResult<JsObject> {
232        Ok(JsObject::from_raw(
233            self.env(),
234            napi_call!(=napi_coerce_to_object, self.env(), self.raw()),
235        ))
236    }
237
238    /// This API implements the abstract operation ToString() as defined in Section 7.1.13 of the
239    /// ECMAScript Language Specification. This function potentially runs JS code if the passed-in
240    /// value is an object.
241    #[inline]
242    fn coerce_to_string(&self) -> NapiResult<JsString> {
243        Ok(JsString::from_raw(
244            self.env(),
245            napi_call!(=napi_coerce_to_string, self.env(), self.raw()),
246        ))
247    }
248
249    /// This API represents invoking the instanceof Operator on the object as defined in Section
250    /// 12.10.4 of the ECMAScript Language Specification.
251    fn instance_of(&self, constructor: JsFunction) -> NapiResult<bool> {
252        Ok(napi_call!(=napi_instanceof, self.env(), self.raw(), constructor.raw()))
253    }
254
255    /// This API represents the invocation of the Strict Equality algorithm as defined in
256    /// Section 7.2.14 of the ECMAScript Language Specification.
257    fn equals(&self, rhs: impl NapiValueT) -> NapiResult<bool> {
258        Ok(napi_call!(=napi_strict_equals, self.env(), self.raw(), rhs.raw()))
259    }
260
261    /// the `NapiEnv` of current value
262    fn env(&self) -> NapiEnv {
263        self.value().env()
264    }
265
266    /// the raw-handle of current value
267    fn raw(&self) -> napi_value {
268        self.value().raw()
269    }
270
271    /// get null singleton
272    fn null(&self) -> NapiResult<JsNull> {
273        JsNull::new(self.env())
274    }
275
276    /// get undefined singleton
277    fn undefined(&self) -> NapiResult<JsUndefined> {
278        JsUndefined::new(self.env())
279    }
280
281    /// get global singleton
282    fn global(&self) -> NapiResult<JsGlobal> {
283        JsGlobal::new(self.env())
284    }
285
286    /// value is throwable
287    #[inline]
288    fn throw(&self) -> NapiResult<()> {
289        napi_call!(napi_throw, self.env(), self.raw())
290    }
291
292    /// This method allows the efficient definition of multiple properties on a given object. The
293    /// properties are defined using property descriptors (see napi_property_descriptor). Given an
294    /// array of such property descriptors, this API will set the properties on the object one at a
295    /// time, as defined by DefineOwnProperty() (described in Section 9.1.6 of the ECMA-262
296    /// specification).
297    fn define_properties<P>(&self, properties: P) -> NapiResult<()>
298    where
299        P: AsRef<[NapiPropertyDescriptor]>,
300    {
301        napi_call!(
302            napi_define_properties,
303            self.env(),
304            self.raw(),
305            properties.as_ref().len(),
306            properties.as_ref().as_ptr() as *const _,
307        )
308    }
309
310    /// This is a hook which is fired when the value is gabage-collected.
311    /// For napi >= 5, we use napi_add_finalizer,
312    /// For napi < 5, we use napi_wrap.
313    fn gc<Finalizer>(&mut self, finalizer: Finalizer) -> NapiResult<NapiRef>
314    where
315        Finalizer: FnOnce(NapiEnv) -> NapiResult<()>,
316    {
317        #[cfg(feature = "v5")]
318        return self.finalizer(finalizer);
319        #[cfg(not(feature = "v5"))]
320        return self.wrap((), move |env, _| finalizer(env));
321    }
322
323    #[cfg(feature = "v5")]
324    /// Adds a napi_finalize callback which will be called when the JavaScript object in
325    /// js_object is ready for garbage collection. This API is similar to napi_wrap() except
326    /// that:
327    ///
328    /// * the native data cannot be retrieved later using napi_unwrap(),
329    /// * nor can it be removed later using napi_remove_wrap(), and
330    /// * the API can be called multiple times with different data items in order to attach
331    /// each of them to the JavaScript object, and
332    /// * the object manipulated by the API can be used with napi_wrap().
333    ///
334    /// Caution: The optional returned reference (if obtained) should be deleted via
335    /// napi_delete_reference ONLY in response to the finalize callback invocation.
336    /// If it is deleted before then, then the finalize callback may never be invoked.
337    /// herefore, when obtaining a reference a finalize callback is also required in order
338    /// to enable correct disposal of the reference.
339    fn finalizer<Finalizer>(&self, finalizer: Finalizer) -> NapiResult<NapiRef>
340    where
341        Finalizer: FnOnce(NapiEnv) -> NapiResult<()>,
342    {
343        // NB: Because we add a closure to the napi finalizer, it's better
344        // to **CAPTURE** the leaked data from rust side, so here we just
345        // ignore the passed in native data pointer.
346        unsafe extern "C" fn finalizer_trampoline(
347            env: NapiEnv,
348            _: DataPointer,
349            finalizer: DataPointer,
350        ) {
351            // NB: here we collect the memory of finalizer closure
352            let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
353                Box::from_raw(finalizer as _);
354            if let Err(err) = finalizer(env) {
355                log::error!("NapiValueT::finalizer(): {}", err);
356            }
357        }
358
359        let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
360            Box::new(Box::new(finalizer));
361        let reference = napi_call!(
362            =napi_add_finalizer,
363            self.env(),
364            self.raw(),
365            std::ptr::null_mut(),
366            Some(finalizer_trampoline),
367            Box::into_raw(finalizer) as DataPointer,
368        );
369
370        Ok(NapiRef::from_raw(self.env(), reference))
371    }
372
373    #[allow(clippy::type_complexity)]
374    /// Wraps a native instance in a JavaScript object. The native instance can be retrieved
375    /// later using napi_unwrap().
376    ///
377    /// When JavaScript code invokes a constructor for a class that was defined using napi_define_class(),
378    /// the napi_callback for the constructor is invoked. After constructing an instance of the native class,
379    /// the callback must then call napi_wrap() to wrap the newly constructed instance in the already-created
380    /// JavaScript object that is the this argument to the constructor callback. (That this object was
381    /// created from the constructor function's prototype, so it already has definitions of all the instance
382    /// properties and methods.)
383    ///
384    /// Typically when wrapping a class instance, a finalize callback should be provided that simply
385    /// deletes the native instance that is received as the data argument to the finalize callback.
386    ///
387    /// The optional returned reference is initially a weak reference, meaning it has a reference
388    /// count of 0. Typically this reference count would be incremented temporarily during async
389    /// operations that require the instance to remain valid.
390    ///
391    /// Caution: The optional returned reference (if obtained) should be deleted via napi_delete_reference
392    /// ONLY in response to the finalize callback invocation. If it is deleted before then, then
393    /// the finalize callback may never be invoked. Therefore, when obtaining a reference a finalize
394    /// callback is also required in order to enable correct disposal of the reference.
395    ///
396    /// Calling napi_wrap() a second time on an object will return an error. To associate another
397    /// native instance with the object, use napi_remove_wrap() first.
398    fn wrap<T>(
399        &mut self,
400        data: T,
401        finalizer: impl FnOnce(NapiEnv, T) -> NapiResult<()>,
402    ) -> NapiResult<NapiRef> {
403        // NB: Because we add a closure to the napi finalizer, it's better
404        // to **CAPTURE** the leaked data from rust side, so here we just
405        // ignore the passed in native data pointer.
406        unsafe extern "C" fn finalizer_trampoline<T>(
407            env: NapiEnv,
408            data: DataPointer,
409            finalizer: DataPointer,
410        ) {
411            // NB: here we collect the memory of finalizer closure
412            let finalizer: Box<Box<dyn FnOnce(NapiEnv, T) -> NapiResult<()>>> =
413                Box::from_raw(finalizer as _);
414            let data = Box::<T>::from_raw(data as _);
415            if let Err(err) = finalizer(env, *data) {
416                log::error!("NapiValueT::wrap(): {}", err);
417            }
418        }
419
420        let finalizer: Box<Box<dyn FnOnce(NapiEnv, T) -> NapiResult<()>>> =
421            Box::new(Box::new(finalizer));
422        let reference = napi_call!(
423            =napi_wrap,
424            self.env(),
425            self.raw(),
426            Box::into_raw(Box::new(data)) as DataPointer,
427            Some(finalizer_trampoline::<T>),
428            Box::into_raw(finalizer) as DataPointer,
429        );
430
431        Ok(NapiRef::from_raw(self.env(), reference))
432    }
433
434    /// Retrieves a native instance that was previously wrapped in a JavaScript object using
435    /// napi_wrap().
436    ///
437    /// When JavaScript code invokes a method or property accessor on the class, the corresponding
438    /// napi_callback is invoked. If the callback is for an instance method or accessor, then the
439    /// this argument to the callback is the wrapper object; the wrapped C++ instance that is the
440    /// target of the call can be obtained then by calling napi_unwrap() on the wrapper object.
441    ///
442    /// NB: if a there is no wrap or the wrap is just removed by NapiValue::remove_wrap, return
443    /// None.
444    fn unwrap<T>(&self) -> NapiResult<Option<&mut T>> {
445        let (status, value) = napi_call!(?napi_unwrap, self.env(), self.raw());
446        match status {
447            NapiStatus::Ok => unsafe { Ok(Some(&mut *(value as *mut T))) },
448            NapiStatus::InvalidArg => Ok(None),
449            err => Err(err),
450        }
451    }
452
453    /// Retrieves a native instance that was previously wrapped in the JavaScript object js_object
454    /// using napi_wrap() and removes the wrapping. If a finalize callback was associated with the
455    /// wrapping, it will no longer be called when the JavaScript object becomes garbage-collected.
456    fn remove_wrap<T>(&mut self) -> NapiResult<T> {
457        let value = napi_call!(=napi_remove_wrap, self.env(), self.raw());
458        unsafe {
459            let value: Box<T> = Box::from_raw(value as *mut _);
460            Ok(*value)
461        }
462    }
463
464    #[cfg(feature = "v8")]
465    /// Associates the value of the type_tag pointer with the JavaScript object.
466    /// napi_check_object_type_tag() can then be used to compare the tag that was attached to the
467    /// object with one owned by the addon to ensure that the object has the right type.
468    /// If the object already has an associated type tag, this API will return napi_invalid_arg.
469    fn type_tag_object(&self, tag: &NapiTypeTag) -> NapiResult<()> {
470        napi_call!(napi_type_tag_object, self.env(), self.raw(), tag)
471    }
472
473    #[cfg(feature = "v8")]
474    /// Compares the pointer given as type_tag with any that can be found on js_object. If no tag
475    /// is found on js_object or, if a tag is found but it does not match type_tag, then result is
476    /// set to false. If a tag is found and it matches type_tag, then result is set to true.
477    fn check_object_type_tag(&self, tag: &NapiTypeTag) -> NapiResult<bool> {
478        Ok(napi_call!(=napi_check_object_type_tag, self.env(), self.raw(), tag))
479    }
480}
481
482mod array;
483mod arraybuffer;
484mod bigint;
485mod boolean;
486mod buffer;
487mod class;
488mod dataview;
489mod date;
490mod error;
491mod external;
492mod function;
493mod global;
494mod name;
495mod null;
496mod number;
497mod object;
498mod promise;
499mod typedarray;
500mod undefined;
501
502pub use array::JsArray;
503pub use arraybuffer::JsArrayBuffer;
504pub use bigint::JsBigInt;
505pub use boolean::JsBoolean;
506pub use buffer::JsBuffer;
507pub use class::JsClass;
508pub use dataview::JsDataView;
509pub use date::JsDate;
510pub use error::JsError;
511pub use external::JsExternal;
512pub use function::{Function, JsFunction};
513pub use global::JsGlobal;
514pub use name::{JsString, JsSymbol};
515pub use null::JsNull;
516pub use number::JsNumber;
517pub use object::JsObject;
518pub use promise::JsPromise;
519pub use typedarray::JsTypedArray;
520pub use undefined::JsUndefined;