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;