nodex-api 0.2.3

rust binding to node_api.h
Documentation
use crate::{api, env::NapiEnv, prelude::*};
use std::mem::MaybeUninit;

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct JsValue(pub(crate) NapiEnv, pub(crate) napi_value);

impl JsValue {
    /// `NapiEnv` of this `JsValue`
    pub fn env(&self) -> NapiEnv {
        self.0
    }

    /// raw napi_value of this `JsValue`
    pub fn raw(&self) -> napi_value {
        self.1
    }

    pub fn is_object(&self) -> NapiResult<bool> {
        napi_is!(self, JsObject)
    }

    /// view it as an object, may fail if it is not an object value
    pub fn as_object(&self) -> NapiResult<JsObject> {
        napi_as!(self, JsObject, NapiStatus::ObjectExpected)
    }

    pub fn is_string(&self) -> NapiResult<bool> {
        napi_is!(self, JsString)
    }

    /// view it as a string, may fail if it is not a string value
    pub fn as_string(&self) -> NapiResult<JsString> {
        napi_as!(self, JsString, NapiStatus::StringExpected)
    }

    pub fn is_symbol(&self) -> NapiResult<bool> {
        napi_is!(self, JsSymbol)
    }

    /// view it as a symbol, may fail if it is not a symbol value
    pub fn as_symbol(&self) -> NapiResult<JsSymbol> {
        napi_as!(self, JsSymbol, NapiStatus::InvalidArg)
    }

    pub fn is_array(&self) -> NapiResult<bool> {
        napi_is!(self, JsArray)
    }

    /// view it as an array, may fail if it is not an array value
    pub fn as_array(&self) -> NapiResult<JsArray> {
        napi_as!(self, JsArray, NapiStatus::ArrayExpected)
    }

    pub fn is_typedarray(&self) -> NapiResult<bool> {
        napi_is!(self, JsTypedArray)
    }
    /// view it as a typed_array, may fail if it is not a typed_array value
    pub fn as_typedarray(&self) -> NapiResult<JsTypedArray> {
        napi_as!(self, JsTypedArray, NapiStatus::InvalidArg)
    }

    pub fn is_arraybuffer(&self) -> NapiResult<bool> {
        napi_is!(self, JsArrayBuffer)
    }

    /// view it as an array_buffer, may fail if it is not an array_buffer value
    pub fn as_arraybuffer(&self) -> NapiResult<JsArrayBuffer> {
        napi_as!(self, JsArrayBuffer, NapiStatus::ArraybufferExpected)
    }

    pub fn is_buffer<const N: usize>(&self) -> NapiResult<bool> {
        napi_is!(self, JsBuffer<N>)
    }

    /// view it as a buffer, may fail if it is not a buffer value
    pub fn as_buffer<const N: usize>(&self) -> NapiResult<JsBuffer<N>> {
        napi_as!(self, JsBuffer<N>, NapiStatus::InvalidArg)
    }

    pub fn is_dataview(&self) -> NapiResult<bool> {
        napi_is!(self, JsDataView)
    }

    /// view it as a dataview, may fail if it is not a dataview value
    pub fn as_dataview(&self) -> NapiResult<JsDataView> {
        napi_as!(self, JsDataView, NapiStatus::InvalidArg)
    }

    pub fn is_external<T>(&self) -> NapiResult<bool> {
        napi_is!(self, JsExternal<T>)
    }

    /// view it as an external, may fail if it is not an external value
    pub fn as_external<T>(&self) -> NapiResult<JsExternal<T>> {
        napi_as!(self, JsExternal<T>, NapiStatus::InvalidArg)
    }

    pub fn is_function(&self) -> NapiResult<bool> {
        napi_is!(self, JsFunction)
    }

    /// view it as a number, may fail if it is not a number value
    pub fn as_function(&self) -> NapiResult<JsFunction> {
        napi_as!(self, JsFunction, NapiStatus::FunctionExpected)
    }

    pub fn is_number(&self) -> NapiResult<bool> {
        napi_is!(self, JsNumber)
    }

    /// view it as a number, may fail if it is not a number value
    pub fn as_number(&self) -> NapiResult<JsNumber> {
        napi_as!(self, JsNumber, NapiStatus::NumberExpected)
    }

    pub fn is_bigint<T: Copy>(&self) -> NapiResult<bool> {
        napi_is!(self, JsBigInt<T>)
    }

    /// view it as a bigint, may fail if it is not a bigint value
    pub fn as_bigint<T: Copy>(&self) -> NapiResult<JsBigInt<T>> {
        napi_as!(self, JsBigInt<T>, NapiStatus::BigintExpected)
    }

    pub fn is_boolean(&self) -> NapiResult<bool> {
        napi_is!(self, JsBoolean)
    }

    /// view it as a boolean, may fail if it is not a boolean value
    pub fn as_boolean(&self) -> NapiResult<JsBoolean> {
        napi_as!(self, JsBoolean, NapiStatus::BooleanExpected)
    }

    #[cfg(feature = "v5")]
    pub fn is_date(&self) -> NapiResult<bool> {
        napi_is!(self, JsDate)
    }

    #[cfg(feature = "v5")]
    /// view it as a date, may fail if it is not a date value
    pub fn as_date(&self) -> NapiResult<JsDate> {
        napi_as!(self, JsDate, NapiStatus::DateExpected)
    }
}

impl NapiValueT for JsValue {
    fn from_raw(env: NapiEnv, value: napi_value) -> JsValue {
        JsValue(env, value)
    }

    fn value(&self) -> JsValue {
        *self
    }
}

impl NapiValueCheck for JsValue {
    fn check(&self) -> NapiResult<bool> {
        Ok(true)
    }
}

pub trait NapiValueCheck {
    fn check(&self) -> NapiResult<bool>;
}

/// The trait for js value, which just store napi_value raw pointer.
pub trait NapiValueT: NapiValueCheck + Sized {
    /// construct value from raw pointer
    fn from_raw(env: NapiEnv, value: napi_value) -> Self;

    /// inner value
    fn value(&self) -> JsValue;

    /// napi_value type cast
    ///
    /// ## Safety
    ///
    /// It just put the handle in new type, does not check the real type.
    #[inline]
    unsafe fn cast<T: NapiValueT>(&self) -> T {
        T::from_raw(self.env(), self.raw())
    }

    /// Upcast to specified value
    #[inline]
    fn cast_checked<T: NapiValueT>(&self) -> NapiResult<T> {
        if unsafe { self.cast::<T>() }.check()? {
            Ok(T::from_raw(self.env(), self.raw()))
        } else {
            Err(NapiStatus::InvalidArg)
        }
    }

    /// Returns napi_ok if the API succeeded.
    /// - `napi_invalid_arg` if the type of value is not a known ECMAScript type and value is not an External value.
    /// This API represents behavior similar to invoking the typeof Operator on the object as defined in
    /// Section 12.5.5 of the ECMAScript Language Specification. However, there are some differences:
    /// It has support for detecting an External value.
    /// It detects null as a separate type, while ECMAScript typeof would detect object.
    /// If value has a type that is invalid, an error is returned.
    #[inline]
    fn kind(&self) -> NapiResult<NapiValuetype> {
        Ok(napi_call!(=napi_typeof, self.env(), self.raw()))
    }

    /// This API implements the abstract operation ToBoolean() as defined in Section 7.1.2 of the
    /// ECMAScript Language Specification.
    #[inline]
    fn coerce_to_bool(&self) -> NapiResult<JsBoolean> {
        Ok(JsBoolean::from_raw(
            self.env(),
            napi_call!(=napi_coerce_to_bool, self.env(), self.raw()),
        ))
    }

    /// This API implements the abstract operation ToNumber() as defined in Section 7.1.3 of the
    /// ECMAScript Language Specification. This function potentially runs JS code if the passed-in
    /// value is an object.
    #[inline]
    fn coerce_coerce_to_number(&self) -> NapiResult<JsNumber> {
        Ok(JsNumber::from_raw(
            self.env(),
            napi_call!(=napi_coerce_to_number, self.env(), self.raw()),
        ))
    }

    /// This API implements the abstract operation ToObject() as defined in Section 7.1.13 of the
    /// ECMAScript Language Specification.
    #[inline]
    fn coerce_to_object(&self) -> NapiResult<JsObject> {
        Ok(JsObject::from_raw(
            self.env(),
            napi_call!(=napi_coerce_to_object, self.env(), self.raw()),
        ))
    }

    /// This API implements the abstract operation ToString() as defined in Section 7.1.13 of the
    /// ECMAScript Language Specification. This function potentially runs JS code if the passed-in
    /// value is an object.
    #[inline]
    fn coerce_to_string(&self) -> NapiResult<JsString> {
        Ok(JsString::from_raw(
            self.env(),
            napi_call!(=napi_coerce_to_string, self.env(), self.raw()),
        ))
    }

    /// This API represents invoking the instanceof Operator on the object as defined in Section
    /// 12.10.4 of the ECMAScript Language Specification.
    fn instance_of(&self, constructor: JsFunction) -> NapiResult<bool> {
        Ok(napi_call!(=napi_instanceof, self.env(), self.raw(), constructor.raw()))
    }

    /// This API represents the invocation of the Strict Equality algorithm as defined in
    /// Section 7.2.14 of the ECMAScript Language Specification.
    fn equals(&self, rhs: impl NapiValueT) -> NapiResult<bool> {
        Ok(napi_call!(=napi_strict_equals, self.env(), self.raw(), rhs.raw()))
    }

    /// the `NapiEnv` of current value
    fn env(&self) -> NapiEnv {
        self.value().env()
    }

    /// the raw-handle of current value
    fn raw(&self) -> napi_value {
        self.value().raw()
    }

    /// get null singleton
    fn null(&self) -> NapiResult<JsNull> {
        JsNull::new(self.env())
    }

    /// get undefined singleton
    fn undefined(&self) -> NapiResult<JsUndefined> {
        JsUndefined::new(self.env())
    }

    /// get global singleton
    fn global(&self) -> NapiResult<JsGlobal> {
        JsGlobal::new(self.env())
    }

    /// value is throwable
    #[inline]
    fn throw(&self) -> NapiResult<()> {
        napi_call!(napi_throw, self.env(), self.raw())
    }

    /// This method allows the efficient definition of multiple properties on a given object. The
    /// properties are defined using property descriptors (see napi_property_descriptor). Given an
    /// array of such property descriptors, this API will set the properties on the object one at a
    /// time, as defined by DefineOwnProperty() (described in Section 9.1.6 of the ECMA-262
    /// specification).
    fn define_properties<P>(&self, properties: P) -> NapiResult<()>
    where
        P: AsRef<[NapiPropertyDescriptor]>,
    {
        napi_call!(
            napi_define_properties,
            self.env(),
            self.raw(),
            properties.as_ref().len(),
            properties.as_ref().as_ptr() as *const _,
        )
    }

    /// This is a hook which is fired when the value is gabage-collected.
    /// For napi >= 5, we use napi_add_finalizer,
    /// For napi < 5, we use napi_wrap.
    fn gc<Finalizer>(&mut self, finalizer: Finalizer) -> NapiResult<NapiRef>
    where
        Finalizer: FnOnce(NapiEnv) -> NapiResult<()>,
    {
        #[cfg(feature = "v5")]
        return self.finalizer(finalizer);
        #[cfg(not(feature = "v5"))]
        return self.wrap((), move |env, _| finalizer(env));
    }

    #[cfg(feature = "v5")]
    /// Adds a napi_finalize callback which will be called when the JavaScript object in
    /// js_object is ready for garbage collection. This API is similar to napi_wrap() except
    /// that:
    ///
    /// * the native data cannot be retrieved later using napi_unwrap(),
    /// * nor can it be removed later using napi_remove_wrap(), and
    /// * the API can be called multiple times with different data items in order to attach
    /// each of them to the JavaScript object, and
    /// * the object manipulated by the API can be used with napi_wrap().
    ///
    /// Caution: The optional returned reference (if obtained) should be deleted via
    /// napi_delete_reference ONLY in response to the finalize callback invocation.
    /// If it is deleted before then, then the finalize callback may never be invoked.
    /// herefore, when obtaining a reference a finalize callback is also required in order
    /// to enable correct disposal of the reference.
    fn finalizer<Finalizer>(&self, finalizer: Finalizer) -> NapiResult<NapiRef>
    where
        Finalizer: FnOnce(NapiEnv) -> NapiResult<()>,
    {
        // NB: Because we add a closure to the napi finalizer, it's better
        // to **CAPTURE** the leaked data from rust side, so here we just
        // ignore the passed in native data pointer.
        unsafe extern "C" fn finalizer_trampoline(
            env: NapiEnv,
            _: DataPointer,
            finalizer: DataPointer,
        ) {
            // NB: here we collect the memory of finalizer closure
            let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
                Box::from_raw(finalizer as _);
            if let Err(err) = finalizer(env) {
                log::error!("NapiValueT::finalizer(): {}", err);
            }
        }

        let finalizer: Box<Box<dyn FnOnce(NapiEnv) -> NapiResult<()>>> =
            Box::new(Box::new(finalizer));
        let reference = napi_call!(
            =napi_add_finalizer,
            self.env(),
            self.raw(),
            std::ptr::null_mut(),
            Some(finalizer_trampoline),
            Box::into_raw(finalizer) as DataPointer,
        );

        Ok(NapiRef::from_raw(self.env(), reference))
    }

    #[allow(clippy::type_complexity)]
    /// Wraps a native instance in a JavaScript object. The native instance can be retrieved
    /// later using napi_unwrap().
    ///
    /// When JavaScript code invokes a constructor for a class that was defined using napi_define_class(),
    /// the napi_callback for the constructor is invoked. After constructing an instance of the native class,
    /// the callback must then call napi_wrap() to wrap the newly constructed instance in the already-created
    /// JavaScript object that is the this argument to the constructor callback. (That this object was
    /// created from the constructor function's prototype, so it already has definitions of all the instance
    /// properties and methods.)
    ///
    /// Typically when wrapping a class instance, a finalize callback should be provided that simply
    /// deletes the native instance that is received as the data argument to the finalize callback.
    ///
    /// The optional returned reference is initially a weak reference, meaning it has a reference
    /// count of 0. Typically this reference count would be incremented temporarily during async
    /// operations that require the instance to remain valid.
    ///
    /// Caution: The optional returned reference (if obtained) should be deleted via napi_delete_reference
    /// ONLY in response to the finalize callback invocation. If it is deleted before then, then
    /// the finalize callback may never be invoked. Therefore, when obtaining a reference a finalize
    /// callback is also required in order to enable correct disposal of the reference.
    ///
    /// Calling napi_wrap() a second time on an object will return an error. To associate another
    /// native instance with the object, use napi_remove_wrap() first.
    fn wrap<T, Finalizer>(&mut self, data: T, finalizer: Finalizer) -> NapiResult<NapiRef>
    where
        Finalizer: FnOnce(NapiEnv, T) -> NapiResult<()>,
    {
        // NB: Because we add a closure to the napi finalizer, it's better
        // to **CAPTURE** the leaked data from rust side, so here we just
        // ignore the passed in native data pointer.
        unsafe extern "C" fn finalizer_trampoline<T>(
            env: NapiEnv,
            data: DataPointer,
            finalizer: DataPointer,
        ) {
            // NB: here we collect the memory of finalizer closure
            let finalizer: Box<Box<dyn FnOnce(NapiEnv, T) -> NapiResult<()>>> =
                Box::from_raw(finalizer as _);
            let data = Box::<T>::from_raw(data as _);
            if let Err(err) = finalizer(env, *data) {
                log::error!("NapiValueT::wrap(): {}", err);
            }
        }

        let finalizer: Box<Box<dyn FnOnce(NapiEnv, T) -> NapiResult<()>>> =
            Box::new(Box::new(finalizer));
        let reference = napi_call!(
            =napi_wrap,
            self.env(),
            self.raw(),
            Box::into_raw(Box::new(data)) as DataPointer,
            Some(finalizer_trampoline::<T>),
            Box::into_raw(finalizer) as DataPointer,
        );

        Ok(NapiRef::from_raw(self.env(), reference))
    }

    /// Retrieves a native instance that was previously wrapped in a JavaScript object using
    /// napi_wrap().
    ///
    /// When JavaScript code invokes a method or property accessor on the class, the corresponding
    /// napi_callback is invoked. If the callback is for an instance method or accessor, then the
    /// this argument to the callback is the wrapper object; the wrapped C++ instance that is the
    /// target of the call can be obtained then by calling napi_unwrap() on the wrapper object.
    ///
    /// NB: if a there is no wrap or the wrap is just removed by NapiValue::remove_wrap, return
    /// None.
    fn unwrap<T>(&self) -> NapiResult<Option<&mut T>> {
        let (status, value) = napi_call!(?napi_unwrap, self.env(), self.raw());
        match status {
            NapiStatus::Ok => unsafe { Ok(Some(&mut *(value as *mut T))) },
            NapiStatus::InvalidArg => Ok(None),
            err => Err(err),
        }
    }

    /// Retrieves a native instance that was previously wrapped in the JavaScript object js_object
    /// using napi_wrap() and removes the wrapping. If a finalize callback was associated with the
    /// wrapping, it will no longer be called when the JavaScript object becomes garbage-collected.
    fn remove_wrap<T>(&mut self) -> NapiResult<T> {
        let value = napi_call!(=napi_remove_wrap, self.env(), self.raw());
        unsafe {
            let value: Box<T> = Box::from_raw(value as *mut _);
            Ok(*value)
        }
    }

    #[cfg(feature = "v8")]
    /// Associates the value of the type_tag pointer with the JavaScript object.
    /// napi_check_object_type_tag() can then be used to compare the tag that was attached to the
    /// object with one owned by the addon to ensure that the object has the right type.
    /// If the object already has an associated type tag, this API will return napi_invalid_arg.
    fn type_tag_object(&self, tag: &NapiTypeTag) -> NapiResult<()> {
        napi_call!(napi_type_tag_object, self.env(), self.raw(), tag)
    }

    #[cfg(feature = "v8")]
    /// Compares the pointer given as type_tag with any that can be found on js_object. If no tag
    /// is found on js_object or, if a tag is found but it does not match type_tag, then result is
    /// set to false. If a tag is found and it matches type_tag, then result is set to true.
    fn check_object_type_tag(&self, tag: &NapiTypeTag) -> NapiResult<bool> {
        Ok(napi_call!(=napi_check_object_type_tag, self.env(), self.raw(), tag))
    }
}

mod array;
mod arraybuffer;
mod bigint;
mod boolean;
mod buffer;
mod class;
mod dataview;
mod date;
mod error;
mod external;
mod function;
mod global;
mod name;
mod null;
mod number;
mod object;
mod promise;
mod typedarray;
mod undefined;

pub use array::JsArray;
pub use arraybuffer::JsArrayBuffer;
pub use bigint::JsBigInt;
pub use boolean::JsBoolean;
pub use buffer::JsBuffer;
pub use class::JsClass;
pub use dataview::JsDataView;
pub use date::JsDate;
pub use error::JsError;
pub use external::JsExternal;
pub use function::{Function, JsFunction};
pub use global::JsGlobal;
pub use name::{JsString, JsSymbol};
pub use null::JsNull;
pub use number::JsNumber;
pub use object::JsObject;
pub use promise::JsPromise;
pub use typedarray::JsTypedArray;
pub use undefined::JsUndefined;