Introduce #[napi] procedural macro to automation development boilerplate ()

* napi procedural macro for basic rust/JavaScript types
* introduce the `compat-mode` for `napi` and `napi-derive` crates for backward compatible
* remove #[inline] and let compiler to decide the inline behavior
* cli now can produce the `.d.ts` file for native binding
* many tests and example for the new procedural macro

Co-authored-by: LongYinan <lynweklm@gmail.com>
This commit is contained in:
forehalo 2021-09-23 01:29:09 +08:00 committed by GitHub
parent b64677aaad
commit 2467b7139b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
203 changed files with 5308 additions and 1500 deletions

56
crates/napi/Cargo.toml Normal file
View file

@ -0,0 +1,56 @@
[package]
authors = ["Nathan Sobo <nathan@github.com>", "Yinan Long <lynweklm@gmail.com>"]
description = "N-API bindings"
edition = "2018"
keywords = ["NodeJS", "Node", "FFI", "NAPI", "n-api"]
license = "MIT"
name = "napi"
readme = "README.md"
repository = "https://github.com/napi-rs/napi-rs"
version = "2.0.0-alpha.0"
[features]
compat-mode = []
default = ["napi3", "compat-mode"] # for most Node.js users
latin1 = ["encoding_rs"]
napi1 = []
napi2 = ["napi1"]
napi3 = ["napi2", "napi-sys/napi3"]
napi4 = ["napi3", "napi-sys/napi4"]
napi5 = ["napi4", "napi-sys/napi5"]
napi6 = ["napi5", "napi-sys/napi6"]
napi7 = ["napi6", "napi-sys/napi7"]
napi8 = ["napi7", "napi-sys/napi8"]
serde-json = ["serde", "serde_json"]
tokio_rt = ["tokio", "once_cell", "napi4"]
[dependencies]
ctor = "0.1"
napi-sys = {version = "1", path = "../sys"}
[target.'cfg(windows)'.dependencies]
winapi = {version = "0.3.9", features = ["winuser", "minwindef", "ntdef", "libloaderapi"]}
[dependencies.encoding_rs]
optional = true
version = "0.8"
[dependencies.tokio]
features = ["rt", "rt-multi-thread", "sync"]
optional = true
version = "1"
[dependencies.once_cell]
optional = true
version = "1"
[dependencies.serde]
optional = true
version = "1"
[dependencies.serde_json]
optional = true
version = "1"
[package.metadata.docs.rs]
all-features = true

228
crates/napi/README.md Normal file
View file

@ -0,0 +1,228 @@
# napi-rs
<a href="https://stakes.social/0x2C9F5c3ebC01A45D34198229E60eE186eCDc5C5E"><img src="https://badge.devprotocol.xyz/0x2C9F5c3ebC01A45D34198229E60eE186eCDc5C5E/descriptive" alt="Stake to support us"></img></a>
<a href="https://discord.gg/SpWzYHsKHs">
<img src="https://img.shields.io/discord/874290842444111882.svg?logo=discord&style=flat-square"
alt="chat" />
</a>
> This project was initialized from [xray](https://github.com/atom/xray)
A minimal library for building compiled `Node.js` add-ons in `Rust`.
<p>
<a href="https://docs.rs/crate/napi"><img src="https://docs.rs/napi/badge.svg"></img></a>
<a href="https://crates.io/crates/napi"><img src="https://img.shields.io/crates/v/napi.svg"></img></a>
<a href="https://www.npmjs.com/package/@napi-rs/cli"><img src="https://img.shields.io/npm/v/@napi-rs/cli.svg"></img></a>
</p>
## Ecosystem
<p align="center">
<a href="https://www.prisma.io/" target="_blank">
<img alt="Prisma" src="./images/prisma.svg" height="50px">
</a>
&nbsp;
&nbsp;
<a href="https://swc.rs/" target="_blank">
<img alt="swc" src="https://raw.githubusercontent.com/swc-project/logo/master/swc.png" height="50px">
</a>
&nbsp;
&nbsp;
<a href="https://parceljs.org/" target="_blank">
<img alt="Parcel" src="https://user-images.githubusercontent.com/19409/31321658-f6aed0f2-ac3d-11e7-8100-1587e676e0ec.png" height="50px">
</a>
&nbsp;
<a href="https://nextjs.org/">
<img alt="next.js" src="https://assets.vercel.com/image/upload/v1607554385/repositories/next-js/next-logo.png" height="50px">
&nbsp;
<img alt="nextjs.svg" src="./images/nextjs.svg" height="50px">
</a>
</p>
## Platform Support
![Lint](https://github.com/napi-rs/napi-rs/workflows/Lint/badge.svg)
![Linux N-API@3](https://github.com/napi-rs/napi-rs/workflows/Linux%20N-API@3/badge.svg)
![Linux musl](https://github.com/napi-rs/napi-rs/workflows/Linux%20musl/badge.svg)
![macOS/Windows/Linux x64](https://github.com/napi-rs/napi-rs/workflows/macOS/Windows/Linux%20x64/badge.svg)
![Linux-aarch64](https://github.com/napi-rs/napi-rs/workflows/Linux-aarch64/badge.svg)
![Linux-armv7](https://github.com/napi-rs/napi-rs/workflows/Linux-armv7/badge.svg)
![macOS-Android](https://github.com/napi-rs/napi-rs/workflows/macOS-Android/badge.svg)
![Windows i686](https://github.com/napi-rs/napi-rs/workflows/Windows%20i686/badge.svg)
[![Windows arm64](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml/badge.svg)](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml)
[![FreeBSD](https://api.cirrus-ci.com/github/napi-rs/napi-rs.svg)](https://cirrus-ci.com/github/napi-rs/napi-rs?branch=main)
| | node12 | node14 | node16 |
| --------------------- | ------ | ------ | ------ |
| Windows x64 | ✓ | ✓ | ✓ |
| Windows x86 | ✓ | ✓ | ✓ |
| Windows arm64 | ✓ | ✓ | ✓ |
| macOS x64 | ✓ | ✓ | ✓ |
| macOS aarch64 | ✓ | ✓ | ✓ |
| Linux x64 gnu | ✓ | ✓ | ✓ |
| Linux x64 musl | ✓ | ✓ | ✓ |
| Linux aarch64 gnu | ✓ | ✓ | ✓ |
| Linux aarch64 musl | ✓ | ✓ | ✓ |
| Linux arm gnueabihf | ✓ | ✓ | ✓ |
| Linux aarch64 android | ✓ | ✓ | ✓ |
| FreeBSD x64 | ✓ | ✓ | ✓ |
This library depends on Node-API and requires `Node@10.0.0` or later.
We already have some packages written by `napi-rs`: [node-rs](https://github.com/napi-rs/node-rs)
One nice feature is that this crate allows you to build add-ons purely with the `Rust/JavaScript` toolchain and without involving `node-gyp`.
## Taste
> You can start from [package-template](https://github.com/napi-rs/package-template) to play with `napi-rs`
### Define JavaScript functions
```rust
#[macro_use]
extern crate napi;
// import the preludes
use napi::bindgen_prelude::*;
/// module registerion is done by the runtime, no need to explicitly do it now.
#[napi]
fn fibonacci(n: u32) -> u32 {
match n {
1 | 2 => 1,
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
}
}
/// use `Fn`, `FnMut` or `FnOnce` traits to defined JavaScript callbacks
/// the return type of callbacks can only be `Result`.
#[napi]
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
}
/// or, define the callback signature in where clause
#[napi]
fn test_callback<T>(callback: T)
where T: Fn(String) -> Result<()>
{}
```
Checkout more examples in [examples](./examples) folder
## Building
This repository is a `Cargo` crate. Any napi-based add-on should contain `Cargo.toml` to make it a Cargo crate.
In your `Cargo.toml` you need to set the `crate-type` to `"cdylib"` so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also need to add this crate as a dependency.
```toml
[package]
name = "awesome"
[lib]
crate-type = ["cdylib"]
[dependencies]
napi = "2"
napi-derive = "2"
[build-dependencies]
napi-build = "1"
```
And create `build.rs` in your own project:
```rust
// build.rs
extern crate napi_build;
fn main() {
napi_build::setup();
}
```
So far, the `napi` build script has only been tested on `macOS` `Linux` `Windows x64 MSVC` and `FreeBSD`.
Install the `@napi-rs/cli` to help you build your `Rust` codes and copy `Dynamic lib` file to `.node` file in case you can `require` it in your program.
```js
{
"package": "awesome-package",
"devDependencies": {
"@napi-rs/cli": "^1.0.0"
},
"napi": {
"name": "jarvis" // <----------- Config the name of native addon, or the napi command will use the name of `Cargo.toml` for the binary file name.
},
"scripts": {
"build": "napi build --release",
"build:debug": "napi build"
}
}
```
Then you can require your native binding:
```js
require('./jarvis.node')
```
The `module_name` would be your `package` name in your `Cargo.toml`.
`xxx => ./xxx.node`
`xxx-yyy => ./xxx_yyy.node`
You can also copy `Dynamic lib` file to an appointed location:
```bash
napi build [--release] ./dll
napi build [--release] ./artifacts
```
There are [documents](./cli) which contains more details about the `@napi-rs/cli` usage.
## Testing
Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the `test_module` subdirectory.
To run tests:
```sh
yarn build:test
yarn test
```
## Related projects
- [neon](https://www.neon-bindings.com)
- [node-bindgen](https://github.com/infinyon/node-bindgen)
## Features table
| Rust Type | Node Type | [NAPI Version](https://nodejs.org/api/n-api.html#n_api_node_api_version_matrix) | Minimal Node version |
| ----------------------- | ---------------------- | ------------------------------------------------------------------------------- | -------------------- |
| u32 | Number | 1 | v8.0.0 |
| i32/i64 | Number | 1 | v8.0.0 |
| f64 | Number | 1 | v8.0.0 |
| bool | Boolean | 1 | v8.0.0 |
| String/&'a str | String | 1 | v8.0.0 |
| Latin1String | String | 1 | v8.0.0 |
| UTF16String | String | 1 | v8.0.0 |
| Object | Object | 1 | v8.0.0 |
| Array | Array<any> | 1 | v8.0.0 |
| Vec<T> | Array<T> | 1 | v8.0.0 |
| Buffer | Buffer | 1 | v8.0.0 |
| Null | null | 1 | v8.0.0 |
| Undefined/() | undefined | 1 | v8.0.0 |
| Result<()> | Error | 1 | v8.0.0 |
| T: Fn(...) -> Result<T> | function | 1 | v8.0.0 |
| (NOT YET) | global | 1 | v8.0.0 |
| (NOT YET) | Symbol | 1 | v8.0.0 |
| (NOT YET) | Promise<T> | 1 | b8.5.0 |
| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 |
| (NOT YET) | threadsafe function | 4 | v10.6.0 |
| (NOT YET) | BigInt | 6 | v10.7.0 |

View file

@ -0,0 +1,28 @@
use std::mem;
use crate::{sys, Status};
/// Notice
/// The hook will be removed if `AsyncCleanupHook` was `dropped`.
/// If you want keep the hook until node process exited, call the `AsyncCleanupHook::forget`.
#[repr(transparent)]
pub struct AsyncCleanupHook(pub(crate) sys::napi_async_cleanup_hook_handle);
impl AsyncCleanupHook {
/// Safe to forget it.
/// Things will be cleanup before process exited.
pub fn forget(self) {
mem::forget(self);
}
}
impl Drop for AsyncCleanupHook {
fn drop(&mut self) {
let status = unsafe { sys::napi_remove_async_cleanup_hook(self.0) };
assert!(
status == sys::Status::napi_ok,
"Delete async cleanup hook failed: {}",
Status::from(status)
);
}
}

View file

@ -0,0 +1,119 @@
use std::mem;
use std::os::raw::{c_char, c_void};
use std::ptr;
use crate::{
check_status,
js_values::{NapiRaw, NapiValue},
sys, Env, JsError, JsObject, Result, Task,
};
struct AsyncWork<T: Task> {
inner_task: T,
deferred: sys::napi_deferred,
value: Result<mem::MaybeUninit<T::Output>>,
napi_async_work: sys::napi_async_work,
}
pub struct AsyncWorkPromise<'env> {
napi_async_work: sys::napi_async_work,
raw_promise: sys::napi_value,
env: &'env Env,
}
impl<'env> AsyncWorkPromise<'env> {
pub fn promise_object(&self) -> JsObject {
unsafe { JsObject::from_raw_unchecked(self.env.0, self.raw_promise) }
}
pub fn cancel(self) -> Result<()> {
check_status!(unsafe { sys::napi_cancel_async_work(self.env.0, self.napi_async_work) })
}
}
pub fn run<T: Task>(env: &Env, task: T) -> Result<AsyncWorkPromise<'_>> {
let mut raw_resource = ptr::null_mut();
check_status!(unsafe { sys::napi_create_object(env.0, &mut raw_resource) })?;
let mut raw_promise = ptr::null_mut();
let mut deferred = ptr::null_mut();
check_status!(unsafe { sys::napi_create_promise(env.0, &mut deferred, &mut raw_promise) })?;
let mut raw_name = ptr::null_mut();
let s = "napi_rs_async_work";
check_status!(unsafe {
sys::napi_create_string_utf8(env.0, s.as_ptr() as *const c_char, s.len(), &mut raw_name)
})?;
let result = Box::leak(Box::new(AsyncWork {
inner_task: task,
deferred,
value: Ok(mem::MaybeUninit::zeroed()),
napi_async_work: ptr::null_mut(),
}));
check_status!(unsafe {
sys::napi_create_async_work(
env.0,
raw_resource,
raw_name,
Some(execute::<T> as unsafe extern "C" fn(env: sys::napi_env, data: *mut c_void)),
Some(
complete::<T>
as unsafe extern "C" fn(env: sys::napi_env, status: sys::napi_status, data: *mut c_void),
),
result as *mut _ as *mut c_void,
&mut result.napi_async_work,
)
})?;
check_status!(unsafe { sys::napi_queue_async_work(env.0, result.napi_async_work) })?;
Ok(AsyncWorkPromise {
napi_async_work: result.napi_async_work,
raw_promise,
env,
})
}
unsafe impl<T: Task> Send for AsyncWork<T> {}
unsafe impl<T: Task> Sync for AsyncWork<T> {}
/// env here is the same with the one in `CallContext`.
/// So it actually could do nothing here, because `execute` function is called in the other thread mostly.
unsafe extern "C" fn execute<T: Task>(_env: sys::napi_env, data: *mut c_void) {
let mut work = Box::from_raw(data as *mut AsyncWork<T>);
let _ = mem::replace(
&mut work.value,
work.inner_task.compute().map(mem::MaybeUninit::new),
);
Box::leak(work);
}
unsafe extern "C" fn complete<T: Task>(
env: sys::napi_env,
status: sys::napi_status,
data: *mut c_void,
) {
let mut work = Box::from_raw(data as *mut AsyncWork<T>);
let value_ptr = mem::replace(&mut work.value, Ok(mem::MaybeUninit::zeroed()));
let deferred = mem::replace(&mut work.deferred, ptr::null_mut());
let napi_async_work = mem::replace(&mut work.napi_async_work, ptr::null_mut());
let value = match value_ptr {
Ok(v) => {
let output = v.assume_init();
work.inner_task.resolve(Env::from_raw(env), output)
}
Err(e) => work.inner_task.reject(Env::from_raw(env), e),
};
match check_status!(status).and_then(move |_| value) {
Ok(v) => {
let status = sys::napi_resolve_deferred(env, deferred, v.raw());
debug_assert!(status == sys::Status::napi_ok, "Reject promise failed");
}
Err(e) => {
let status = sys::napi_reject_deferred(env, deferred, JsError::from(e).into_value(env));
debug_assert!(status == sys::Status::napi_ok, "Reject promise failed");
}
};
let delete_status = sys::napi_delete_async_work(env, napi_async_work);
debug_assert!(
delete_status == sys::Status::napi_ok,
"Delete async work failed"
);
}

View file

@ -0,0 +1,118 @@
use crate::{bindgen_prelude::*, check_status, sys, Result};
use std::{ffi::c_void, ptr};
pub struct CallbackInfo<const N: usize> {
env: sys::napi_env,
this: sys::napi_value,
args: [sys::napi_value; N],
}
impl<const N: usize> CallbackInfo<N> {
pub fn new(
env: sys::napi_env,
callback_info: sys::napi_callback_info,
required_argc: Option<usize>,
) -> Result<Self> {
let mut this = ptr::null_mut();
let mut args = [ptr::null_mut(); N];
let mut argc = N;
unsafe {
check_status!(
sys::napi_get_cb_info(
env,
callback_info,
&mut argc,
args.as_mut_ptr(),
&mut this,
ptr::null_mut(),
),
"Failed to initialize napi function call."
)?;
};
if let Some(required_argc) = required_argc {
if required_argc > argc {
return Err(Error::new(
Status::InvalidArg,
format!(
"{} arguments required by received {}.",
required_argc, &argc
),
));
}
}
Ok(Self { env, this, args })
}
pub fn get_arg(&self, index: usize) -> sys::napi_value {
*self
.args
.get(index)
.unwrap_or_else(|| panic!("index {} must < {}", index, N))
}
pub fn this(&self) -> sys::napi_value {
debug_assert!(!self.this.is_null());
self.this
}
pub fn construct<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
let obj = Box::new(obj);
let mut result = std::ptr::null_mut();
let this = self.this();
unsafe {
check_status!(
sys::napi_wrap(
self.env,
this,
Box::into_raw(obj) as *mut std::ffi::c_void,
Some(raw_finalize_unchecked::<T>),
ptr::null_mut(),
&mut result
),
"Failed to initialize class `{}`",
js_name,
)?;
};
Ok(this)
}
pub fn unwrap_borrow_mut<T>(&mut self) -> Result<&'static mut T>
where
T: FromNapiMutRef + TypeName,
{
let mut wrapped_val: *mut c_void = std::ptr::null_mut();
unsafe {
check_status!(
sys::napi_unwrap(self.env, self.this, &mut wrapped_val),
"Failed to unwrap exclusive reference of `{}` type from napi value",
T::type_name(),
)?;
Ok(&mut *(wrapped_val as *mut T))
}
}
pub fn unwrap_borrow<T>(&mut self) -> Result<&'static T>
where
T: FromNapiRef + TypeName,
{
let mut wrapped_val: *mut c_void = std::ptr::null_mut();
unsafe {
check_status!(
sys::napi_unwrap(self.env, self.this, &mut wrapped_val),
"Failed to unwrap shared reference of `{}` type from napi value",
T::type_name(),
)?;
Ok(&*(wrapped_val as *const T))
}
}
}

View file

@ -0,0 +1,22 @@
use crate::{sys, Result};
use super::{Array, Object};
#[repr(transparent)]
pub struct Env(sys::napi_env);
impl From<sys::napi_env> for Env {
fn from(raw_env: sys::napi_env) -> Env {
Env(raw_env)
}
}
impl Env {
pub fn create_object(&self) -> Result<Object> {
Object::new(self.0)
}
pub fn create_array(&self, len: u32) -> Result<Array> {
Array::new(self.0, len)
}
}

View file

@ -0,0 +1,10 @@
#[doc(hidden)]
#[macro_export]
macro_rules! check_status_or_throw {
($env:expr, $code:expr, $($msg:tt)*) => {
if let Err(e) = $crate::check_status!($code, $($msg)*) {
$crate::JsError::from(e).throw_into($env);
return;
}
};
}

View file

@ -0,0 +1,145 @@
use crate::{check_status, sys, Error, Result, Status, ValueType};
use std::ptr;
mod array;
mod boolean;
mod buffer;
mod nil;
mod number;
mod object;
mod string;
pub use array::*;
pub use buffer::*;
pub use nil::*;
pub use object::*;
pub use string::*;
#[cfg(feature = "latin1")]
pub use string::latin1_string::*;
pub trait TypeName {
fn type_name() -> &'static str;
}
pub trait ToNapiValue {
/// # Safety
///
/// this function called to convert rust values to napi values
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value>;
}
pub trait FromNapiValue: Sized {
/// # Safety
///
/// this function called to convert napi values to native rust values
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self>;
}
pub trait FromNapiRef {
/// # Safety
///
/// this function called to convert napi values to native rust values
unsafe fn from_napi_ref(env: sys::napi_env, napi_val: sys::napi_value) -> Result<&'static Self>;
}
pub trait FromNapiMutRef {
/// # Safety
///
/// this function called to convert napi values to native rust values
unsafe fn from_napi_mut_ref(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> Result<&'static mut Self>;
}
pub trait ValidateNapiValue: FromNapiValue + TypeName {
fn type_of() -> Vec<ValueType> {
vec![]
}
/// # Safety
///
/// this function called to validate whether napi value passed to rust is valid type
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<()> {
let available_types = Self::type_of();
if available_types.is_empty() {
return Ok(());
}
let mut result = -1;
check_status!(
sys::napi_typeof(env, napi_val, &mut result),
"Failed to detect napi value type",
)?;
let received_type = ValueType::from(result);
if available_types.contains(&received_type) {
Ok(())
} else {
Err(Error::new(
Status::InvalidArg,
if available_types.len() > 1 {
format!(
"Expect value to be one of {:?}, but received {}",
available_types, received_type
)
} else {
format!(
"Expect value to be {}, but received {}",
available_types[0], received_type
)
},
))
}
}
}
impl<T> TypeName for Option<T>
where
T: TypeName,
{
fn type_name() -> &'static str {
"Option"
}
}
impl<T> FromNapiValue for Option<T>
where
T: FromNapiValue + TypeName,
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut val_type = 0;
check_status!(
sys::napi_typeof(env, napi_val, &mut val_type),
"Failed to convert napi value into rust type `Option<{}>`",
T::type_name()
)?;
match val_type {
sys::ValueType::napi_undefined => Ok(None),
_ => Ok(Some(T::from_napi_value(env, napi_val)?)),
}
}
}
impl<T> ToNapiValue for Option<T>
where
T: ToNapiValue + TypeName,
{
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
match val {
Some(val) => T::to_napi_value(env, val),
None => {
let mut ptr = ptr::null_mut();
check_status!(
sys::napi_get_undefined(env, &mut ptr),
"Failed to convert rust type `Option<{}>` into napi value",
T::type_name(),
)?;
Ok(ptr)
}
}
}
}

View file

@ -0,0 +1,172 @@
use crate::{bindgen_prelude::*, check_status, sys, ValueType};
use std::ptr;
pub struct Array {
env: sys::napi_env,
inner: sys::napi_value,
len: u32,
}
impl Array {
pub(crate) fn new(env: sys::napi_env, len: u32) -> Result<Self> {
let mut ptr = ptr::null_mut();
unsafe {
check_status!(
sys::napi_create_array_with_length(env, len as usize, &mut ptr),
"Failed to create napi Array"
)?;
}
Ok(Array {
env,
inner: ptr,
len,
})
}
pub fn get<T: FromNapiValue>(&self, index: u32) -> Result<Option<T>> {
if index >= self.len() {
return Ok(None);
}
let mut ret = ptr::null_mut();
unsafe {
check_status!(
sys::napi_get_element(self.env, self.inner, index, &mut ret),
"Failed to get element with index `{}`",
index,
)?;
Ok(Some(T::from_napi_value(self.env, ret)?))
}
}
pub fn set<T: ToNapiValue>(&mut self, index: u32, val: T) -> Result<()> {
unsafe {
let napi_val = T::to_napi_value(self.env, val)?;
check_status!(
sys::napi_set_element(self.env, self.inner, index, napi_val),
"Failed to set element with index `{}`",
index,
)?;
if index >= self.len() {
self.len = index + 1;
}
Ok(())
}
}
pub fn insert<T: ToNapiValue>(&mut self, val: T) -> Result<()> {
self.set(self.len(), val)?;
Ok(())
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u32 {
self.len
}
}
impl TypeName for Array {
fn type_name() -> &'static str {
"Array"
}
}
impl ToNapiValue for Array {
unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
Ok(val.inner)
}
}
impl FromNapiValue for Array {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut is_arr = false;
check_status!(
sys::napi_is_array(env, napi_val, &mut is_arr),
"Failed to check given napi value is array"
)?;
if is_arr {
let mut len = 0;
check_status!(
sys::napi_get_array_length(env, napi_val, &mut len),
"Failed to get Array length",
)?;
Ok(Array {
inner: napi_val,
env,
len,
})
} else {
Err(Error::new(
Status::InvalidArg,
"Given napi value is not an array".to_owned(),
))
}
}
}
impl ValidateNapiValue for Array {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
}
}
impl<T> TypeName for Vec<T> {
fn type_name() -> &'static str {
"Array<T>"
}
}
impl<T> ToNapiValue for Vec<T>
where
T: ToNapiValue,
{
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
let mut arr = Array::new(env, val.len() as u32)?;
for (i, v) in val.into_iter().enumerate() {
arr.set(i as u32, v)?;
}
Array::to_napi_value(env, arr)
}
}
impl<T> FromNapiValue for Vec<T>
where
T: FromNapiValue,
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let arr = Array::from_napi_value(env, napi_val)?;
let mut vec = vec![];
for i in 0..arr.len() {
if let Some(val) = arr.get::<T>(i)? {
vec.push(val);
} else {
return Err(Error::new(
Status::InvalidArg,
"Found inconsistent data type in Array<T> when converting to Rust Vec<T>".to_owned(),
));
}
}
Ok(vec)
}
}
impl<T> ValidateNapiValue for Vec<T>
where
T: FromNapiValue,
{
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
}
}

View file

@ -0,0 +1,39 @@
use crate::{bindgen_prelude::*, check_status, sys, ValueType};
impl TypeName for bool {
fn type_name() -> &'static str {
"bool"
}
}
impl ValidateNapiValue for bool {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Boolean]
}
}
impl ToNapiValue for bool {
unsafe fn to_napi_value(env: sys::napi_env, val: bool) -> Result<sys::napi_value> {
let mut ptr = std::ptr::null_mut();
check_status!(
sys::napi_get_boolean(env, val, &mut ptr),
"Failed to convert rust type `bool` into napi value",
)?;
Ok(ptr)
}
}
impl FromNapiValue for bool {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut ret = false;
check_status!(
sys::napi_get_value_bool(env, napi_val, &mut ret),
"Failed to convert napi value into rust type `bool`",
)?;
Ok(ret)
}
}

View file

@ -0,0 +1,99 @@
use std::ops::{Deref, DerefMut};
use std::{mem, ptr};
use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType};
/// zero copy u8 vector shared between rust and napi
pub struct Buffer {
raw: Option<sys::napi_value>,
inner: mem::ManuallyDrop<Vec<u8>>,
}
impl From<Vec<u8>> for Buffer {
fn from(data: Vec<u8>) -> Self {
Buffer {
raw: None,
inner: mem::ManuallyDrop::new(data),
}
}
}
impl AsRef<[u8]> for Buffer {
fn as_ref(&self) -> &[u8] {
self.inner.as_slice()
}
}
impl AsMut<[u8]> for Buffer {
fn as_mut(&mut self) -> &mut [u8] {
self.inner.as_mut_slice()
}
}
impl Deref for Buffer {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.inner.as_slice()
}
}
impl DerefMut for Buffer {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.as_mut_slice()
}
}
impl TypeName for Buffer {
fn type_name() -> &'static str {
"Vec<u8>"
}
}
impl FromNapiValue for Buffer {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut buf = ptr::null_mut();
let mut len = 0;
check_status!(
sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len as *mut usize),
"Failed to convert napi buffer into rust Vec<u8>"
)?;
Ok(Self {
raw: Some(napi_val),
inner: mem::ManuallyDrop::new(Vec::from_raw_parts(buf as *mut _, len, len)),
})
}
}
impl ToNapiValue for Buffer {
unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
match val.raw {
Some(raw) => Ok(raw),
None => {
let len = val.inner.len();
let mut ret = ptr::null_mut();
check_status!(
sys::napi_create_external_buffer(
env,
len,
val.inner.as_mut_ptr() as *mut _,
Some(drop_buffer),
Box::into_raw(Box::new((len, val.inner.capacity()))) as *mut _,
&mut ret,
),
"Failed to create napi buffer"
)?;
Ok(ret)
}
}
}
}
impl ValidateNapiValue for Buffer {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Object]
}
}

View file

@ -0,0 +1,100 @@
use std::ptr;
use crate::{bindgen_prelude::*, check_status, sys, type_of, Error, Result, Status, ValueType};
pub struct Null;
pub type Undefined = ();
impl TypeName for Null {
fn type_name() -> &'static str {
"null"
}
}
impl ValidateNapiValue for Null {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Null]
}
}
impl FromNapiValue for Null {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
match type_of!(env, napi_val) {
Ok(ValueType::Null) => Ok(Null),
_ => Err(Error::new(
Status::InvalidArg,
"Value is not null".to_owned(),
)),
}
}
}
impl ToNapiValue for Null {
unsafe fn to_napi_value(env: sys::napi_env, _val: Self) -> Result<sys::napi_value> {
let mut ret = ptr::null_mut();
check_status!(
sys::napi_get_null(env, &mut ret),
"Failed to create napi null value"
)?;
Ok(ret)
}
}
impl TypeName for Undefined {
fn type_name() -> &'static str {
"undefined"
}
}
impl ValidateNapiValue for Undefined {
fn type_of() -> Vec<ValueType> {
vec![ValueType::Undefined]
}
}
impl FromNapiValue for Undefined {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
// TODO: with typecheck
match type_of!(env, napi_val) {
Ok(ValueType::Undefined) => Ok(()),
_ => Err(Error::new(
Status::InvalidArg,
"Value is not undefined".to_owned(),
)),
}
}
}
impl ToNapiValue for Undefined {
unsafe fn to_napi_value(env: sys::napi_env, _val: Self) -> Result<sys::napi_value> {
let mut ret = ptr::null_mut();
check_status!(
sys::napi_get_undefined(env, &mut ret),
"Failed to create napi null value"
)?;
Ok(ret)
}
}
impl ToNapiValue for Result<()> {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
match val {
Ok(_) => Ok(Null::to_napi_value(env, Null).unwrap_or_else(|_| ptr::null_mut())),
Err(e) => {
let error_code = String::to_napi_value(env, format!("{:?}", e.status))?;
let reason = String::to_napi_value(env, e.reason)?;
let mut error = ptr::null_mut();
check_status!(
sys::napi_create_error(env, error_code, reason, &mut error),
"Failed to create napi error"
)?;
Ok(error)
}
}
}
}

View file

@ -0,0 +1,58 @@
use super::{check_status, sys, Result};
macro_rules! impl_number_conversions {
( $( ($name:literal, $t:ty, $get:ident, $create:ident) ,)* ) => {
$(
impl $crate::bindgen_prelude::TypeName for $t {
#[inline(always)]
fn type_name() -> &'static str {
$name
}
}
impl $crate::bindgen_prelude::ValidateNapiValue for $t {
#[inline(always)]
fn type_of() -> Vec<$crate::ValueType> {
vec![$crate::ValueType::Number]
}
}
impl $crate::bindgen_prelude::ToNapiValue for $t {
#[inline(always)]
unsafe fn to_napi_value(env: $crate::sys::napi_env, val: $t) -> Result<$crate::sys::napi_value> {
let mut ptr = std::ptr::null_mut();
check_status!(
sys::$create(env, val, &mut ptr),
"Failed to convert rust type `{}` into napi value",
$name,
)?;
Ok(ptr)
}
}
impl $crate::bindgen_prelude::FromNapiValue for $t {
#[inline(always)]
unsafe fn from_napi_value(env: $crate::sys::napi_env, napi_val: $crate::sys::napi_value) -> Result<Self> {
let mut ret = 0 as $t;
check_status!(
sys::$get(env, napi_val, &mut ret),
"Failed to convert napi value into rust type `{}`",
$name
)?;
Ok(ret)
}
}
)*
};
}
impl_number_conversions!(
("u32", u32, napi_get_value_uint32, napi_create_uint32),
("i32", i32, napi_get_value_int32, napi_create_int32),
("i64", i64, napi_get_value_int64, napi_create_int64),
("f64", f64, napi_get_value_double, napi_create_double),
);

View file

@ -0,0 +1,106 @@
use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType};
use std::{ffi::CString, ptr};
pub struct Object {
env: sys::napi_env,
inner: sys::napi_value,
}
impl Object {
pub(crate) fn new(env: sys::napi_env) -> Result<Self> {
let mut ptr = ptr::null_mut();
unsafe {
check_status!(
sys::napi_create_object(env, &mut ptr),
"Failed to create napi Object"
)?;
}
Ok(Object { env, inner: ptr })
}
pub fn get<T: FromNapiValue>(&self, field: String) -> Result<Option<T>> {
let c_field = CString::new(field)?;
unsafe {
let mut ret = ptr::null_mut();
check_status!(
sys::napi_get_named_property(self.env, self.inner, c_field.as_ptr(), &mut ret),
"Failed to get property with field `{}`",
c_field.to_string_lossy(),
)?;
let ty = type_of!(self.env, ret)?;
Ok(if ty == ValueType::Undefined {
None
} else {
Some(T::from_napi_value(self.env, ret)?)
})
}
}
pub fn set<T: ToNapiValue>(&mut self, field: String, val: T) -> Result<()> {
let c_field = CString::new(field)?;
unsafe {
let napi_val = T::to_napi_value(self.env, val)?;
check_status!(
sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val),
"Failed to set property with field `{}`",
c_field.to_string_lossy(),
)?;
Ok(())
}
}
pub fn keys(obj: &Object) -> Result<Vec<String>> {
let mut names = ptr::null_mut();
unsafe {
check_status!(
sys::napi_get_property_names(obj.env, obj.inner, &mut names),
"Failed to get property names of given object"
)?;
}
let names = unsafe { Array::from_napi_value(obj.env, names)? };
let mut ret = vec![];
for i in 0..names.len() {
ret.push(names.get::<String>(i)?.unwrap());
}
Ok(ret)
}
}
impl TypeName for Object {
fn type_name() -> &'static str {
"Object"
}
}
impl ToNapiValue for Object {
unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
Ok(val.inner)
}
}
impl FromNapiValue for Object {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let value_type = type_of!(env, napi_val)?;
match value_type {
ValueType::Object => Ok(Self {
inner: napi_val,
env,
}),
_ => Err(Error::new(
Status::InvalidArg,
"Given napi value is not an object".to_owned(),
)),
}
}
}

View file

@ -0,0 +1,235 @@
use crate::{bindgen_prelude::*, check_status, sys, Error, Result, Status};
use std::ffi::CStr;
use std::fmt::Display;
#[cfg(feature = "latin1")]
use std::mem;
use std::ops::Deref;
use std::ptr;
impl TypeName for String {
fn type_name() -> &'static str {
"String"
}
}
impl ToNapiValue for String {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
let mut ptr = ptr::null_mut();
check_status!(
sys::napi_create_string_utf8(env, val.as_ptr() as *const _, val.len(), &mut ptr),
"Failed to convert rust `String` into napi `string`"
)?;
Ok(ptr)
}
}
impl FromNapiValue for String {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut len = 0;
check_status!(
sys::napi_get_value_string_utf8(env, napi_val, ptr::null_mut(), 0, &mut len),
"Failed to convert napi `string` into rust type `String`",
)?;
// end char len in C
len += 1;
let mut ret = Vec::with_capacity(len);
let buf_ptr = ret.as_mut_ptr();
let mut written_char_count = 0;
check_status!(
sys::napi_get_value_string_utf8(env, napi_val, buf_ptr, len, &mut written_char_count),
"Failed to convert napi `string` into rust type `String`"
)?;
match CStr::from_ptr(buf_ptr).to_str() {
Err(e) => Err(Error::new(
Status::InvalidArg,
format!("Failed to read utf8 string, {}", e),
)),
Ok(s) => Ok(s.to_owned()),
}
}
}
impl TypeName for &str {
fn type_name() -> &'static str {
"String"
}
}
impl ToNapiValue for &str {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
String::to_napi_value(env, val.to_owned())
}
}
#[derive(Debug)]
pub struct Utf16String(String);
impl From<String> for Utf16String {
fn from(s: String) -> Self {
Utf16String(s)
}
}
impl Display for Utf16String {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Deref for Utf16String {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl TypeName for Utf16String {
fn type_name() -> &'static str {
"String(utf16)"
}
}
impl FromNapiValue for Utf16String {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut len = 0;
check_status!(
sys::napi_get_value_string_utf16(env, napi_val, ptr::null_mut(), 0, &mut len),
"Failed to convert napi `utf16 string` into rust type `String`",
)?;
// end char len in C
len += 1;
let mut ret = vec![0; len];
let mut written_char_count = 0;
check_status!(
sys::napi_get_value_string_utf16(
env,
napi_val,
ret.as_mut_ptr(),
len,
&mut written_char_count
),
"Failed to convert napi `utf16 string` into rust type `String`",
)?;
let (_, ret) = ret.split_last().unwrap_or((&0, &[]));
match String::from_utf16(ret) {
Err(e) => Err(Error::new(
Status::InvalidArg,
format!("Failed to read utf16 string, {}", e),
)),
Ok(s) => Ok(Utf16String(s)),
}
}
}
impl ToNapiValue for Utf16String {
unsafe fn to_napi_value(env: sys::napi_env, val: Utf16String) -> Result<sys::napi_value> {
let mut ptr = ptr::null_mut();
let encoded = val.0.encode_utf16().collect::<Vec<_>>();
check_status!(
sys::napi_create_string_utf16(env, encoded.as_ptr() as *const _, encoded.len(), &mut ptr),
"Failed to convert napi `string` into rust type `String`"
)?;
Ok(ptr)
}
}
#[cfg(feature = "latin1")]
pub mod latin1_string {
use super::*;
#[derive(Debug)]
pub struct Latin1String(String);
impl From<String> for Latin1String {
fn from(s: String) -> Self {
Latin1String(s)
}
}
impl Display for Latin1String {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Deref for Latin1String {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl TypeName for Latin1String {
fn type_name() -> &'static str {
"String(latin1)"
}
}
#[cfg(feature = "latin1")]
impl FromNapiValue for Latin1String {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut len = 0;
check_status!(
sys::napi_get_value_string_latin1(env, napi_val, ptr::null_mut(), 0, &mut len),
"Failed to convert napi `latin1 string` into rust type `String`",
)?;
// end char len in C
len += 1;
let mut buf = Vec::with_capacity(len);
let buf_ptr = buf.as_mut_ptr();
let mut written_char_count = 0;
mem::forget(buf);
check_status!(
sys::napi_get_value_string_latin1(env, napi_val, buf_ptr, len, &mut written_char_count),
"Failed to convert napi `latin1 string` into rust type `String`"
)?;
let buf = Vec::from_raw_parts(buf_ptr as *mut _, written_char_count, written_char_count);
let mut dst_slice = vec![0; buf.len() * 2];
let written =
encoding_rs::mem::convert_latin1_to_utf8(buf.as_slice(), dst_slice.as_mut_slice());
dst_slice.truncate(written);
Ok(Latin1String(String::from_utf8_unchecked(dst_slice)))
}
}
impl ToNapiValue for Latin1String {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
let mut ptr = ptr::null_mut();
let mut dst = vec![0; val.len()];
encoding_rs::mem::convert_utf8_to_latin1_lossy(val.0.as_bytes(), dst.as_mut_slice());
check_status!(
sys::napi_create_string_latin1(env, dst.as_ptr() as *const _, dst.len(), &mut ptr),
"Failed to convert rust type `String` into napi `latin1 string`"
)?;
Ok(ptr)
}
}
}

View file

@ -0,0 +1,50 @@
mod callback_info;
mod env;
mod error;
mod js_values;
mod module_register;
pub use callback_info::*;
pub use ctor::ctor;
pub use env::*;
pub use js_values::*;
pub use module_register::*;
use super::sys;
use std::{ffi::c_void, mem};
/// # Safety
///
/// called when node wrapper objects destroyed
pub unsafe extern "C" fn raw_finalize_unchecked<T>(
env: sys::napi_env,
finalize_data: *mut c_void,
finalize_hint: *mut c_void,
) {
let obj = finalize_data as *mut T;
Box::from_raw(obj);
if !finalize_hint.is_null() {
let size_hint = *Box::from_raw(finalize_hint as *mut Option<i64>);
if let Some(changed) = size_hint {
let mut adjusted = 0i64;
let status = sys::napi_adjust_external_memory(env, -changed, &mut adjusted);
debug_assert!(
status == sys::Status::napi_ok,
"Calling napi_adjust_external_memory failed"
);
};
}
}
/// # Safety
///
/// called when node buffer is ready for gc
pub unsafe extern "C" fn drop_buffer(
_env: sys::napi_env,
finalize_data: *mut c_void,
finalize_hint: *mut c_void,
) {
let length_ptr = finalize_hint as *mut (usize, usize);
let (length, cap) = *Box::from_raw(length_ptr);
mem::drop(Vec::from_raw_parts(finalize_data as *mut u8, length, cap));
}

View file

@ -0,0 +1,141 @@
use std::{cell::RefCell, collections::HashMap, ffi::CString, ptr};
use crate::{check_status, check_status_or_throw, sys, JsError, Property, Result};
pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>;
pub type ModuleExportsCallback =
unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
thread_local! {
static MODULE_REGISTER_CALLBACK: RefCell<Vec<(&'static str, ExportRegisterCallback)>> = Default::default();
static MODULE_CLASS_PROPERTIES: RefCell<HashMap<&'static str, (&'static str, Vec<Property>)>> = Default::default();
static REGISTERED_CLASSES: RefCell<HashMap<
/* export name */ &'static str,
/* constructor */ sys::napi_ref,
>> = Default::default();
// compatibility for #[module_exports]
#[cfg(feature = "compat-mode")]
static MODULE_EXPORTS: std::cell::Cell<Vec<ModuleExportsCallback>> = Default::default();
}
pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
REGISTERED_CLASSES.with(|registered_classes| {
let classes = registered_classes.borrow();
classes.get(js_name).copied()
})
}
#[cfg(feature = "compat-mode")]
// compatibility for #[module_exports]
pub fn register_module_exports(callback: ModuleExportsCallback) {
MODULE_EXPORTS.with(|cell| cell.set(vec![callback]));
}
pub fn register_module_export(name: &'static str, cb: ExportRegisterCallback) {
MODULE_REGISTER_CALLBACK.with(|exports| {
let mut list = exports.borrow_mut();
list.push((name, cb));
});
}
pub fn register_class(rust_name: &'static str, js_name: &'static str, props: Vec<Property>) {
MODULE_CLASS_PROPERTIES.with(|map| {
let mut map = map.borrow_mut();
let val = map.entry(rust_name).or_default();
val.0 = js_name;
val.1.extend(props.into_iter());
});
}
#[no_mangle]
unsafe extern "C" fn napi_register_module_v1(
env: sys::napi_env,
exports: sys::napi_value,
) -> sys::napi_value {
MODULE_REGISTER_CALLBACK.with(|to_register_exports| {
to_register_exports
.take()
.into_iter()
.for_each(|(name, callback)| {
let js_name = CString::new(name).unwrap();
unsafe {
if let Err(e) = callback(env).and_then(|v| {
check_status!(
sys::napi_set_named_property(env, exports, js_name.as_ptr(), v),
"Failed to register export `{}`",
name,
)
}) {
JsError::from(e).throw_into(env)
}
}
})
});
MODULE_CLASS_PROPERTIES.with(|to_register_classes| {
for (rust_name, (js_name, props)) in to_register_classes.take().into_iter() {
unsafe {
let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor);
// one or more?
let ctor = ctor[0].raw().method.unwrap();
let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
let js_class_name = CString::new(js_name).unwrap();
let mut class_ptr = ptr::null_mut();
check_status_or_throw!(
env,
sys::napi_define_class(
env,
js_class_name.as_ptr(),
js_name.len(),
Some(ctor),
ptr::null_mut(),
raw_props.len(),
raw_props.as_ptr(),
&mut class_ptr,
),
"Failed to register class `{}` generate by struct `{}`",
&js_name,
&rust_name
);
let mut ctor_ref = ptr::null_mut();
sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
REGISTERED_CLASSES.with(|registered_classes| {
let mut registered_class = registered_classes.borrow_mut();
registered_class.insert(js_name, ctor_ref);
});
check_status_or_throw!(
env,
sys::napi_set_named_property(env, exports, js_class_name.as_ptr(), class_ptr),
"Failed to register class `{}` generate by struct `{}`",
&js_name,
&rust_name
);
}
}
});
#[cfg(feature = "compat-mode")]
MODULE_EXPORTS.with(|callbacks| {
for callback in callbacks.take().into_iter() {
if let Err(e) = callback(env, exports) {
JsError::from(e).throw_into(env);
}
}
});
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
if let Err(e) = check_status!(
sys::napi_add_env_cleanup_hook(env, Some(crate::shutdown_tokio_rt), ptr::null_mut()),
"Failed to initialize module",
) {
JsError::from(e).throw_into(env);
}
exports
}

View file

@ -0,0 +1,97 @@
use std::ptr;
use crate::check_status;
use crate::{sys, Either, Env, Error, JsUndefined, NapiValue, Result, Status};
/// Function call context
pub struct CallContext<'env> {
pub env: &'env mut Env,
raw_this: sys::napi_value,
callback_info: sys::napi_callback_info,
args: &'env [sys::napi_value],
/// arguments.length
pub length: usize,
}
impl<'env> CallContext<'env> {
/// The number of N-api obtained values. In practice this is the numeric
/// parameter provided to the `#[js_function(arg_len)]` macro.
///
/// As a comparison, the (arguments) `.length` represents the actual number
/// of arguments given at a specific function call.
///
/// If `.length < .arg_len`, then the elements in the `length .. arg_len`
/// range are just `JsUndefined`s.
///
/// If `.length > .arg_len`, then truncation has happened and some args have
/// been lost.
fn arg_len(&self) -> usize {
self.args.len()
}
pub fn new(
env: &'env mut Env,
callback_info: sys::napi_callback_info,
raw_this: sys::napi_value,
args: &'env [sys::napi_value],
length: usize,
) -> Self {
Self {
env,
raw_this,
callback_info,
args,
length,
}
}
pub fn get<ArgType: NapiValue>(&self, index: usize) -> Result<ArgType> {
if index >= self.arg_len() {
Err(Error {
status: Status::GenericFailure,
reason: "Arguments index out of range".to_owned(),
})
} else {
Ok(unsafe { ArgType::from_raw_unchecked(self.env.0, self.args[index]) })
}
}
pub fn try_get<ArgType: NapiValue>(&self, index: usize) -> Result<Either<ArgType, JsUndefined>> {
if index >= self.arg_len() {
Err(Error {
status: Status::GenericFailure,
reason: "Arguments index out of range".to_owned(),
})
} else if index < self.length {
unsafe { ArgType::from_raw(self.env.0, self.args[index]) }.map(Either::A)
} else {
self.env.get_undefined().map(Either::B)
}
}
pub fn get_all(&self) -> Vec<crate::JsUnknown> {
/* (0 .. self.arg_len()).map(|i| self.get(i).unwrap()).collect() */
self
.args
.iter()
.map(|&raw| unsafe { crate::JsUnknown::from_raw_unchecked(self.env.0, raw) })
.collect()
}
pub fn get_new_target<V>(&self) -> Result<V>
where
V: NapiValue,
{
let mut value = ptr::null_mut();
check_status!(unsafe { sys::napi_get_new_target(self.env.0, self.callback_info, &mut value) })?;
unsafe { V::from_raw(self.env.0, value) }
}
pub fn this<T: NapiValue>(&self) -> Result<T> {
unsafe { T::from_raw(self.env.0, self.raw_this) }
}
pub fn this_unchecked<T: NapiValue>(&self) -> T {
unsafe { T::from_raw_unchecked(self.env.0, self.raw_this) }
}
}

View file

@ -0,0 +1,9 @@
pub(crate) struct CleanupEnvHookData<T: 'static> {
pub(crate) data: T,
pub(crate) hook: Box<dyn FnOnce(T)>,
}
/// Created by `Env::add_env_cleanup_hook`
/// And used by `Env::remove_env_cleanup_hook`
#[derive(Clone, Copy)]
pub struct CleanupEnvHook<T: 'static>(pub(crate) *mut CleanupEnvHookData<T>);

1372
crates/napi/src/env.rs Normal file

File diff suppressed because it is too large Load diff

227
crates/napi/src/error.rs Normal file
View file

@ -0,0 +1,227 @@
use std::convert::{From, TryFrom};
use std::error;
use std::ffi::CString;
use std::fmt;
#[cfg(feature = "serde-json")]
use std::fmt::Display;
use std::os::raw::{c_char, c_void};
use std::ptr;
#[cfg(feature = "serde-json")]
use serde::{de, ser};
#[cfg(feature = "serde-json")]
use serde_json::Error as SerdeJSONError;
use crate::{check_status, sys, Status};
pub type Result<T> = std::result::Result<T, Error>;
/// Represent `JsError`.
/// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you.
/// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)`
#[derive(Debug, Clone)]
pub struct Error {
pub status: Status,
pub reason: String,
}
impl error::Error for Error {}
#[cfg(feature = "serde-json")]
impl ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::new(Status::InvalidArg, msg.to_string())
}
}
#[cfg(feature = "serde-json")]
impl de::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::new(Status::InvalidArg, msg.to_string())
}
}
#[cfg(feature = "serde-json")]
impl From<SerdeJSONError> for Error {
fn from(value: SerdeJSONError) -> Self {
Error::new(Status::InvalidArg, format!("{}", value))
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.reason.is_empty() {
write!(f, "{:?}, {}", self.status, self.reason)
} else {
write!(f, "{:?}", self.status)
}
}
}
impl Error {
pub fn new(status: Status, reason: String) -> Self {
Error { status, reason }
}
pub fn from_status(status: Status) -> Self {
Error {
status,
reason: "".to_owned(),
}
}
pub fn from_reason(reason: String) -> Self {
Error {
status: Status::GenericFailure,
reason,
}
}
}
impl From<std::ffi::NulError> for Error {
fn from(error: std::ffi::NulError) -> Self {
Error {
status: Status::GenericFailure,
reason: format!("{}", error),
}
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error {
status: Status::GenericFailure,
reason: format!("{}", error),
}
}
}
#[derive(Clone, Debug)]
pub struct ExtendedErrorInfo {
pub message: String,
pub engine_reserved: *mut c_void,
pub engine_error_code: u32,
pub error_code: Status,
}
impl TryFrom<sys::napi_extended_error_info> for ExtendedErrorInfo {
type Error = Error;
fn try_from(value: sys::napi_extended_error_info) -> Result<Self> {
Ok(Self {
message: unsafe {
CString::from_raw(value.error_message as *mut c_char)
.into_string()
.map_err(|e| Error::new(Status::GenericFailure, format!("{}", e)))?
},
engine_error_code: value.engine_error_code,
engine_reserved: value.engine_reserved,
error_code: Status::from(value.error_code),
})
}
}
pub struct JsError(Error);
pub struct JsTypeError(Error);
pub struct JsRangeError(Error);
macro_rules! impl_object_methods {
($js_value:ident, $kind:expr) => {
impl $js_value {
/// # Safety
///
/// This function is safety if env is not null ptr.
pub unsafe fn into_value(self, env: sys::napi_env) -> sys::napi_value {
let error_status = format!("{:?}", self.0.status);
let status_len = error_status.len();
let error_code_string = CString::new(error_status).unwrap();
let reason_len = self.0.reason.len();
let reason = CString::new(self.0.reason).unwrap();
let mut error_code = ptr::null_mut();
let mut reason_string = ptr::null_mut();
let mut js_error = ptr::null_mut();
let create_code_status = sys::napi_create_string_utf8(
env,
error_code_string.as_ptr(),
status_len,
&mut error_code,
);
debug_assert!(create_code_status == sys::Status::napi_ok);
let create_reason_status =
sys::napi_create_string_utf8(env, reason.as_ptr(), reason_len, &mut reason_string);
debug_assert!(create_reason_status == sys::Status::napi_ok);
let create_error_status = $kind(env, error_code, reason_string, &mut js_error);
debug_assert!(create_error_status == sys::Status::napi_ok);
js_error
}
/// # Safety
///
/// This function is safety if env is not null ptr.
pub unsafe fn throw_into(self, env: sys::napi_env) {
let js_error = self.into_value(env);
let throw_status = sys::napi_throw(env, js_error);
debug_assert!(throw_status == sys::Status::napi_ok);
}
pub fn throw(&self, env: sys::napi_env) -> Result<()> {
let error_status = format!("{:?}", self.0.status);
let status_len = error_status.len();
let error_code_string = CString::new(error_status).unwrap();
let reason_len = self.0.reason.len();
let reason = CString::new(self.0.reason.clone()).unwrap();
let mut error_code = ptr::null_mut();
let mut reason_string = ptr::null_mut();
let mut js_error = ptr::null_mut();
check_status!(unsafe {
sys::napi_create_string_utf8(env, error_code_string.as_ptr(), status_len, &mut error_code)
})?;
check_status!(unsafe {
sys::napi_create_string_utf8(env, reason.as_ptr(), reason_len, &mut reason_string)
})?;
check_status!(unsafe { $kind(env, error_code, reason_string, &mut js_error) })?;
check_status!(unsafe { sys::napi_throw(env, js_error) })
}
}
impl From<Error> for $js_value {
fn from(err: Error) -> Self {
Self(err)
}
}
};
}
impl_object_methods!(JsError, sys::napi_create_error);
impl_object_methods!(JsTypeError, sys::napi_create_type_error);
impl_object_methods!(JsRangeError, sys::napi_create_range_error);
#[doc(hidden)]
#[macro_export]
macro_rules! error {
($status:expr, $($msg:tt)*) => {
$crate::Error::new($status, format!($($msg)*))
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! check_status {
($code:expr) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
}
}};
($code:expr, $($msg:tt)*) => {{
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
_ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
}
}};
}

View file

@ -0,0 +1,285 @@
use std::ops::{Deref, DerefMut};
use std::os::raw::c_void;
use std::ptr;
use std::slice;
use crate::{check_status, sys, JsUnknown, NapiValue, Ref, Result, Value, ValueType};
pub struct JsArrayBuffer(pub(crate) Value);
pub struct JsArrayBufferValue {
pub(crate) value: JsArrayBuffer,
len: usize,
data: *mut c_void,
}
pub struct JsTypedArray(pub(crate) Value);
pub struct JsTypedArrayValue {
pub arraybuffer: JsArrayBuffer,
data: *mut c_void,
pub byte_offset: u64,
pub length: u64,
pub typedarray_type: TypedArrayType,
}
pub struct JsDataView(pub(crate) Value);
pub struct JsDataViewValue {
pub arraybuffer: JsArrayBuffer,
_data: *mut c_void,
pub byte_offset: u64,
pub length: u64,
}
#[repr(i32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TypedArrayType {
Int8 = 0,
Uint8,
Uint8Clamped,
Int16,
Uint16,
Int32,
Uint32,
Float32,
Float64,
#[cfg(feature = "napi6")]
BigInt64,
#[cfg(feature = "napi6")]
BigUint64,
/// compatible with higher versions
Unknown = 1024,
}
impl From<sys::napi_typedarray_type> for TypedArrayType {
fn from(value: sys::napi_typedarray_type) -> Self {
match value {
sys::TypedarrayType::napi_int8_array => Self::Int8,
sys::TypedarrayType::napi_uint8_array => Self::Uint8,
sys::TypedarrayType::napi_uint8_clamped_array => Self::Uint8Clamped,
sys::TypedarrayType::napi_int16_array => Self::Int16,
sys::TypedarrayType::napi_uint16_array => Self::Uint16,
sys::TypedarrayType::napi_int32_array => Self::Int32,
sys::TypedarrayType::napi_uint32_array => Self::Uint32,
sys::TypedarrayType::napi_float32_array => Self::Float32,
sys::TypedarrayType::napi_float64_array => Self::Float64,
#[cfg(feature = "napi6")]
sys::TypedarrayType::napi_bigint64_array => Self::BigInt64,
#[cfg(feature = "napi6")]
sys::TypedarrayType::napi_biguint64_array => Self::BigUint64,
_ => Self::Unknown,
}
}
}
impl From<TypedArrayType> for sys::napi_typedarray_type {
fn from(value: TypedArrayType) -> sys::napi_typedarray_type {
value as i32
}
}
impl JsArrayBuffer {
#[cfg(feature = "napi7")]
pub fn detach(self) -> Result<()> {
check_status!(unsafe { sys::napi_detach_arraybuffer(self.0.env, self.0.value) })
}
#[cfg(feature = "napi7")]
pub fn is_detached(&self) -> Result<bool> {
let mut is_detached = false;
check_status!(unsafe {
sys::napi_is_detached_arraybuffer(self.0.env, self.0.value, &mut is_detached)
})?;
Ok(is_detached)
}
pub fn into_value(self) -> Result<JsArrayBufferValue> {
let mut data = ptr::null_mut();
let mut len: usize = 0;
check_status!(unsafe {
sys::napi_get_arraybuffer_info(self.0.env, self.0.value, &mut data, &mut len as *mut usize)
})?;
Ok(JsArrayBufferValue {
data,
value: self,
len,
})
}
pub fn into_typedarray(
self,
typedarray_type: TypedArrayType,
length: usize,
byte_offset: usize,
) -> Result<JsTypedArray> {
let mut typedarray_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_create_typedarray(
self.0.env,
typedarray_type.into(),
length,
self.0.value,
byte_offset,
&mut typedarray_value,
)
})?;
Ok(JsTypedArray(Value {
env: self.0.env,
value: typedarray_value,
value_type: ValueType::Object,
}))
}
pub fn into_dataview(self, length: usize, byte_offset: usize) -> Result<JsDataView> {
let mut dataview_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_create_dataview(
self.0.env,
length,
self.0.value,
byte_offset,
&mut dataview_value,
)
})?;
Ok(JsDataView(Value {
env: self.0.env,
value: dataview_value,
value_type: ValueType::Object,
}))
}
pub fn into_ref(self) -> Result<Ref<JsArrayBufferValue>> {
Ref::new(self.0, 1, self.into_value()?)
}
}
impl JsArrayBufferValue {
pub fn new(value: JsArrayBuffer, data: *mut c_void, len: usize) -> Self {
JsArrayBufferValue { value, len, data }
}
pub fn into_raw(self) -> JsArrayBuffer {
self.value
}
pub fn into_unknown(self) -> JsUnknown {
unsafe { JsUnknown::from_raw_unchecked(self.value.0.env, self.value.0.value) }
}
}
impl AsRef<[u8]> for JsArrayBufferValue {
fn as_ref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.data as *const u8, self.len) }
}
}
impl AsMut<[u8]> for JsArrayBufferValue {
fn as_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.data as *mut u8, self.len) }
}
}
impl Deref for JsArrayBufferValue {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.as_ref()
}
}
impl DerefMut for JsArrayBufferValue {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}
impl JsTypedArray {
/// get TypeArray info
/// https://nodejs.org/api/n-api.html#n_api_napi_get_typedarray_info
///
/// ***Warning***: Use caution while using this API since the underlying data buffer is managed by the VM.
pub fn into_value(self) -> Result<JsTypedArrayValue> {
let mut typedarray_type = 0;
let mut len = 0u64;
let mut data = ptr::null_mut();
let mut arraybuffer_value = ptr::null_mut();
let mut byte_offset = 0u64;
check_status!(unsafe {
sys::napi_get_typedarray_info(
self.0.env,
self.0.value,
&mut typedarray_type,
&mut len as *mut u64 as *mut _,
&mut data,
&mut arraybuffer_value,
&mut byte_offset as *mut u64 as *mut usize,
)
})?;
Ok(JsTypedArrayValue {
data,
length: len,
byte_offset,
typedarray_type: typedarray_type.into(),
arraybuffer: unsafe { JsArrayBuffer::from_raw_unchecked(self.0.env, arraybuffer_value) },
})
}
}
macro_rules! impl_as_ref {
($ref_type:ident) => {
impl AsRef<[$ref_type]> for JsTypedArrayValue {
fn as_ref(&self) -> &[$ref_type] {
unsafe { slice::from_raw_parts(self.data as *const $ref_type, self.length as usize) }
}
}
impl AsMut<[$ref_type]> for JsTypedArrayValue {
fn as_mut(&mut self) -> &mut [$ref_type] {
unsafe { slice::from_raw_parts_mut(self.data as *mut $ref_type, self.length as usize) }
}
}
};
}
impl_as_ref!(u8);
impl_as_ref!(i8);
impl_as_ref!(u16);
impl_as_ref!(i16);
impl_as_ref!(u32);
impl_as_ref!(i32);
impl_as_ref!(f32);
impl_as_ref!(f64);
#[cfg(feature = "napi6")]
impl_as_ref!(i64);
#[cfg(feature = "napi6")]
impl_as_ref!(u64);
impl JsDataView {
pub fn into_value(self) -> Result<JsDataViewValue> {
let mut length = 0u64;
let mut byte_offset = 0u64;
let mut arraybuffer_value = ptr::null_mut();
let mut data = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_dataview_info(
self.0.env,
self.0.value,
&mut length as *mut u64 as *mut _,
&mut data,
&mut arraybuffer_value,
&mut byte_offset as *mut u64 as *mut _,
)
})?;
Ok(JsDataViewValue {
arraybuffer: unsafe { JsArrayBuffer::from_raw_unchecked(self.0.env, arraybuffer_value) },
byte_offset,
length,
_data: data,
})
}
}

View file

@ -0,0 +1,249 @@
use std::convert::TryFrom;
use std::ptr;
use super::*;
use crate::{check_status, sys, Result};
#[derive(Clone, Copy)]
pub struct JsBigint {
pub(crate) raw: Value,
pub word_count: usize,
}
impl JsBigint {
pub(crate) fn from_raw_unchecked(
env: sys::napi_env,
value: sys::napi_value,
word_count: usize,
) -> Self {
Self {
raw: Value {
env,
value,
value_type: ValueType::Object,
},
word_count,
}
}
pub fn into_unknown(self) -> Result<JsUnknown> {
unsafe { JsUnknown::from_raw(self.raw.env, self.raw.value) }
}
pub fn coerce_to_number(self) -> Result<JsNumber> {
let mut new_raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_coerce_to_number(self.raw.env, self.raw.value, &mut new_raw_value)
})?;
Ok(JsNumber(Value {
env: self.raw.env,
value: new_raw_value,
value_type: ValueType::Number,
}))
}
pub fn coerce_to_string(self) -> Result<JsString> {
let mut new_raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_coerce_to_string(self.raw.env, self.raw.value, &mut new_raw_value)
})?;
Ok(JsString(Value {
env: self.raw.env,
value: new_raw_value,
value_type: ValueType::String,
}))
}
pub fn coerce_to_object(self) -> Result<JsObject> {
let mut new_raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_coerce_to_object(self.raw.env, self.raw.value, &mut new_raw_value)
})?;
Ok(JsObject(Value {
env: self.raw.env,
value: new_raw_value,
value_type: ValueType::Object,
}))
}
#[cfg(feature = "napi5")]
pub fn is_date(&self) -> Result<bool> {
let mut is_date = true;
check_status!(unsafe { sys::napi_is_date(self.raw.env, self.raw.value, &mut is_date) })?;
Ok(is_date)
}
pub fn is_error(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_is_error(self.raw.env, self.raw.value, &mut result) })?;
Ok(result)
}
pub fn is_typedarray(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_is_typedarray(self.raw.env, self.raw.value, &mut result) })?;
Ok(result)
}
pub fn is_dataview(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_is_dataview(self.raw.env, self.raw.value, &mut result) })?;
Ok(result)
}
pub fn is_array(&self) -> Result<bool> {
let mut is_array = false;
check_status!(unsafe { sys::napi_is_array(self.raw.env, self.raw.value, &mut is_array) })?;
Ok(is_array)
}
pub fn is_buffer(&self) -> Result<bool> {
let mut is_buffer = false;
check_status!(unsafe { sys::napi_is_buffer(self.raw.env, self.raw.value, &mut is_buffer) })?;
Ok(is_buffer)
}
pub fn instanceof<Constructor: NapiRaw>(&self, constructor: Constructor) -> Result<bool> {
let mut result = false;
check_status!(unsafe {
sys::napi_instanceof(self.raw.env, self.raw.value, constructor.raw(), &mut result)
})?;
Ok(result)
}
}
impl NapiRaw for JsBigint {
unsafe fn raw(&self) -> sys::napi_value {
self.raw.value
}
}
impl<'env> NapiRaw for &'env JsBigint {
unsafe fn raw(&self) -> sys::napi_value {
self.raw.value
}
}
impl NapiValue for JsBigint {
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut word_count = 0usize;
check_status!(sys::napi_get_value_bigint_words(
env,
value,
ptr::null_mut(),
&mut word_count,
ptr::null_mut(),
))?;
Ok(JsBigint {
raw: Value {
env,
value,
value_type: ValueType::Bigint,
},
word_count,
})
}
unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
let mut word_count = 0usize;
let status = sys::napi_get_value_bigint_words(
env,
value,
ptr::null_mut(),
&mut word_count,
ptr::null_mut(),
);
debug_assert!(
Status::from(status) == Status::Ok,
"napi_get_value_bigint_words failed"
);
JsBigint {
raw: Value {
env,
value,
value_type: ValueType::Bigint,
},
word_count,
}
}
}
/// The BigInt will be converted losslessly when the value is over what an int64 could hold.
impl TryFrom<JsBigint> for i64 {
type Error = Error;
fn try_from(value: JsBigint) -> Result<i64> {
value.get_i64().map(|(v, _)| v)
}
}
/// The BigInt will be converted losslessly when the value is over what an uint64 could hold.
impl TryFrom<JsBigint> for u64 {
type Error = Error;
fn try_from(value: JsBigint) -> Result<u64> {
value.get_u64().map(|(v, _)| v)
}
}
impl JsBigint {
/// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words
pub fn get_words(&mut self) -> Result<(bool, Vec<u64>)> {
let mut words: Vec<u64> = Vec::with_capacity(self.word_count as usize);
let word_count = &mut self.word_count;
let mut sign_bit = 0;
check_status!(unsafe {
sys::napi_get_value_bigint_words(
self.raw.env,
self.raw.value,
&mut sign_bit,
word_count,
words.as_mut_ptr(),
)
})?;
unsafe {
words.set_len(self.word_count as usize);
};
Ok((sign_bit == 1, words))
}
pub fn get_u64(&self) -> Result<(u64, bool)> {
let mut val: u64 = 0;
let mut loss = false;
check_status!(unsafe {
sys::napi_get_value_bigint_uint64(self.raw.env, self.raw.value, &mut val, &mut loss)
})?;
Ok((val, loss))
}
pub fn get_i64(&self) -> Result<(i64, bool)> {
let mut val: i64 = 0;
let mut loss: bool = false;
check_status!(unsafe {
sys::napi_get_value_bigint_int64(self.raw.env, self.raw.value, &mut val, &mut loss)
})?;
Ok((val, loss))
}
pub fn get_i128(&mut self) -> Result<(i128, bool)> {
let (signed, words) = self.get_words()?;
let len = words.len();
let i128_words: [i64; 2] = [words[0] as _, words[1] as _];
let mut val = unsafe { ptr::read(i128_words.as_ptr() as *const i128) };
if signed {
val = -val;
}
Ok((val, len > 2))
}
pub fn get_u128(&mut self) -> Result<(bool, u128, bool)> {
let (signed, words) = self.get_words()?;
let len = words.len();
let u128_words: [u64; 2] = [words[0], words[1]];
let val = unsafe { ptr::read(u128_words.as_ptr() as *const u128) };
Ok((signed, val, len > 2))
}
}

View file

@ -0,0 +1,24 @@
use std::convert::TryFrom;
use super::Value;
use crate::check_status;
use crate::{sys, Error, Result};
#[derive(Clone, Copy)]
pub struct JsBoolean(pub(crate) Value);
impl JsBoolean {
pub fn get_value(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_get_value_bool(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
}
impl TryFrom<JsBoolean> for bool {
type Error = Error;
fn try_from(value: JsBoolean) -> Result<bool> {
value.get_value()
}
}

View file

@ -0,0 +1,91 @@
use std::mem;
use std::ops::{Deref, DerefMut};
use std::ptr;
use super::Value;
#[cfg(feature = "serde-json")]
use super::ValueType;
use crate::check_status;
use crate::{sys, JsUnknown, NapiValue, Ref, Result};
pub struct JsBuffer(pub(crate) Value);
pub struct JsBufferValue {
pub(crate) value: JsBuffer,
data: mem::ManuallyDrop<Vec<u8>>,
}
impl JsBuffer {
pub fn into_value(self) -> Result<JsBufferValue> {
let mut data = ptr::null_mut();
let mut len: usize = 0;
check_status!(unsafe {
sys::napi_get_buffer_info(self.0.env, self.0.value, &mut data, &mut len)
})?;
Ok(JsBufferValue {
data: mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(data as *mut _, len, len) }),
value: self,
})
}
pub fn into_ref(self) -> Result<Ref<JsBufferValue>> {
Ref::new(self.0, 1, self.into_value()?)
}
}
impl JsBufferValue {
#[cfg(feature = "serde-json")]
pub(crate) fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut data = ptr::null_mut();
let mut len = 0usize;
check_status!(unsafe {
sys::napi_get_buffer_info(env, value, &mut data, &mut len as *mut usize as *mut _)
})?;
Ok(Self {
value: JsBuffer(Value {
env,
value,
value_type: ValueType::Object,
}),
data: mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(data as *mut _, len, len) }),
})
}
pub fn new(value: JsBuffer, data: mem::ManuallyDrop<Vec<u8>>) -> Self {
JsBufferValue { value, data }
}
pub fn into_raw(self) -> JsBuffer {
self.value
}
pub fn into_unknown(self) -> JsUnknown {
unsafe { JsUnknown::from_raw_unchecked(self.value.0.env, self.value.0.value) }
}
}
impl AsRef<[u8]> for JsBufferValue {
fn as_ref(&self) -> &[u8] {
self.data.as_slice()
}
}
impl AsMut<[u8]> for JsBufferValue {
fn as_mut(&mut self) -> &mut [u8] {
self.data.as_mut_slice()
}
}
impl Deref for JsBufferValue {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.data.as_slice()
}
}
impl DerefMut for JsBufferValue {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data.as_mut_slice()
}
}

View file

@ -0,0 +1,12 @@
use super::check_status;
use crate::{sys, Result, Value};
pub struct JsDate(pub(crate) Value);
impl JsDate {
pub fn value_of(&self) -> Result<f64> {
let mut timestamp: f64 = 0.0;
check_status!(unsafe { sys::napi_get_date_value(self.0.env, self.0.value, &mut timestamp) })?;
Ok(timestamp)
}
}

View file

@ -0,0 +1,364 @@
use std::convert::TryInto;
use serde::de::Visitor;
use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, VariantAccess};
#[cfg(feature = "napi6")]
use crate::JsBigint;
use crate::{type_of, NapiValue, Value, ValueType};
use crate::{
Error, JsBoolean, JsBufferValue, JsNumber, JsObject, JsString, JsUnknown, Result, Status,
};
pub(crate) struct De<'env>(pub(crate) &'env Value);
#[doc(hidden)]
impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
let js_value_type = unsafe { type_of!(self.0.env, self.0.value) }?;
match js_value_type {
ValueType::Null | ValueType::Undefined => visitor.visit_unit(),
ValueType::Boolean => {
let js_boolean = unsafe { JsBoolean::from_raw_unchecked(self.0.env, self.0.value) };
visitor.visit_bool(js_boolean.get_value()?)
}
ValueType::Number => {
let js_number: f64 =
unsafe { JsNumber::from_raw_unchecked(self.0.env, self.0.value).try_into()? };
if (js_number.trunc() - js_number).abs() < f64::EPSILON {
visitor.visit_i64(js_number as i64)
} else {
visitor.visit_f64(js_number)
}
}
ValueType::String => {
let js_string = unsafe { JsString::from_raw_unchecked(self.0.env, self.0.value) };
visitor.visit_str(js_string.into_utf8()?.as_str()?)
}
ValueType::Object => {
let js_object = unsafe { JsObject::from_raw_unchecked(self.0.env, self.0.value) };
if js_object.is_array()? {
let mut deserializer =
JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
visitor.visit_seq(&mut deserializer)
} else if js_object.is_buffer()? {
visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
} else {
let mut deserializer = JsObjectAccess::new(&js_object)?;
visitor.visit_map(&mut deserializer)
}
}
#[cfg(feature = "napi6")]
ValueType::Bigint => {
let mut js_bigint = unsafe { JsBigint::from_raw(self.0.env, self.0.value)? };
let (signed, v, _loss) = js_bigint.get_u128()?;
if signed {
visitor.visit_i128(-(v as i128))
} else {
visitor.visit_u128(v)
}
}
ValueType::External | ValueType::Function | ValueType::Symbol => Err(Error::new(
Status::InvalidArg,
format!("typeof {:?} value could not be deserialized", js_value_type),
)),
ValueType::Unknown => unreachable!(),
}
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
match unsafe { type_of!(self.0.env, self.0.value) }? {
ValueType::Undefined | ValueType::Null => visitor.visit_none(),
_ => visitor.visit_some(self),
}
}
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'x>,
{
let js_value_type = unsafe { type_of!(self.0.env, self.0.value)? };
match js_value_type {
ValueType::String => visitor.visit_enum(JsEnumAccess::new(
unsafe { JsString::from_raw_unchecked(self.0.env, self.0.value) }
.into_utf8()?
.into_owned()?,
None,
)),
ValueType::Object => {
let js_object = unsafe { JsObject::from_raw_unchecked(self.0.env, self.0.value) };
let properties = js_object.get_property_names()?;
let property_len = properties.get_array_length_unchecked()?;
if property_len != 1 {
Err(Error::new(
Status::InvalidArg,
format!(
"object key length: {}, can not deserialize to Enum",
property_len
),
))
} else {
let key = properties.get_element::<JsString>(0)?;
let value: JsUnknown = js_object.get_property(&key)?;
visitor.visit_enum(JsEnumAccess::new(
key.into_utf8()?.into_owned()?,
Some(&value.0),
))
}
}
_ => Err(Error::new(
Status::InvalidArg,
format!(
"{:?} type could not deserialize to Enum type",
js_value_type
),
)),
}
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
visitor.visit_unit()
}
forward_to_deserialize_any! {
<V: Visitor<'x>>
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
unit unit_struct seq tuple tuple_struct map struct identifier
newtype_struct
}
}
#[doc(hidden)]
pub(crate) struct JsEnumAccess<'env> {
variant: String,
value: Option<&'env Value>,
}
#[doc(hidden)]
impl<'env> JsEnumAccess<'env> {
fn new(variant: String, value: Option<&'env Value>) -> Self {
Self { variant, value }
}
}
#[doc(hidden)]
impl<'de, 'env> EnumAccess<'de> for JsEnumAccess<'env> {
type Error = Error;
type Variant = JsVariantAccess<'env>;
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
where
V: DeserializeSeed<'de>,
{
use serde::de::IntoDeserializer;
let variant = self.variant.into_deserializer();
let variant_access = JsVariantAccess { value: self.value };
seed.deserialize(variant).map(|v| (v, variant_access))
}
}
#[doc(hidden)]
pub(crate) struct JsVariantAccess<'env> {
value: Option<&'env Value>,
}
#[doc(hidden)]
impl<'de, 'env> VariantAccess<'de> for JsVariantAccess<'env> {
type Error = Error;
fn unit_variant(self) -> Result<()> {
match self.value {
Some(val) => {
let mut deserializer = De(val);
serde::de::Deserialize::deserialize(&mut deserializer)
}
None => Ok(()),
}
}
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: DeserializeSeed<'de>,
{
match self.value {
Some(val) => {
let mut deserializer = De(val);
seed.deserialize(&mut deserializer)
}
None => Err(serde::de::Error::invalid_type(
Unexpected::UnitVariant,
&"newtype variant",
)),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match self.value {
Some(js_value) => {
let js_object = unsafe { JsObject::from_raw(js_value.env, js_value.value)? };
if js_object.is_array()? {
let mut deserializer =
JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
visitor.visit_seq(&mut deserializer)
} else {
Err(serde::de::Error::invalid_type(
Unexpected::Other("JsValue"),
&"tuple variant",
))
}
}
None => Err(serde::de::Error::invalid_type(
Unexpected::UnitVariant,
&"tuple variant",
)),
}
}
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match self.value {
Some(js_value) => {
if let Ok(val) = unsafe { JsObject::from_raw(js_value.env, js_value.value) } {
let mut deserializer = JsObjectAccess::new(&val)?;
visitor.visit_map(&mut deserializer)
} else {
Err(serde::de::Error::invalid_type(
Unexpected::Other("JsValue"),
&"struct variant",
))
}
}
_ => Err(serde::de::Error::invalid_type(
Unexpected::UnitVariant,
&"struct variant",
)),
}
}
}
#[doc(hidden)]
struct JsArrayAccess<'env> {
input: &'env JsObject,
idx: u32,
len: u32,
}
#[doc(hidden)]
impl<'env> JsArrayAccess<'env> {
fn new(input: &'env JsObject, len: u32) -> Self {
Self { input, idx: 0, len }
}
}
#[doc(hidden)]
impl<'de, 'env> SeqAccess<'de> for JsArrayAccess<'env> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: DeserializeSeed<'de>,
{
if self.idx >= self.len {
return Ok(None);
}
let v = self.input.get_element::<JsUnknown>(self.idx)?;
self.idx += 1;
let mut de = De(&v.0);
seed.deserialize(&mut de).map(Some)
}
}
#[doc(hidden)]
pub(crate) struct JsObjectAccess<'env> {
value: &'env JsObject,
properties: JsObject,
idx: u32,
property_len: u32,
}
#[doc(hidden)]
impl<'env> JsObjectAccess<'env> {
fn new(value: &'env JsObject) -> Result<Self> {
let properties = value.get_property_names()?;
let property_len = properties.get_array_length_unchecked()?;
Ok(Self {
value,
properties,
idx: 0,
property_len,
})
}
}
#[doc(hidden)]
impl<'de, 'env> MapAccess<'de> for JsObjectAccess<'env> {
type Error = Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: DeserializeSeed<'de>,
{
if self.idx >= self.property_len {
return Ok(None);
}
let prop_name = self.properties.get_element::<JsUnknown>(self.idx)?;
let mut de = De(&prop_name.0);
seed.deserialize(&mut de).map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
where
V: DeserializeSeed<'de>,
{
if self.idx >= self.property_len {
return Err(Error::new(
Status::InvalidArg,
format!("Index:{} out of range: {}", self.property_len, self.idx),
));
}
let prop_name = self.properties.get_element::<JsString>(self.idx)?;
let value: JsUnknown = self.value.get_property(&prop_name)?;
self.idx += 1;
let mut de = De(&value.0);
let res = seed.deserialize(&mut de)?;
Ok(res)
}
}

View file

@ -0,0 +1,37 @@
use crate::{sys, JsUndefined, NapiRaw, NapiValue, Result};
#[derive(Debug, Clone, Copy)]
pub enum Either<A: NapiValue, B: NapiValue> {
A(A),
B(B),
}
impl<T: NapiValue> From<Either<T, JsUndefined>> for Option<T> {
fn from(value: Either<T, JsUndefined>) -> Option<T> {
match value {
Either::A(v) => Some(v),
Either::B(_) => None,
}
}
}
impl<A: NapiValue, B: NapiValue> NapiValue for Either<A, B> {
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Either<A, B>> {
A::from_raw(env, value)
.map(Self::A)
.or_else(|_| B::from_raw(env, value).map(Self::B))
}
unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Either<A, B> {
Self::from_raw(env, value).unwrap()
}
}
impl<A: NapiValue, B: NapiValue> NapiRaw for Either<A, B> {
unsafe fn raw(&self) -> sys::napi_value {
match self {
Either::A(v) => v.raw(),
Either::B(v) => v.raw(),
}
}
}

View file

@ -0,0 +1,37 @@
use std::ops::Deref;
use std::ptr;
use crate::check_status;
use crate::{sys, Env, NapiRaw, Result};
pub struct EscapableHandleScope<T: NapiRaw> {
handle_scope: sys::napi_escapable_handle_scope,
value: T,
}
impl<T: NapiRaw> EscapableHandleScope<T> {
pub fn open(env: sys::napi_env, value: T) -> Result<Self> {
let mut handle_scope = ptr::null_mut();
check_status!(unsafe { sys::napi_open_escapable_handle_scope(env, &mut handle_scope) })?;
let mut result = ptr::null_mut();
check_status!(unsafe {
sys::napi_escape_handle(env, handle_scope, NapiRaw::raw(&value), &mut result)
})?;
Ok(Self {
handle_scope,
value,
})
}
pub fn close(self, env: Env) -> Result<()> {
check_status!(unsafe { sys::napi_close_escapable_handle_scope(env.0, self.handle_scope) })
}
}
impl<T: NapiRaw> Deref for EscapableHandleScope<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}

View file

@ -0,0 +1,109 @@
use std::ptr;
use super::Value;
use crate::check_status;
use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status};
pub struct JsFunction(pub(crate) Value);
/// See [Working with JavaScript Functions](https://nodejs.org/api/n-api.html#n_api_working_with_javascript_functions).
///
/// Example:
/// ```
/// use napi::{JsFunction, CallContext, JsNull, Result};
///
/// #[js_function(1)]
/// pub fn call_function(ctx: CallContext) -> Result<JsNull> {
/// let js_func = ctx.get::<JsFunction>(0)?;
/// let js_string = ctx.env.create_string("hello".as_ref())?.into_unknown()?;
/// js_func.call(None, &[js_string])?;
/// Ok(ctx.env.get_null()?)
/// }
/// ```
impl JsFunction {
/// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function)
pub fn call<V>(&self, this: Option<&JsObject>, args: &[V]) -> Result<JsUnknown>
where
V: NapiRaw,
{
let raw_this = this
.map(|v| unsafe { v.raw() })
.or_else(|| {
unsafe { Env::from_raw(self.0.env) }
.get_undefined()
.ok()
.map(|u| unsafe { u.raw() })
})
.ok_or_else(|| Error::new(Status::GenericFailure, "Get raw this failed".to_owned()))?;
let raw_args = args
.iter()
.map(|arg| unsafe { arg.raw() })
.collect::<Vec<sys::napi_value>>();
let mut return_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_call_function(
self.0.env,
raw_this,
self.0.value,
args.len(),
raw_args.as_ptr(),
&mut return_value,
)
})?;
unsafe { JsUnknown::from_raw(self.0.env, return_value) }
}
/// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function)
/// The same with `call`, but without arguments
pub fn call_without_args(&self, this: Option<&JsObject>) -> Result<JsUnknown> {
let raw_this = this
.map(|v| unsafe { v.raw() })
.or_else(|| {
unsafe { Env::from_raw(self.0.env) }
.get_undefined()
.ok()
.map(|u| unsafe { u.raw() })
})
.ok_or_else(|| Error::new(Status::GenericFailure, "Get raw this failed".to_owned()))?;
let mut return_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_call_function(
self.0.env,
raw_this,
self.0.value,
0,
ptr::null_mut(),
&mut return_value,
)
})?;
unsafe { JsUnknown::from_raw(self.0.env, return_value) }
}
/// https://nodejs.org/api/n-api.html#n_api_napi_new_instance
///
/// This method is used to instantiate a new `JavaScript` value using a given `JsFunction` that represents the constructor for the object.
#[allow(clippy::new_ret_no_self)]
pub fn new<V>(&self, args: &[V]) -> Result<JsObject>
where
V: NapiRaw,
{
let mut js_instance = ptr::null_mut();
let length = args.len();
let raw_args = args
.iter()
.map(|arg| unsafe { arg.raw() })
.collect::<Vec<sys::napi_value>>();
check_status!(unsafe {
sys::napi_new_instance(
self.0.env,
self.0.value,
length,
raw_args.as_ptr(),
&mut js_instance,
)
})?;
Ok(unsafe { JsObject::from_raw_unchecked(self.0.env, js_instance) })
}
}

View file

@ -0,0 +1,54 @@
use std::convert::TryInto;
use super::*;
use crate::Env;
pub struct JsGlobal(pub(crate) Value);
pub struct JsTimeout(pub(crate) Value);
impl JsGlobal {
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
let func: JsFunction = self.get_named_property("setInterval")?;
func
.call(
None,
&[
handler.into_unknown(),
unsafe { Env::from_raw(self.0.env) }
.create_double(interval)?
.into_unknown(),
],
)
.and_then(|ret| ret.try_into())
}
pub fn clear_interval(&self, timer: JsTimeout) -> Result<JsUndefined> {
let func: JsFunction = self.get_named_property("clearInterval")?;
func
.call(None, &[timer.into_unknown()])
.and_then(|ret| ret.try_into())
}
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
let func: JsFunction = self.get_named_property("setTimeout")?;
func
.call(
None,
&[
handler.into_unknown(),
unsafe { Env::from_raw(self.0.env) }
.create_double(interval)?
.into_unknown(),
],
)
.and_then(|ret| ret.try_into())
}
pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> {
let func: JsFunction = self.get_named_property("clearTimeout")?;
func
.call(None, &[timer.into_unknown()])
.and_then(|ret| ret.try_into())
}
}

View file

@ -0,0 +1,642 @@
use std::convert::TryFrom;
use std::ffi::CString;
use std::ptr;
use crate::{check_status, sys, type_of, Callback, Error, Result, Status, ValueType};
#[cfg(feature = "serde-json")]
mod de;
#[cfg(feature = "serde-json")]
mod ser;
mod arraybuffer;
#[cfg(feature = "napi6")]
mod bigint;
mod boolean;
mod buffer;
#[cfg(feature = "napi5")]
mod date;
mod either;
mod escapable_handle_scope;
mod function;
mod global;
mod number;
mod object;
mod object_property;
mod string;
mod tagged_object;
mod undefined;
mod value;
mod value_ref;
pub use arraybuffer::*;
#[cfg(feature = "napi6")]
pub use bigint::JsBigint;
pub use boolean::JsBoolean;
pub use buffer::*;
#[cfg(feature = "napi5")]
pub use date::*;
#[cfg(feature = "serde-json")]
pub(crate) use de::De;
pub use either::Either;
pub use escapable_handle_scope::EscapableHandleScope;
pub use function::JsFunction;
pub use global::*;
pub use number::JsNumber;
pub use object::*;
pub use object_property::*;
#[cfg(feature = "serde-json")]
pub(crate) use ser::Ser;
pub use string::*;
pub(crate) use tagged_object::TaggedObject;
pub use undefined::JsUndefined;
pub(crate) use value::Value;
pub use value_ref::*;
// Value types
pub struct JsUnknown(pub(crate) Value);
#[derive(Clone, Copy)]
pub struct JsNull(pub(crate) Value);
#[derive(Clone, Copy)]
pub struct JsSymbol(pub(crate) Value);
pub struct JsExternal(pub(crate) Value);
macro_rules! impl_napi_value_trait {
($js_value:ident, $value_type:ident) => {
impl NapiValue for $js_value {
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<$js_value> {
let value_type = type_of!(env, value)?;
if value_type != $value_type {
Err(Error::new(
Status::InvalidArg,
format!("expect {:?}, got: {:?}", $value_type, value_type),
))
} else {
Ok($js_value(Value {
env,
value,
value_type: $value_type,
}))
}
}
unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> $js_value {
$js_value(Value {
env,
value,
value_type: $value_type,
})
}
}
impl NapiRaw for $js_value {
unsafe fn raw(&self) -> sys::napi_value {
self.0.value
}
}
impl<'env> NapiRaw for &'env $js_value {
unsafe fn raw(&self) -> sys::napi_value {
self.0.value
}
}
impl TryFrom<JsUnknown> for $js_value {
type Error = Error;
fn try_from(value: JsUnknown) -> Result<$js_value> {
unsafe { $js_value::from_raw(value.0.env, value.0.value) }
}
}
};
}
macro_rules! impl_js_value_methods {
($js_value:ident) => {
impl $js_value {
pub fn into_unknown(self) -> JsUnknown {
unsafe { JsUnknown::from_raw_unchecked(self.0.env, self.0.value) }
}
pub fn coerce_to_number(self) -> Result<JsNumber> {
let mut new_raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_coerce_to_number(self.0.env, self.0.value, &mut new_raw_value)
})?;
Ok(JsNumber(Value {
env: self.0.env,
value: new_raw_value,
value_type: ValueType::Number,
}))
}
pub fn coerce_to_string(self) -> Result<JsString> {
let mut new_raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_coerce_to_string(self.0.env, self.0.value, &mut new_raw_value)
})?;
Ok(JsString(Value {
env: self.0.env,
value: new_raw_value,
value_type: ValueType::String,
}))
}
pub fn coerce_to_object(self) -> Result<JsObject> {
let mut new_raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_coerce_to_object(self.0.env, self.0.value, &mut new_raw_value)
})?;
Ok(JsObject(Value {
env: self.0.env,
value: new_raw_value,
value_type: ValueType::Object,
}))
}
#[cfg(feature = "napi5")]
pub fn is_date(&self) -> Result<bool> {
let mut is_date = true;
check_status!(unsafe { sys::napi_is_date(self.0.env, self.0.value, &mut is_date) })?;
Ok(is_date)
}
pub fn is_promise(&self) -> Result<bool> {
let mut is_promise = true;
check_status!(unsafe { sys::napi_is_promise(self.0.env, self.0.value, &mut is_promise) })?;
Ok(is_promise)
}
pub fn is_error(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_is_error(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
pub fn is_typedarray(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_is_typedarray(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
pub fn is_dataview(&self) -> Result<bool> {
let mut result = false;
check_status!(unsafe { sys::napi_is_dataview(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
pub fn is_array(&self) -> Result<bool> {
let mut is_array = false;
check_status!(unsafe { sys::napi_is_array(self.0.env, self.0.value, &mut is_array) })?;
Ok(is_array)
}
pub fn is_buffer(&self) -> Result<bool> {
let mut is_buffer = false;
check_status!(unsafe { sys::napi_is_buffer(self.0.env, self.0.value, &mut is_buffer) })?;
Ok(is_buffer)
}
pub fn instanceof<Constructor>(&self, constructor: Constructor) -> Result<bool>
where
Constructor: NapiRaw,
{
let mut result = false;
check_status!(unsafe {
sys::napi_instanceof(self.0.env, self.0.value, constructor.raw(), &mut result)
})?;
Ok(result)
}
#[cfg(feature = "napi8")]
pub fn freeze(&mut self) -> Result<()> {
check_status!(unsafe { sys::napi_object_freeze(self.0.env, self.0.value) })
}
#[cfg(feature = "napi8")]
pub fn seal(&mut self) -> Result<()> {
check_status!(unsafe { sys::napi_object_seal(self.0.env, self.0.value) })
}
}
};
}
macro_rules! impl_object_methods {
($js_value:ident) => {
impl $js_value {
pub fn set_property<V>(&mut self, key: JsString, value: V) -> Result<()>
where
V: NapiRaw,
{
check_status!(unsafe {
sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw())
})
}
pub fn get_property<K, T>(&self, key: K) -> Result<T>
where
K: NapiRaw,
T: NapiValue,
{
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_property(self.0.env, self.0.value, key.raw(), &mut raw_value)
})?;
unsafe { T::from_raw(self.0.env, raw_value) }
}
pub fn get_property_unchecked<K, T>(&self, key: K) -> Result<T>
where
K: NapiRaw,
T: NapiValue,
{
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_property(self.0.env, self.0.value, key.raw(), &mut raw_value)
})?;
Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) })
}
pub fn set_named_property<T>(&mut self, name: &str, value: T) -> Result<()>
where
T: NapiRaw,
{
let key = CString::new(name)?;
check_status!(unsafe {
sys::napi_set_named_property(self.0.env, self.0.value, key.as_ptr(), value.raw())
})
}
pub fn create_named_method(&mut self, name: &str, function: Callback) -> Result<()> {
let mut js_function = ptr::null_mut();
let len = name.len();
let name = CString::new(name.as_bytes())?;
check_status!(unsafe {
sys::napi_create_function(
self.0.env,
name.as_ptr(),
len,
Some(function),
ptr::null_mut(),
&mut js_function,
)
})?;
check_status!(unsafe {
sys::napi_set_named_property(self.0.env, self.0.value, name.as_ptr(), js_function)
})
}
pub fn get_named_property<T>(&self, name: &str) -> Result<T>
where
T: NapiValue,
{
let key = CString::new(name)?;
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_named_property(self.0.env, self.0.value, key.as_ptr(), &mut raw_value)
})?;
unsafe { T::from_raw(self.0.env, raw_value) }
}
pub fn get_named_property_unchecked<T>(&self, name: &str) -> Result<T>
where
T: NapiValue,
{
let key = CString::new(name)?;
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_named_property(self.0.env, self.0.value, key.as_ptr(), &mut raw_value)
})?;
Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) })
}
pub fn has_named_property(&self, name: &str) -> Result<bool> {
let mut result = false;
let key = CString::new(name)?;
check_status!(unsafe {
sys::napi_has_named_property(self.0.env, self.0.value, key.as_ptr(), &mut result)
})?;
Ok(result)
}
pub fn delete_property<S>(&mut self, name: S) -> Result<bool>
where
S: NapiRaw,
{
let mut result = false;
check_status!(unsafe {
sys::napi_delete_property(self.0.env, self.0.value, name.raw(), &mut result)
})?;
Ok(result)
}
pub fn delete_named_property(&mut self, name: &str) -> Result<bool> {
let mut result = false;
let key_str = CString::new(name)?;
let mut js_key = ptr::null_mut();
check_status!(unsafe {
sys::napi_create_string_utf8(self.0.env, key_str.as_ptr(), name.len(), &mut js_key)
})?;
check_status!(unsafe {
sys::napi_delete_property(self.0.env, self.0.value, js_key, &mut result)
})?;
Ok(result)
}
pub fn has_own_property(&self, key: &str) -> Result<bool> {
let mut result = false;
let string = CString::new(key)?;
let mut js_key = ptr::null_mut();
check_status!(unsafe {
sys::napi_create_string_utf8(self.0.env, string.as_ptr(), key.len(), &mut js_key)
})?;
check_status!(unsafe {
sys::napi_has_own_property(self.0.env, self.0.value, js_key, &mut result)
})?;
Ok(result)
}
pub fn has_own_property_js<K>(&self, key: K) -> Result<bool>
where
K: NapiRaw,
{
let mut result = false;
check_status!(unsafe {
sys::napi_has_own_property(self.0.env, self.0.value, key.raw(), &mut result)
})?;
Ok(result)
}
pub fn has_property(&self, name: &str) -> Result<bool> {
let string = CString::new(name)?;
let mut js_key = ptr::null_mut();
let mut result = false;
check_status!(unsafe {
sys::napi_create_string_utf8(self.0.env, string.as_ptr(), name.len(), &mut js_key)
})?;
check_status!(unsafe {
sys::napi_has_property(self.0.env, self.0.value, js_key, &mut result)
})?;
Ok(result)
}
pub fn has_property_js<K>(&self, name: K) -> Result<bool>
where
K: NapiRaw,
{
let mut result = false;
check_status!(unsafe {
sys::napi_has_property(self.0.env, self.0.value, name.raw(), &mut result)
})?;
Ok(result)
}
pub fn get_property_names(&self) -> Result<JsObject> {
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_property_names(self.0.env, self.0.value, &mut raw_value)
})?;
Ok(unsafe { JsObject::from_raw_unchecked(self.0.env, raw_value) })
}
/// https://nodejs.org/api/n-api.html#n_api_napi_get_all_property_names
/// return `Array` of property names
#[cfg(feature = "napi6")]
pub fn get_all_property_names(
&self,
mode: KeyCollectionMode,
filter: KeyFilter,
conversion: KeyConversion,
) -> Result<JsObject> {
let mut properties_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_all_property_names(
self.0.env,
self.0.value,
mode.into(),
filter.into(),
conversion.into(),
&mut properties_value,
)
})?;
Ok(unsafe { JsObject::from_raw_unchecked(self.0.env, properties_value) })
}
/// This returns the equivalent of `Object.getPrototypeOf` (which is not the same as the function's prototype property).
pub fn get_prototype<T>(&self) -> Result<T>
where
T: NapiValue,
{
let mut result = ptr::null_mut();
check_status!(unsafe { sys::napi_get_prototype(self.0.env, self.0.value, &mut result) })?;
unsafe { T::from_raw(self.0.env, result) }
}
pub fn get_prototype_unchecked<T>(&self) -> Result<T>
where
T: NapiValue,
{
let mut result = ptr::null_mut();
check_status!(unsafe { sys::napi_get_prototype(self.0.env, self.0.value, &mut result) })?;
Ok(unsafe { T::from_raw_unchecked(self.0.env, result) })
}
pub fn set_element<T>(&mut self, index: u32, value: T) -> Result<()>
where
T: NapiRaw,
{
check_status!(unsafe {
sys::napi_set_element(self.0.env, self.0.value, index, value.raw())
})
}
pub fn has_element(&self, index: u32) -> Result<bool> {
let mut result = false;
check_status!(unsafe {
sys::napi_has_element(self.0.env, self.0.value, index, &mut result)
})?;
Ok(result)
}
pub fn delete_element(&mut self, index: u32) -> Result<bool> {
let mut result = false;
check_status!(unsafe {
sys::napi_delete_element(self.0.env, self.0.value, index, &mut result)
})?;
Ok(result)
}
pub fn get_element<T>(&self, index: u32) -> Result<T>
where
T: NapiValue,
{
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_element(self.0.env, self.0.value, index, &mut raw_value)
})?;
unsafe { T::from_raw(self.0.env, raw_value) }
}
pub fn get_element_unchecked<T>(&self, index: u32) -> Result<T>
where
T: NapiValue,
{
let mut raw_value = ptr::null_mut();
check_status!(unsafe {
sys::napi_get_element(self.0.env, self.0.value, index, &mut raw_value)
})?;
Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) })
}
/// This method allows the efficient definition of multiple properties on a given object.
pub fn define_properties(&mut self, properties: &[Property]) -> Result<()> {
check_status!(unsafe {
sys::napi_define_properties(
self.0.env,
self.0.value,
properties.len(),
properties
.iter()
.map(|property| property.raw())
.collect::<Vec<sys::napi_property_descriptor>>()
.as_ptr(),
)
})
}
/// Perform `is_array` check before get the length
/// if `Object` is not array, `ArrayExpected` error returned
pub fn get_array_length(&self) -> Result<u32> {
if self.is_array()? != true {
return Err(Error::new(
Status::ArrayExpected,
"Object is not array".to_owned(),
));
}
self.get_array_length_unchecked()
}
/// use this API if you can ensure this `Object` is `Array`
pub fn get_array_length_unchecked(&self) -> Result<u32> {
let mut length: u32 = 0;
check_status!(unsafe {
sys::napi_get_array_length(self.0.env, self.0.value, &mut length)
})?;
Ok(length)
}
}
};
}
pub trait NapiRaw {
#[allow(clippy::missing_safety_doc)]
unsafe fn raw(&self) -> sys::napi_value;
}
pub trait NapiValue: Sized + NapiRaw {
#[allow(clippy::missing_safety_doc)]
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self>;
#[allow(clippy::missing_safety_doc)]
unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self;
}
impl_js_value_methods!(JsUnknown);
impl_js_value_methods!(JsUndefined);
impl_js_value_methods!(JsNull);
impl_js_value_methods!(JsBoolean);
impl_js_value_methods!(JsBuffer);
impl_js_value_methods!(JsArrayBuffer);
impl_js_value_methods!(JsTypedArray);
impl_js_value_methods!(JsDataView);
impl_js_value_methods!(JsNumber);
impl_js_value_methods!(JsString);
impl_js_value_methods!(JsObject);
impl_js_value_methods!(JsGlobal);
#[cfg(feature = "napi5")]
impl_js_value_methods!(JsDate);
impl_js_value_methods!(JsFunction);
impl_js_value_methods!(JsExternal);
impl_js_value_methods!(JsSymbol);
impl_js_value_methods!(JsTimeout);
impl_object_methods!(JsObject);
impl_object_methods!(JsBuffer);
impl_object_methods!(JsArrayBuffer);
impl_object_methods!(JsTypedArray);
impl_object_methods!(JsDataView);
impl_object_methods!(JsGlobal);
use ValueType::*;
impl_napi_value_trait!(JsUndefined, Undefined);
impl_napi_value_trait!(JsNull, Null);
impl_napi_value_trait!(JsBoolean, Boolean);
impl_napi_value_trait!(JsBuffer, Object);
impl_napi_value_trait!(JsArrayBuffer, Object);
impl_napi_value_trait!(JsTypedArray, Object);
impl_napi_value_trait!(JsDataView, Object);
impl_napi_value_trait!(JsNumber, Number);
impl_napi_value_trait!(JsString, String);
impl_napi_value_trait!(JsObject, Object);
impl_napi_value_trait!(JsGlobal, Object);
#[cfg(feature = "napi5")]
impl_napi_value_trait!(JsDate, Object);
impl_napi_value_trait!(JsTimeout, Object);
impl_napi_value_trait!(JsFunction, Function);
impl_napi_value_trait!(JsExternal, External);
impl_napi_value_trait!(JsSymbol, Symbol);
impl NapiValue for JsUnknown {
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
Ok(JsUnknown(Value {
env,
value,
value_type: Unknown,
}))
}
unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
JsUnknown(Value {
env,
value,
value_type: Unknown,
})
}
}
impl NapiRaw for JsUnknown {
/// get raw js value ptr
unsafe fn raw(&self) -> sys::napi_value {
self.0.value
}
}
impl<'env> NapiRaw for &'env JsUnknown {
/// get raw js value ptr
unsafe fn raw(&self) -> sys::napi_value {
self.0.value
}
}
impl JsUnknown {
pub fn get_type(&self) -> Result<ValueType> {
unsafe { type_of!(self.0.env, self.0.value) }
}
/// # Safety
///
/// This function should be called after `JsUnknown::get_type`
///
/// And the `V` must be match with the return value of `get_type`
pub unsafe fn cast<V>(&self) -> V
where
V: NapiValue,
{
V::from_raw_unchecked(self.0.env, self.0.value)
}
}

View file

@ -0,0 +1,74 @@
use std::convert::TryFrom;
use super::Value;
use crate::check_status;
use crate::{sys, Error, Result};
#[derive(Clone, Copy)]
pub struct JsNumber(pub(crate) Value);
impl JsNumber {
pub fn get_uint32(&self) -> Result<u32> {
let mut result = 0;
check_status!(unsafe { sys::napi_get_value_uint32(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
pub fn get_int32(&self) -> Result<i32> {
let mut result = 0;
check_status!(unsafe { sys::napi_get_value_int32(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
pub fn get_int64(&self) -> Result<i64> {
let mut result = 0;
check_status!(unsafe { sys::napi_get_value_int64(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
pub fn get_double(&self) -> Result<f64> {
let mut result = 0_f64;
check_status!(unsafe { sys::napi_get_value_double(self.0.env, self.0.value, &mut result) })?;
Ok(result)
}
}
impl TryFrom<JsNumber> for u32 {
type Error = Error;
fn try_from(value: JsNumber) -> Result<u32> {
let mut result = 0;
check_status!(unsafe { sys::napi_get_value_uint32(value.0.env, value.0.value, &mut result) })?;
Ok(result)
}
}
impl TryFrom<JsNumber> for i32 {
type Error = Error;
fn try_from(value: JsNumber) -> Result<i32> {
let mut result = 0;
check_status!(unsafe { sys::napi_get_value_int32(value.0.env, value.0.value, &mut result) })?;
Ok(result)
}
}
impl TryFrom<JsNumber> for i64 {
type Error = Error;
fn try_from(value: JsNumber) -> Result<i64> {
let mut result = 0;
check_status!(unsafe { sys::napi_get_value_int64(value.0.env, value.0.value, &mut result) })?;
Ok(result)
}
}
impl TryFrom<JsNumber> for f64 {
type Error = Error;
fn try_from(value: JsNumber) -> Result<f64> {
let mut result = 0_f64;
check_status!(unsafe { sys::napi_get_value_double(value.0.env, value.0.value, &mut result) })?;
Ok(result)
}
}

View file

@ -0,0 +1,185 @@
#[cfg(feature = "napi6")]
use std::convert::TryFrom;
#[cfg(feature = "napi5")]
use std::ffi::c_void;
#[cfg(feature = "napi5")]
use std::ptr;
#[cfg(feature = "napi5")]
use super::check_status;
use super::Value;
#[cfg(feature = "napi5")]
use crate::sys;
#[cfg(feature = "napi5")]
use crate::Env;
#[cfg(feature = "napi6")]
use crate::Error;
#[cfg(feature = "napi5")]
use crate::Result;
pub struct JsObject(pub(crate) Value);
#[cfg(feature = "napi5")]
pub struct FinalizeContext<T: 'static, Hint: 'static> {
pub env: Env,
pub value: T,
pub hint: Hint,
}
#[cfg(feature = "napi5")]
impl JsObject {
pub fn add_finalizer<T, Hint, F>(
&mut self,
native: T,
finalize_hint: Hint,
finalize_cb: F,
) -> Result<()>
where
T: 'static,
Hint: 'static,
F: FnOnce(FinalizeContext<T, Hint>),
{
let mut maybe_ref = ptr::null_mut();
let wrap_context = Box::leak(Box::new((native, finalize_cb, ptr::null_mut())));
check_status!(unsafe {
sys::napi_add_finalizer(
self.0.env,
self.0.value,
wrap_context as *mut _ as *mut c_void,
Some(
finalize_callback::<T, Hint, F>
as unsafe extern "C" fn(
env: sys::napi_env,
finalize_data: *mut c_void,
finalize_hint: *mut c_void,
),
),
Box::leak(Box::new(finalize_hint)) as *mut _ as *mut c_void,
&mut maybe_ref, // Note: this does not point to the boxed one…
)
})?;
wrap_context.2 = maybe_ref;
Ok(())
}
}
#[cfg(feature = "napi5")]
unsafe extern "C" fn finalize_callback<T, Hint, F>(
raw_env: sys::napi_env,
finalize_data: *mut c_void,
finalize_hint: *mut c_void,
) where
T: 'static,
Hint: 'static,
F: FnOnce(FinalizeContext<T, Hint>),
{
let (value, callback, raw_ref) = *Box::from_raw(finalize_data as *mut (T, F, sys::napi_ref));
let hint = *Box::from_raw(finalize_hint as *mut Hint);
let env = Env::from_raw(raw_env);
callback(FinalizeContext { env, value, hint });
if !raw_ref.is_null() {
let status = sys::napi_delete_reference(raw_env, raw_ref);
debug_assert!(
status == sys::Status::napi_ok,
"Delete reference in finalize callback failed"
);
}
}
#[cfg(feature = "napi6")]
pub enum KeyCollectionMode {
IncludePrototypes,
OwnOnly,
}
#[cfg(feature = "napi6")]
impl TryFrom<sys::napi_key_collection_mode> for KeyCollectionMode {
type Error = Error;
fn try_from(value: sys::napi_key_collection_mode) -> Result<Self> {
match value {
sys::napi_key_collection_mode::napi_key_include_prototypes => Ok(Self::IncludePrototypes),
sys::napi_key_collection_mode::napi_key_own_only => Ok(Self::OwnOnly),
}
}
}
#[cfg(feature = "napi6")]
impl From<KeyCollectionMode> for sys::napi_key_collection_mode {
fn from(value: KeyCollectionMode) -> Self {
match value {
KeyCollectionMode::IncludePrototypes => {
sys::napi_key_collection_mode::napi_key_include_prototypes
}
KeyCollectionMode::OwnOnly => sys::napi_key_collection_mode::napi_key_own_only,
}
}
}
#[cfg(feature = "napi6")]
pub enum KeyFilter {
AllProperties,
Writable,
Enumerable,
Configurable,
SkipStrings,
SkipSymbols,
}
#[cfg(feature = "napi6")]
impl TryFrom<sys::napi_key_filter> for KeyFilter {
type Error = Error;
fn try_from(value: sys::napi_key_filter) -> Result<Self> {
match value {
sys::napi_key_filter::napi_key_all_properties => Ok(Self::AllProperties),
sys::napi_key_filter::napi_key_writable => Ok(Self::Writable),
sys::napi_key_filter::napi_key_enumerable => Ok(Self::Enumerable),
sys::napi_key_filter::napi_key_configurable => Ok(Self::Configurable),
sys::napi_key_filter::napi_key_skip_strings => Ok(Self::SkipStrings),
sys::napi_key_filter::napi_key_skip_symbols => Ok(Self::SkipSymbols),
}
}
}
#[cfg(feature = "napi6")]
impl From<KeyFilter> for sys::napi_key_filter {
fn from(value: KeyFilter) -> Self {
match value {
KeyFilter::AllProperties => Self::napi_key_all_properties,
KeyFilter::Writable => Self::napi_key_writable,
KeyFilter::Enumerable => Self::napi_key_enumerable,
KeyFilter::Configurable => Self::napi_key_configurable,
KeyFilter::SkipStrings => Self::napi_key_skip_strings,
KeyFilter::SkipSymbols => Self::napi_key_skip_symbols,
}
}
}
#[cfg(feature = "napi6")]
pub enum KeyConversion {
KeepNumbers,
NumbersToStrings,
}
#[cfg(feature = "napi6")]
impl TryFrom<sys::napi_key_conversion> for KeyConversion {
type Error = Error;
fn try_from(value: sys::napi_key_conversion) -> Result<Self> {
match value {
sys::napi_key_conversion::napi_key_keep_numbers => Ok(Self::KeepNumbers),
sys::napi_key_conversion::napi_key_numbers_to_strings => Ok(Self::NumbersToStrings),
}
}
}
#[cfg(feature = "napi6")]
impl From<KeyConversion> for sys::napi_key_conversion {
fn from(value: KeyConversion) -> Self {
match value {
KeyConversion::KeepNumbers => Self::napi_key_keep_numbers,
KeyConversion::NumbersToStrings => Self::napi_key_numbers_to_strings,
}
}
}

View file

@ -0,0 +1,96 @@
use std::convert::From;
use std::ffi::CString;
use std::ptr;
use crate::{sys, Callback, Result};
#[derive(Clone, Default)]
pub struct Property {
pub name: CString,
getter: sys::napi_callback,
setter: sys::napi_callback,
method: sys::napi_callback,
attrs: PropertyAttributes,
pub(crate) is_ctor: bool,
}
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum PropertyAttributes {
Default = sys::napi_property_attributes::napi_default as _,
Writable = sys::napi_property_attributes::napi_writable as _,
Enumerable = sys::napi_property_attributes::napi_enumerable as _,
Configurable = sys::napi_property_attributes::napi_configurable as _,
Static = sys::napi_property_attributes::napi_static as _,
}
impl Default for PropertyAttributes {
fn default() -> Self {
PropertyAttributes::Default
}
}
impl From<PropertyAttributes> for sys::napi_property_attributes {
fn from(value: PropertyAttributes) -> Self {
match value {
PropertyAttributes::Default => sys::napi_property_attributes::napi_default,
PropertyAttributes::Writable => sys::napi_property_attributes::napi_writable,
PropertyAttributes::Enumerable => sys::napi_property_attributes::napi_enumerable,
PropertyAttributes::Configurable => sys::napi_property_attributes::napi_configurable,
PropertyAttributes::Static => sys::napi_property_attributes::napi_static,
}
}
}
impl Property {
pub fn new(name: &str) -> Result<Self> {
Ok(Property {
name: CString::new(name)?,
..Default::default()
})
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = CString::new(name).unwrap();
self
}
pub fn with_method(mut self, callback: Callback) -> Self {
self.method = Some(callback);
self
}
pub fn with_getter(mut self, callback: Callback) -> Self {
self.getter = Some(callback);
self
}
pub fn with_setter(mut self, callback: Callback) -> Self {
self.setter = Some(callback);
self
}
pub fn with_property_attributes(mut self, attributes: PropertyAttributes) -> Self {
self.attrs = attributes;
self
}
pub(crate) fn raw(&self) -> sys::napi_property_descriptor {
sys::napi_property_descriptor {
utf8name: self.name.as_ptr(),
name: ptr::null_mut(),
method: self.method,
getter: self.getter,
setter: self.setter,
value: ptr::null_mut(),
attributes: self.attrs.into(),
data: ptr::null_mut(),
}
}
pub fn with_ctor(mut self, callback: Callback) -> Self {
self.method = Some(callback);
self.is_ctor = true;
self
}
}

View file

@ -0,0 +1,487 @@
use std::result::Result as StdResult;
#[cfg(feature = "napi6")]
use std::slice;
use serde::{ser, Serialize, Serializer};
use super::*;
use crate::{Env, Error, Result};
pub(crate) struct Ser<'env>(pub(crate) &'env Env);
impl<'env> Ser<'env> {
fn new(env: &'env Env) -> Self {
Self(env)
}
}
impl<'env> Serializer for Ser<'env> {
type Ok = Value;
type Error = Error;
type SerializeSeq = SeqSerializer;
type SerializeTuple = SeqSerializer;
type SerializeTupleStruct = SeqSerializer;
type SerializeTupleVariant = SeqSerializer;
type SerializeMap = MapSerializer;
type SerializeStruct = StructSerializer;
type SerializeStructVariant = StructSerializer;
fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
self.0.get_boolean(v).map(|js_value| js_value.0)
}
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
self
.0
.create_buffer_with_data(v.to_owned())
.map(|js_value| js_value.value.0)
}
fn serialize_char(self, v: char) -> Result<Self::Ok> {
let mut b = [0; 4];
let result = v.encode_utf8(&mut b);
self.0.create_string(result).map(|js_string| js_string.0)
}
fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
self.0.create_double(v as _).map(|js_number| js_number.0)
}
fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
self.0.create_double(v).map(|js_number| js_number.0)
}
fn serialize_i16(self, v: i16) -> Result<Self::Ok> {
self.0.create_int32(v as _).map(|js_number| js_number.0)
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok> {
self.0.create_int32(v).map(|js_number| js_number.0)
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
self.0.create_int64(v).map(|js_number| js_number.0)
}
fn serialize_i8(self, v: i8) -> Result<Self::Ok> {
self.0.create_int32(v as _).map(|js_number| js_number.0)
}
fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
self.0.create_uint32(v as _).map(|js_number| js_number.0)
}
fn serialize_u16(self, v: u16) -> Result<Self::Ok> {
self.0.create_uint32(v as _).map(|js_number| js_number.0)
}
fn serialize_u32(self, v: u32) -> Result<Self::Ok> {
self.0.create_uint32(v).map(|js_number| js_number.0)
}
#[cfg(all(
any(
feature = "napi2",
feature = "napi3",
feature = "napi4",
feature = "napi5"
),
not(feature = "napi6")
))]
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
self.0.create_int64(v as _).map(|js_number| js_number.0)
}
#[cfg(feature = "napi6")]
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
self
.0
.create_bigint_from_u64(v)
.map(|js_number| js_number.raw)
}
#[cfg(all(
any(
feature = "napi2",
feature = "napi3",
feature = "napi4",
feature = "napi5"
),
not(feature = "napi6")
))]
fn serialize_u128(self, v: u128) -> Result<Self::Ok> {
self.0.create_string(v.to_string().as_str()).map(|v| v.0)
}
#[cfg(feature = "napi6")]
fn serialize_u128(self, v: u128) -> Result<Self::Ok> {
let words_ref = &v as *const _;
let words = unsafe { slice::from_raw_parts(words_ref as *const u64, 2) };
self
.0
.create_bigint_from_words(false, words.to_vec())
.map(|v| v.raw)
}
#[cfg(all(
any(
feature = "napi2",
feature = "napi3",
feature = "napi4",
feature = "napi5"
),
not(feature = "napi6")
))]
fn serialize_i128(self, v: i128) -> Result<Self::Ok> {
self.0.create_string(v.to_string().as_str()).map(|v| v.0)
}
#[cfg(feature = "napi6")]
fn serialize_i128(self, v: i128) -> Result<Self::Ok> {
let words_ref = &(v as u128) as *const _;
let words = unsafe { slice::from_raw_parts(words_ref as *const u64, 2) };
self
.0
.create_bigint_from_words(v < 0, words.to_vec())
.map(|v| v.raw)
}
fn serialize_unit(self) -> Result<Self::Ok> {
self.0.get_null().map(|null| null.0)
}
fn serialize_none(self) -> Result<Self::Ok> {
self.0.get_null().map(|null| null.0)
}
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
self.0.create_string(v).map(|string| string.0)
}
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok>
where
T: Serialize,
{
value.serialize(self)
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
let env = self.0;
let key = env.create_string("")?;
let obj = env.create_object()?;
Ok(MapSerializer { key, obj })
}
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let array = self.0.create_array_with_length(len.unwrap_or(0))?;
Ok(SeqSerializer {
current_index: 0,
array,
})
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant> {
let env = self.0;
let array = env.create_array_with_length(len)?;
let mut object = env.create_object()?;
object.set_named_property(
variant,
JsObject(Value {
value: array.0.value,
env: array.0.env,
value_type: ValueType::Object,
}),
)?;
Ok(SeqSerializer {
current_index: 0,
array,
})
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
self.0.get_null().map(|null| null.0)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok> {
self.0.create_string(variant).map(|string| string.0)
}
fn serialize_newtype_struct<T: ?Sized>(self, _name: &'static str, value: &T) -> Result<Self::Ok>
where
T: Serialize,
{
value.serialize(self)
}
fn serialize_newtype_variant<T: ?Sized>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok>
where
T: Serialize,
{
let mut obj = self.0.create_object()?;
obj.set_named_property(variant, JsUnknown(value.serialize(self)?))?;
Ok(obj.0)
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
Ok(SeqSerializer {
array: self.0.create_array_with_length(len)?,
current_index: 0,
})
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
Ok(SeqSerializer {
array: self.0.create_array_with_length(len)?,
current_index: 0,
})
}
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
Ok(StructSerializer {
obj: self.0.create_object()?,
})
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
let mut outer = self.0.create_object()?;
let inner = self.0.create_object()?;
outer.set_named_property(
variant,
JsObject(Value {
env: inner.0.env,
value: inner.0.value,
value_type: ValueType::Object,
}),
)?;
Ok(StructSerializer {
obj: self.0.create_object()?,
})
}
}
pub struct SeqSerializer {
array: JsObject,
current_index: usize,
}
impl ser::SerializeSeq for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.array.0.env) };
self.array.set_element(
self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?),
)?;
self.current_index += 1;
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.array.0)
}
}
#[doc(hidden)]
impl ser::SerializeTuple for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.array.0.env) };
self.array.set_element(
self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?),
)?;
self.current_index += 1;
Ok(())
}
fn end(self) -> StdResult<Self::Ok, Self::Error> {
Ok(self.array.0)
}
}
#[doc(hidden)]
impl ser::SerializeTupleStruct for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.array.0.env) };
self.array.set_element(
self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?),
)?;
self.current_index += 1;
Ok(())
}
fn end(self) -> StdResult<Self::Ok, Self::Error> {
Ok(self.array.0)
}
}
#[doc(hidden)]
impl ser::SerializeTupleVariant for SeqSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.array.0.env) };
self.array.set_element(
self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?),
)?;
self.current_index += 1;
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.array.0)
}
}
pub struct MapSerializer {
key: JsString,
obj: JsObject,
}
#[doc(hidden)]
impl ser::SerializeMap for MapSerializer {
type Ok = Value;
type Error = Error;
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> StdResult<(), Self::Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.obj.0.env) };
self.key = JsString(key.serialize(Ser::new(&env))?);
Ok(())
}
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.obj.0.env) };
self.obj.set_property(
JsString(Value {
env: self.key.0.env,
value: self.key.0.value,
value_type: ValueType::String,
}),
JsUnknown(value.serialize(Ser::new(&env))?),
)?;
Ok(())
}
fn serialize_entry<K: ?Sized, V: ?Sized>(
&mut self,
key: &K,
value: &V,
) -> StdResult<(), Self::Error>
where
K: Serialize,
V: Serialize,
{
let env = unsafe { Env::from_raw(self.obj.0.env) };
self.obj.set_property(
JsString(key.serialize(Ser::new(&env))?),
JsUnknown(value.serialize(Ser::new(&env))?),
)?;
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.obj.0)
}
}
pub struct StructSerializer {
obj: JsObject,
}
#[doc(hidden)]
impl ser::SerializeStruct for StructSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> StdResult<(), Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.obj.0.env) };
self
.obj
.set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?;
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.obj.0)
}
}
#[doc(hidden)]
impl ser::SerializeStructVariant for StructSerializer {
type Ok = Value;
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> StdResult<(), Error>
where
T: Serialize,
{
let env = unsafe { Env::from_raw(self.obj.0.env) };
self
.obj
.set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?;
Ok(())
}
fn end(self) -> Result<Self::Ok> {
Ok(self.obj.0)
}
}

View file

@ -0,0 +1,46 @@
use std::mem::ManuallyDrop;
use crate::JsString;
#[cfg(feature = "latin1")]
use crate::Result;
pub struct JsStringLatin1 {
pub(crate) inner: JsString,
pub(crate) buf: ManuallyDrop<Vec<u8>>,
}
impl JsStringLatin1 {
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn take(self) -> Vec<u8> {
self.as_slice().to_vec()
}
pub fn into_value(self) -> JsString {
self.inner
}
#[cfg(feature = "latin1")]
pub fn into_latin1_string(self) -> Result<String> {
let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; self.len() * 2 + 1]) };
encoding_rs::mem::convert_latin1_to_str(self.buf.as_slice(), dst_str.as_mut_str());
Ok(dst_str)
}
}
impl From<JsStringLatin1> for Vec<u8> {
fn from(value: JsStringLatin1) -> Self {
value.take()
}
}

View file

@ -0,0 +1,108 @@
use std::mem;
use std::ptr;
use crate::{check_status, sys, Result, Value};
pub use latin1::JsStringLatin1;
pub use utf16::JsStringUtf16;
pub use utf8::JsStringUtf8;
mod latin1;
mod utf16;
mod utf8;
#[derive(Clone, Copy)]
pub struct JsString(pub(crate) Value);
impl JsString {
pub fn utf8_len(&self) -> Result<usize> {
let mut length = 0;
check_status!(unsafe {
sys::napi_get_value_string_utf8(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn utf16_len(&self) -> Result<usize> {
let mut length = 0;
check_status!(unsafe {
sys::napi_get_value_string_utf16(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn latin1_len(&self) -> Result<usize> {
let mut length = 0;
check_status!(unsafe {
sys::napi_get_value_string_latin1(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn into_utf8(self) -> Result<JsStringUtf8> {
let mut written_char_count = 0;
let len = self.utf8_len()? + 1;
let mut result = Vec::with_capacity(len);
let buf_ptr = result.as_mut_ptr();
check_status!(unsafe {
sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
buf_ptr,
len,
&mut written_char_count,
)
})?;
Ok(JsStringUtf8 {
inner: self,
buf: result,
})
}
pub fn into_utf16(self) -> Result<JsStringUtf16> {
let mut written_char_count = 0usize;
let len = self.utf16_len()? + 1;
let mut result = vec![0; len];
let buf_ptr = result.as_mut_ptr();
check_status!(unsafe {
sys::napi_get_value_string_utf16(
self.0.env,
self.0.value,
buf_ptr,
len,
&mut written_char_count,
)
})?;
Ok(JsStringUtf16 {
inner: self,
buf: result,
})
}
pub fn into_latin1(self) -> Result<JsStringLatin1> {
let mut written_char_count = 0usize;
let len = self.latin1_len()? + 1;
let mut result = Vec::with_capacity(len);
let buf_ptr = result.as_mut_ptr();
check_status!(unsafe {
sys::napi_get_value_string_latin1(
self.0.env,
self.0.value,
buf_ptr,
len,
&mut written_char_count,
)
})?;
mem::forget(result);
Ok(JsStringLatin1 {
inner: self,
buf: mem::ManuallyDrop::new(unsafe {
Vec::from_raw_parts(buf_ptr as *mut _, written_char_count, written_char_count)
}),
})
}
}

View file

@ -0,0 +1,63 @@
use std::convert::TryFrom;
use std::ops::Deref;
use crate::{Error, JsString, Result, Status};
pub struct JsStringUtf16 {
pub(crate) inner: JsString,
pub(crate) buf: Vec<u16>,
}
impl JsStringUtf16 {
pub fn as_str(&self) -> Result<String> {
if let Some((_, prefix)) = self.as_slice().split_last() {
String::from_utf16(prefix).map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
} else {
Ok(String::new())
}
}
pub fn as_slice(&self) -> &[u16] {
self.buf.as_slice()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn into_value(self) -> JsString {
self.inner
}
}
impl TryFrom<JsStringUtf16> for String {
type Error = Error;
fn try_from(value: JsStringUtf16) -> Result<String> {
value.as_str()
}
}
impl Deref for JsStringUtf16 {
type Target = [u16];
fn deref(&self) -> &[u16] {
self.buf.as_slice()
}
}
impl AsRef<Vec<u16>> for JsStringUtf16 {
fn as_ref(&self) -> &Vec<u16> {
&self.buf
}
}
impl From<JsStringUtf16> for Vec<u16> {
fn from(value: JsStringUtf16) -> Self {
value.as_slice().to_vec()
}
}

View file

@ -0,0 +1,57 @@
use std::convert::TryFrom;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::str;
use crate::{Error, JsString, Result, Status};
pub struct JsStringUtf8 {
pub(crate) inner: JsString,
pub(crate) buf: Vec<c_char>,
}
impl JsStringUtf8 {
pub fn as_str(&self) -> Result<&str> {
unsafe { CStr::from_ptr(self.buf.as_ptr()) }
.to_str()
.map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
}
pub fn as_slice(&self) -> &[u8] {
unsafe { CStr::from_ptr(self.buf.as_ptr()) }.to_bytes()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn into_owned(self) -> Result<String> {
Ok(self.as_str()?.to_owned())
}
pub fn take(self) -> Vec<u8> {
self.as_slice().to_vec()
}
pub fn into_value(self) -> JsString {
self.inner
}
}
impl TryFrom<JsStringUtf8> for String {
type Error = Error;
fn try_from(value: JsStringUtf8) -> Result<String> {
value.into_owned()
}
}
impl From<JsStringUtf8> for Vec<u8> {
fn from(value: JsStringUtf8) -> Self {
value.take()
}
}

View file

@ -0,0 +1,16 @@
use std::any::TypeId;
#[repr(C)]
pub struct TaggedObject<T> {
type_id: TypeId,
pub(crate) object: Option<T>,
}
impl<T: 'static> TaggedObject<T> {
pub fn new(object: T) -> Self {
TaggedObject {
type_id: TypeId::of::<T>(),
object: Some(object),
}
}
}

View file

@ -0,0 +1,4 @@
use super::Value;
#[derive(Clone, Copy)]
pub struct JsUndefined(pub(crate) Value);

View file

@ -0,0 +1,10 @@
use crate::sys;
use super::ValueType;
#[derive(Clone, Copy)]
pub struct Value {
pub env: sys::napi_env,
pub value: sys::napi_value,
pub value_type: ValueType,
}

View file

@ -0,0 +1,63 @@
use std::ops::Deref;
use std::ptr;
use super::{check_status, Value};
use crate::{sys, Env, Result};
pub struct Ref<T> {
pub(crate) raw_ref: sys::napi_ref,
pub(crate) count: u32,
pub(crate) inner: T,
pub(crate) raw_value: sys::napi_value,
}
unsafe impl<T> Send for Ref<T> {}
unsafe impl<T> Sync for Ref<T> {}
impl<T> Ref<T> {
pub(crate) fn new(js_value: Value, ref_count: u32, inner: T) -> Result<Ref<T>> {
let mut raw_ref = ptr::null_mut();
assert_ne!(ref_count, 0, "Initial `ref_count` must be > 0");
check_status!(unsafe {
sys::napi_create_reference(js_value.env, js_value.value, ref_count, &mut raw_ref)
})?;
Ok(Ref {
raw_ref,
count: ref_count,
inner,
raw_value: js_value.value,
})
}
pub fn reference(&mut self, env: &Env) -> Result<u32> {
check_status!(unsafe { sys::napi_reference_ref(env.0, self.raw_ref, &mut self.count) })?;
Ok(self.count)
}
pub fn unref(mut self, env: Env) -> Result<u32> {
check_status!(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?;
if self.count == 0 {
check_status!(unsafe { sys::napi_delete_reference(env.0, self.raw_ref) })?;
}
Ok(self.count)
}
}
impl<T> Deref for Ref<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
#[cfg(debug_assertions)]
impl<T> Drop for Ref<T> {
fn drop(&mut self) {
debug_assert_eq!(
self.count, 0,
"Ref count is not equal to 0 while dropping Ref, potential memory leak"
);
}
}

161
crates/napi/src/lib.rs Normal file
View file

@ -0,0 +1,161 @@
#![deny(clippy::all)]
//! High level Node.js [N-API](https://nodejs.org/api/n-api.html) binding
//!
//! **napi-rs** provides minimal overhead to write N-API modules in `Rust`.
//!
//! ## Feature flags
//!
//! ### napi1 ~ napi8
//!
//! Because `Node.js` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for.
//! For example, if you want build a library which can be used by `node@10.17.0`, you should choose the `napi5` or lower.
//!
//! The details of N-API versions and support matrix: [n_api_version_matrix](https://nodejs.org/api/n-api.html#n_api_n_api_version_matrix)
//!
//! ### tokio_rt
//! With `tokio_rt` feature, `napi-rs` provides a ***tokio runtime*** in an additional thread.
//! And you can easily run tokio `future` in it and return `promise`.
//!
//! ```
//! use futures::prelude::*;
//! use napi::{CallContext, Error, JsObject, JsString, Result, Status};
//! use tokio;
//!
//! #[js_function(1)]
//! pub fn tokio_readfile(ctx: CallContext) -> Result<JsObject> {
//! let js_filepath = ctx.get::<JsString>(0)?;
//! let path_str = js_filepath.as_str()?;
//! ctx.env.execute_tokio_future(
//! tokio::fs::read(path_str.to_owned())
//! .map(|v| v.map_err(|e| Error::new(Status::Unknown, format!("failed to read file, {}", e)))),
//! |&mut env, data| env.create_buffer_with_data(data),
//! )
//! }
//! ```
//!
//! ***Tokio channel in `napi-rs` buffer size is default `100`.***
//!
//! ***You can adjust it via `NAPI_RS_TOKIO_CHANNEL_BUFFER_SIZE` environment variable***
//!
//! ```
//! NAPI_RS_TOKIO_CHANNEL_BUFFER_SIZE=1000 node ./app.js
//! ```
//!
//! ### latin1
//!
//! Decode latin1 string from JavaScript using [encoding_rs](https://docs.rs/encoding_rs).
//!
//! With this feature, you can use `JsString.as_latin1_string` function
//!
//! ### serde-json
//!
//! Enable Serialize/Deserialize data cross `JavaScript Object` and `Rust struct`.
//!
//! ```
//! #[derive(Serialize, Debug, Deserialize)]
//! struct AnObject {
//! a: u32,
//! b: Vec<f64>,
//! c: String,
//! }
//!
//! #[js_function(1)]
//! fn deserialize_from_js(ctx: CallContext) -> Result<JsUndefined> {
//! let arg0 = ctx.get::<JsUnknown>(0)?;
//! let de_serialized: AnObject = ctx.env.from_js_value(arg0)?;
//! ...
//! }
//!
//! #[js_function]
//! fn serialize(ctx: CallContext) -> Result<JsUnknown> {
//! let value = AnyObject { a: 1, b: vec![0.1, 2.22], c: "hello" };
//! ctx.env.to_js_value(&value)
//! }
//! ```
//!
#[cfg(feature = "napi8")]
mod async_cleanup_hook;
#[cfg(feature = "napi8")]
pub use async_cleanup_hook::AsyncCleanupHook;
mod async_work;
mod bindgen_runtime;
mod call_context;
#[cfg(feature = "napi3")]
mod cleanup_env;
mod env;
mod error;
mod js_values;
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
mod promise;
mod status;
mod task;
mod value_type;
#[cfg(feature = "napi3")]
pub use cleanup_env::CleanupEnvHook;
#[cfg(feature = "napi4")]
pub mod threadsafe_function;
mod version;
#[cfg(target_os = "windows")]
mod win_delay_load_hook;
pub use napi_sys as sys;
pub use async_work::AsyncWorkPromise;
pub use call_context::CallContext;
pub use env::*;
pub use error::*;
pub use js_values::*;
pub use status::Status;
pub use task::Task;
pub use value_type::*;
pub use version::NodeVersion;
#[cfg(feature = "serde-json")]
#[macro_use]
extern crate serde;
pub type ContextlessResult<T> = Result<Option<T>>;
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! type_of {
($env:expr, $value:expr) => {{
let mut value_type = 0;
check_status!($crate::sys::napi_typeof($env, $value, &mut value_type))
.and_then(|_| Ok($crate::ValueType::from(value_type)))
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert_type_of {
($env: expr, $value:expr, $value_ty: expr) => {
$crate::type_of!($env, $value).and_then(|received_type| {
if received_type == $value_ty {
Ok(())
} else {
Err($crate::Error::new(
$crate::Status::InvalidArg,
format!(
"Expect value to be {}, but received {}",
$value_ty, received_type
),
))
}
})
};
}
pub mod bindgen_prelude {
#[cfg(feature = "compat-mode")]
pub use crate::bindgen_runtime::register_module_exports;
pub use crate::{
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
};
}

118
crates/napi/src/promise.rs Normal file
View file

@ -0,0 +1,118 @@
use std::future::Future;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_void};
use std::ptr;
use crate::{check_status, sys, Env, JsError, NapiRaw, Result};
pub struct FuturePromise<T, V: NapiRaw, F: FnOnce(&mut Env, T) -> Result<V>> {
deferred: sys::napi_deferred,
env: sys::napi_env,
tsfn: sys::napi_threadsafe_function,
async_resource_name: sys::napi_value,
resolver: F,
_data: PhantomData<T>,
_value: PhantomData<V>,
}
unsafe impl<T, V: NapiRaw, F: FnOnce(&mut Env, T) -> Result<V>> Send for FuturePromise<T, V, F> {}
impl<T, V: NapiRaw, F: FnOnce(&mut Env, T) -> Result<V>> FuturePromise<T, V, F> {
pub fn create(env: sys::napi_env, raw_deferred: sys::napi_deferred, resolver: F) -> Result<Self> {
let mut async_resource_name = ptr::null_mut();
let s = "napi_resolve_promise_from_future";
check_status!(unsafe {
sys::napi_create_string_utf8(
env,
s.as_ptr() as *const c_char,
s.len(),
&mut async_resource_name,
)
})?;
Ok(FuturePromise {
deferred: raw_deferred,
resolver,
env,
tsfn: ptr::null_mut(),
async_resource_name,
_data: PhantomData,
_value: PhantomData,
})
}
pub(crate) fn start(self) -> Result<TSFNValue> {
let mut tsfn_value = ptr::null_mut();
let async_resource_name = self.async_resource_name;
let env = self.env;
let self_ref = Box::leak(Box::from(self));
check_status!(unsafe {
sys::napi_create_threadsafe_function(
env,
ptr::null_mut(),
ptr::null_mut(),
async_resource_name,
0,
1,
ptr::null_mut(),
None,
self_ref as *mut FuturePromise<T, V, F> as *mut c_void,
Some(call_js_cb::<T, V, F>),
&mut tsfn_value,
)
})?;
self_ref.tsfn = tsfn_value;
Ok(TSFNValue(tsfn_value))
}
}
pub(crate) struct TSFNValue(sys::napi_threadsafe_function);
unsafe impl Send for TSFNValue {}
pub(crate) async fn resolve_from_future<T: Send, F: Future<Output = Result<T>>>(
tsfn_value: TSFNValue,
fut: F,
) {
let val = fut.await;
check_status!(unsafe {
sys::napi_call_threadsafe_function(
tsfn_value.0,
Box::into_raw(Box::from(val)) as *mut T as *mut c_void,
sys::napi_threadsafe_function_call_mode::napi_tsfn_nonblocking,
)
})
.expect("Failed to call thread safe function");
check_status!(unsafe {
sys::napi_release_threadsafe_function(
tsfn_value.0,
sys::napi_threadsafe_function_release_mode::napi_tsfn_release,
)
})
.expect("Failed to release thread safe function");
}
unsafe extern "C" fn call_js_cb<T, V: NapiRaw, F: FnOnce(&mut Env, T) -> Result<V>>(
raw_env: sys::napi_env,
_js_callback: sys::napi_value,
context: *mut c_void,
data: *mut c_void,
) {
let mut env = Env::from_raw(raw_env);
let future_promise = Box::from_raw(context as *mut FuturePromise<T, V, F>);
let value = Box::from_raw(data as *mut Result<T>);
let resolver = future_promise.resolver;
let deferred = future_promise.deferred;
let js_value_to_resolve = value.and_then(move |v| (resolver)(&mut env, v));
match js_value_to_resolve {
Ok(v) => {
let status = sys::napi_resolve_deferred(raw_env, deferred, v.raw());
debug_assert!(status == sys::Status::napi_ok, "Resolve promise failed");
}
Err(e) => {
let status =
sys::napi_reject_deferred(raw_env, deferred, JsError::from(e).into_value(raw_env));
debug_assert!(status == sys::Status::napi_ok, "Reject promise failed");
}
};
}

100
crates/napi/src/status.rs Normal file
View file

@ -0,0 +1,100 @@
use std::fmt::{Display, Formatter, Result};
use crate::sys;
#[repr(i32)]
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub enum Status {
Ok = 0,
InvalidArg,
ObjectExpected,
StringExpected,
NameExpected,
FunctionExpected,
NumberExpected,
BooleanExpected,
ArrayExpected,
GenericFailure,
PendingException,
Cancelled,
EscapeCalledTwice,
HandleScopeMismatch,
CallbackScopeMismatch,
/// ThreadSafeFunction queue is full
QueueFull,
/// ThreadSafeFunction closed
Closing,
BigintExpected,
DateExpected,
ArrayBufferExpected,
DetachableArraybufferExpected,
WouldDeadlock,
Unknown = 1024, // unknown status. for example, using napi3 module in napi7 Node.js, and generate an invalid napi3 status
}
impl Display for Status {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let status_string = format!("{:?}", self);
write!(f, "{}", status_string)
}
}
impl From<i32> for Status {
fn from(code: i32) -> Self {
match code {
sys::Status::napi_ok => Status::Ok,
sys::Status::napi_invalid_arg => Status::InvalidArg,
sys::Status::napi_object_expected => Status::ObjectExpected,
sys::Status::napi_string_expected => Status::StringExpected,
sys::Status::napi_name_expected => Status::NameExpected,
sys::Status::napi_function_expected => Status::FunctionExpected,
sys::Status::napi_number_expected => Status::NumberExpected,
sys::Status::napi_boolean_expected => Status::BooleanExpected,
sys::Status::napi_array_expected => Status::ArrayExpected,
sys::Status::napi_generic_failure => Status::GenericFailure,
sys::Status::napi_pending_exception => Status::PendingException,
sys::Status::napi_cancelled => Status::Cancelled,
sys::Status::napi_escape_called_twice => Status::EscapeCalledTwice,
sys::Status::napi_handle_scope_mismatch => Status::HandleScopeMismatch,
sys::Status::napi_callback_scope_mismatch => Status::CallbackScopeMismatch,
sys::Status::napi_queue_full => Status::QueueFull,
sys::Status::napi_closing => Status::Closing,
sys::Status::napi_bigint_expected => Status::BigintExpected,
sys::Status::napi_date_expected => Status::DateExpected,
sys::Status::napi_arraybuffer_expected => Status::ArrayBufferExpected,
sys::Status::napi_detachable_arraybuffer_expected => Status::DetachableArraybufferExpected,
sys::Status::napi_would_deadlock => Status::WouldDeadlock,
_ => Status::Unknown,
}
}
}
impl From<Status> for i32 {
fn from(code: Status) -> Self {
match code {
Status::Ok => sys::Status::napi_ok,
Status::InvalidArg => sys::Status::napi_invalid_arg,
Status::ObjectExpected => sys::Status::napi_object_expected,
Status::StringExpected => sys::Status::napi_string_expected,
Status::NameExpected => sys::Status::napi_name_expected,
Status::FunctionExpected => sys::Status::napi_function_expected,
Status::NumberExpected => sys::Status::napi_number_expected,
Status::BooleanExpected => sys::Status::napi_boolean_expected,
Status::ArrayExpected => sys::Status::napi_array_expected,
Status::GenericFailure => sys::Status::napi_generic_failure,
Status::PendingException => sys::Status::napi_pending_exception,
Status::Cancelled => sys::Status::napi_cancelled,
Status::EscapeCalledTwice => sys::Status::napi_escape_called_twice,
Status::HandleScopeMismatch => sys::Status::napi_handle_scope_mismatch,
Status::CallbackScopeMismatch => sys::Status::napi_callback_scope_mismatch,
Status::QueueFull => sys::Status::napi_queue_full,
Status::Closing => sys::Status::napi_closing,
Status::BigintExpected => sys::Status::napi_bigint_expected,
Status::DateExpected => sys::Status::napi_date_expected,
Status::ArrayBufferExpected => sys::Status::napi_arraybuffer_expected,
Status::DetachableArraybufferExpected => sys::Status::napi_detachable_arraybuffer_expected,
Status::WouldDeadlock => sys::Status::napi_would_deadlock,
Status::Unknown => sys::Status::napi_generic_failure,
}
}
}

17
crates/napi/src/task.rs Normal file
View file

@ -0,0 +1,17 @@
use crate::{js_values::NapiValue, Env, Error, Result};
pub trait Task: Send + Sized {
type Output: Send + Sized + 'static;
type JsValue: NapiValue;
/// Compute logic in libuv thread
fn compute(&mut self) -> Result<Self::Output>;
/// Into this method if `compute` return `Ok`
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue>;
/// Into this method if `compute` return `Err`
fn reject(self, _env: Env, err: Error) -> Result<Self::JsValue> {
Err(err)
}
}

View file

@ -0,0 +1,544 @@
#![allow(clippy::single_component_path_imports)]
use std::convert::Into;
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use crate::{check_status, sys, Env, Error, JsError, JsFunction, NapiRaw, Result, Status};
use sys::napi_threadsafe_function_call_mode;
/// ThreadSafeFunction Context object
/// the `value` is the value passed to `call` method
pub struct ThreadSafeCallContext<T: 'static> {
pub env: Env,
pub value: T,
}
#[repr(u8)]
pub enum ThreadsafeFunctionCallMode {
NonBlocking,
Blocking,
}
impl From<ThreadsafeFunctionCallMode> for napi_threadsafe_function_call_mode {
fn from(value: ThreadsafeFunctionCallMode) -> Self {
match value {
ThreadsafeFunctionCallMode::Blocking => {
napi_threadsafe_function_call_mode::napi_tsfn_blocking
}
ThreadsafeFunctionCallMode::NonBlocking => {
napi_threadsafe_function_call_mode::napi_tsfn_nonblocking
}
}
}
}
type_level_enum! {
/// Type-level `enum` to express how to feed [`ThreadsafeFunction`] errors to
/// the inner [`JsFunction`].
///
/// ### Context
///
/// For callbacks that expect a `Result`-like kind of input, the convention is
/// to have the callback take an `error` parameter as its first parameter.
///
/// This way receiving a `Result<Args…>` can be modelled as follows:
///
/// - In case of `Err(error)`, feed that `error` entity as the first parameter
/// of the callback;
///
/// - Otherwise (in case of `Ok(_)`), feed `null` instead.
///
/// In pseudo-code:
///
/// ```rust,ignore
/// match result_args {
/// Ok(args) => {
/// let js_null = /* … */;
/// callback.call(
/// // this
/// None,
/// // args…
/// &iter::once(js_null).chain(args).collect::<Vec<_>>(),
/// )
/// },
/// Err(err) => callback.call(None, &[JsError::from(err)]),
/// }
/// ```
///
/// **Note that the `Err` case can stem from a failed conversion from native
/// values to js values when calling the callback!**
///
/// That's why:
///
/// > **[This][`ErrorStrategy::CalleeHandled`] is the default error strategy**.
///
/// In order to opt-out of it, [`ThreadsafeFunction`] has an optional second
/// generic parameter (of "kind" [`ErrorStrategy::T`]) that defines whether
/// this behavior ([`ErrorStrategy::CalleeHandled`]) or a non-`Result` one
/// ([`ErrorStrategy::Fatal`]) is desired.
pub enum ErrorStrategy {
/// Input errors (including conversion errors) are left for the callee to
/// handle:
///
/// The callee receives an extra `error` parameter (the first one), which is
/// `null` if no error occurred, and the error payload otherwise.
CalleeHandled,
/// Input errors (including conversion errors) are deemed fatal:
///
/// they can thus cause a `panic!` or abort the process.
///
/// The callee thus is not expected to have to deal with [that extra `error`
/// parameter][CalleeHandled], which is thus not added.
Fatal,
}
}
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
///
/// ## Example
/// An example of using `ThreadsafeFunction`:
///
/// ```rust
/// #[macro_use]
/// extern crate napi_derive;
///
/// use std::thread;
///
/// use napi::{
/// threadsafe_function::{
/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
/// },
/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
/// };
///
/// #[js_function(1)]
/// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
/// let func = ctx.get::<JsFunction>(0)?;
///
/// let tsfn =
/// ctx
/// .env
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
/// ctx.value
/// .iter()
/// .map(|v| ctx.env.create_uint32(*v))
/// .collect::<Result<Vec<JsNumber>>>()
/// })?;
///
/// let tsfn_cloned = tsfn.clone();
///
/// thread::spawn(move || {
/// let output: Vec<u32> = vec![0, 1, 2, 3];
/// // It's okay to call a threadsafe function multiple times.
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);
/// });
///
/// thread::spawn(move || {
/// let output: Vec<u32> = vec![3, 2, 1, 0];
/// // It's okay to call a threadsafe function multiple times.
/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
/// });
///
/// ctx.env.get_undefined()
/// }
/// ```
pub struct ThreadsafeFunction<T: 'static, ES: ErrorStrategy::T = ErrorStrategy::CalleeHandled> {
raw_tsfn: sys::napi_threadsafe_function,
aborted: Arc<AtomicBool>,
ref_count: Arc<AtomicUsize>,
_phantom: PhantomData<(T, ES)>,
}
impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
fn clone(&self) -> Self {
if !self.aborted.load(Ordering::Acquire) {
let acquire_status = unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) };
debug_assert!(
acquire_status == sys::Status::napi_ok,
"Acquire threadsafe function failed in clone"
);
}
Self {
raw_tsfn: self.raw_tsfn,
aborted: Arc::clone(&self.aborted),
ref_count: Arc::clone(&self.ref_count),
_phantom: PhantomData,
}
}
}
unsafe impl<T, ES: ErrorStrategy::T> Send for ThreadsafeFunction<T, ES> {}
unsafe impl<T, ES: ErrorStrategy::T> Sync for ThreadsafeFunction<T, ES> {}
impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
/// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
/// for more information.
pub fn create<
V: NapiRaw,
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
>(
env: sys::napi_env,
func: &JsFunction,
max_queue_size: usize,
callback: R,
) -> Result<Self> {
let mut async_resource_name = ptr::null_mut();
let s = "napi_rs_threadsafe_function";
let len = s.len();
let s = CString::new(s)?;
check_status!(unsafe {
sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name)
})?;
let initial_thread_count = 1usize;
let mut raw_tsfn = ptr::null_mut();
let ptr = Box::into_raw(Box::new(callback)) as *mut c_void;
check_status!(unsafe {
sys::napi_create_threadsafe_function(
env,
func.0.value,
ptr::null_mut(),
async_resource_name,
max_queue_size,
initial_thread_count,
ptr,
Some(thread_finalize_cb::<T, V, R>),
ptr,
Some(call_js_cb::<T, V, R, ES>),
&mut raw_tsfn,
)
})?;
Ok(ThreadsafeFunction {
raw_tsfn,
aborted: Arc::new(AtomicBool::new(false)),
ref_count: Arc::new(AtomicUsize::new(initial_thread_count)),
_phantom: PhantomData,
})
}
/// See [napi_ref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_ref_threadsafe_function)
/// for more information.
///
/// "ref" is a keyword so that we use "refer" here.
pub fn refer(&mut self, env: &Env) -> Result<()> {
if self.aborted.load(Ordering::Acquire) {
return Err(Error::new(
Status::Closing,
"Can not ref, Thread safe function already aborted".to_string(),
));
}
self.ref_count.fetch_add(1, Ordering::AcqRel);
check_status!(unsafe { sys::napi_ref_threadsafe_function(env.0, self.raw_tsfn) })
}
/// See [napi_unref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_unref_threadsafe_function)
/// for more information.
pub fn unref(&mut self, env: &Env) -> Result<()> {
if self.aborted.load(Ordering::Acquire) {
return Err(Error::new(
Status::Closing,
"Can not unref, Thread safe function already aborted".to_string(),
));
}
self.ref_count.fetch_sub(1, Ordering::AcqRel);
check_status!(unsafe { sys::napi_unref_threadsafe_function(env.0, self.raw_tsfn) })
}
pub fn aborted(&self) -> bool {
self.aborted.load(Ordering::Relaxed)
}
pub fn abort(self) -> Result<()> {
check_status!(unsafe {
sys::napi_release_threadsafe_function(
self.raw_tsfn,
sys::napi_threadsafe_function_release_mode::napi_tsfn_abort,
)
})?;
self.aborted.store(true, Ordering::Release);
Ok(())
}
/// Get the raw `ThreadSafeFunction` pointer
pub fn raw(&self) -> sys::napi_threadsafe_function {
self.raw_tsfn
}
}
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
/// for more information.
pub fn call(&self, value: Result<T>, mode: ThreadsafeFunctionCallMode) -> Status {
if self.aborted.load(Ordering::Acquire) {
return Status::Closing;
}
unsafe {
sys::napi_call_threadsafe_function(
self.raw_tsfn,
Box::into_raw(Box::new(value)) as *mut _,
mode.into(),
)
}
.into()
}
}
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
/// for more information.
pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {
if self.aborted.load(Ordering::Acquire) {
return Status::Closing;
}
unsafe {
sys::napi_call_threadsafe_function(
self.raw_tsfn,
Box::into_raw(Box::new(value)) as *mut _,
mode.into(),
)
}
.into()
}
}
impl<T: 'static, ES: ErrorStrategy::T> Drop for ThreadsafeFunction<T, ES> {
fn drop(&mut self) {
if !self.aborted.load(Ordering::Acquire) && self.ref_count.load(Ordering::Acquire) > 0usize {
let release_status = unsafe {
sys::napi_release_threadsafe_function(
self.raw_tsfn,
sys::napi_threadsafe_function_release_mode::napi_tsfn_release,
)
};
assert!(
release_status == sys::Status::napi_ok,
"Threadsafe Function release failed"
);
}
}
}
unsafe extern "C" fn thread_finalize_cb<T: 'static, V: NapiRaw, R>(
_raw_env: sys::napi_env,
finalize_data: *mut c_void,
_finalize_hint: *mut c_void,
) where
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
{
// cleanup
drop(Box::<R>::from_raw(finalize_data.cast()));
}
unsafe extern "C" fn call_js_cb<T: 'static, V: NapiRaw, R, ES>(
raw_env: sys::napi_env,
js_callback: sys::napi_value,
context: *mut c_void,
data: *mut c_void,
) where
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
ES: ErrorStrategy::T,
{
let ctx: &mut R = &mut *context.cast::<R>();
let val: Result<T> = match ES::VALUE {
ErrorStrategy::CalleeHandled::VALUE => *Box::<Result<T>>::from_raw(data.cast()),
ErrorStrategy::Fatal::VALUE => Ok(*Box::<T>::from_raw(data.cast())),
};
let mut recv = ptr::null_mut();
sys::napi_get_undefined(raw_env, &mut recv);
let ret = val.and_then(|v| {
(ctx)(ThreadSafeCallContext {
env: Env::from_raw(raw_env),
value: v,
})
});
let status;
// Follow async callback conventions: https://nodejs.org/en/knowledge/errors/what-are-the-error-conventions/
// Check if the Result is okay, if so, pass a null as the first (error) argument automatically.
// If the Result is an error, pass that as the first argument.
match ret {
Ok(values) => {
let values = values.iter().map(|v| v.raw());
let args: Vec<sys::napi_value> = if ES::VALUE == ErrorStrategy::CalleeHandled::VALUE {
let mut js_null = ptr::null_mut();
sys::napi_get_null(raw_env, &mut js_null);
::core::iter::once(js_null).chain(values).collect()
} else {
values.collect()
};
status = sys::napi_call_function(
raw_env,
recv,
js_callback,
args.len(),
args.as_ptr(),
ptr::null_mut(),
);
}
Err(e) if ES::VALUE == ErrorStrategy::Fatal::VALUE => {
panic!("{}", e);
}
Err(e) => {
status = sys::napi_call_function(
raw_env,
recv,
js_callback,
1,
[JsError::from(e).into_value(raw_env)].as_mut_ptr(),
ptr::null_mut(),
);
}
}
if status == sys::Status::napi_ok {
return;
}
if status == sys::Status::napi_pending_exception {
let mut error_result = ptr::null_mut();
assert_eq!(
sys::napi_get_and_clear_last_exception(raw_env, &mut error_result),
sys::Status::napi_ok
);
assert_eq!(
sys::napi_fatal_exception(raw_env, error_result),
sys::Status::napi_ok
);
} else {
let error_code: Status = status.into();
let error_code_string = format!("{:?}", error_code);
let mut error_code_value = ptr::null_mut();
assert_eq!(
sys::napi_create_string_utf8(
raw_env,
error_code_string.as_ptr() as *const _,
error_code_string.len(),
&mut error_code_value
),
sys::Status::napi_ok,
);
let error_msg = "Call JavaScript callback failed in thread safe function";
let mut error_msg_value = ptr::null_mut();
assert_eq!(
sys::napi_create_string_utf8(
raw_env,
error_msg.as_ptr() as *const _,
error_msg.len(),
&mut error_msg_value,
),
sys::Status::napi_ok,
);
let mut error_value = ptr::null_mut();
assert_eq!(
sys::napi_create_error(raw_env, error_code_value, error_msg_value, &mut error_value),
sys::Status::napi_ok,
);
assert_eq!(
sys::napi_fatal_exception(raw_env, error_value),
sys::Status::napi_ok
);
}
}
/// Helper
macro_rules! type_level_enum {(
$( #[doc = $doc:tt] )*
$pub:vis
enum $EnumName:ident {
$(
$( #[doc = $doc_variant:tt] )*
$Variant:ident
),* $(,)?
}
) => (type_level_enum! { // This requires the macro to be in scope when called.
with_docs! {
$( #[doc = $doc] )*
///
/// ### Type-level `enum`
///
/// Until `const_generics` can handle custom `enum`s, this pattern must be
/// implemented at the type level.
///
/// We thus end up with:
///
/// ```rust,ignore
/// #[type_level_enum]
#[doc = ::core::concat!(
" enum ", ::core::stringify!($EnumName), " {",
)]
$(
#[doc = ::core::concat!(
" ", ::core::stringify!($Variant), ",",
)]
)*
#[doc = " }"]
/// ```
///
#[doc = ::core::concat!(
"With [`", ::core::stringify!($EnumName), "::T`](#reexports) \
being the type-level \"enum type\":",
)]
///
/// ```rust,ignore
#[doc = ::core::concat!(
"<Param: ", ::core::stringify!($EnumName), "::T>"
)]
/// ```
}
#[allow(warnings)]
$pub mod $EnumName {
#[doc(no_inline)]
pub use $EnumName as T;
super::type_level_enum! {
with_docs! {
#[doc = ::core::concat!(
"See [`", ::core::stringify!($EnumName), "`]\
[super::", ::core::stringify!($EnumName), "]"
)]
}
pub trait $EnumName : __sealed::$EnumName + ::core::marker::Sized + 'static {
const VALUE: __value::$EnumName;
}
}
mod __sealed { pub trait $EnumName {} }
mod __value {
#[derive(Debug, PartialEq, Eq)]
pub enum $EnumName { $( $Variant ),* }
}
$(
$( #[doc = $doc_variant] )*
pub enum $Variant {}
impl __sealed::$EnumName for $Variant {}
impl $EnumName for $Variant {
const VALUE: __value::$EnumName = __value::$EnumName::$Variant;
}
impl $Variant {
pub const VALUE: __value::$EnumName = __value::$EnumName::$Variant;
}
)*
}
});(
with_docs! {
$( #[doc = $doc:expr] )*
}
$item:item
) => (
$( #[doc = $doc] )*
$item
)}
use type_level_enum;

View file

@ -0,0 +1,46 @@
use std::fmt::{Display, Formatter, Result};
use crate::sys;
#[repr(i32)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum ValueType {
Undefined = 0,
Null = 1,
Boolean = 2,
Number = 3,
String = 4,
Symbol = 5,
Object = 6,
Function = 7,
External = 8,
#[cfg(feature = "napi6")]
Bigint = 9,
Unknown = 1024,
}
impl Display for ValueType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let status_string = format!("{:?}", self);
write!(f, "{}", status_string)
}
}
impl From<i32> for ValueType {
fn from(value: i32) -> ValueType {
match value {
#[cfg(feature = "napi6")]
sys::ValueType::napi_bigint => ValueType::Bigint,
sys::ValueType::napi_boolean => ValueType::Boolean,
sys::ValueType::napi_external => ValueType::External,
sys::ValueType::napi_function => ValueType::Function,
sys::ValueType::napi_null => ValueType::Null,
sys::ValueType::napi_number => ValueType::Number,
sys::ValueType::napi_object => ValueType::Object,
sys::ValueType::napi_string => ValueType::String,
sys::ValueType::napi_symbol => ValueType::Symbol,
sys::ValueType::napi_undefined => ValueType::Undefined,
_ => ValueType::Unknown,
}
}
}

View file

@ -0,0 +1,29 @@
use crate::{sys, Error, Status};
use std::convert::TryFrom;
use std::ffi::CStr;
#[derive(Debug, Clone, Copy)]
pub struct NodeVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub release: &'static str,
}
impl TryFrom<sys::napi_node_version> for NodeVersion {
type Error = Error;
fn try_from(value: sys::napi_node_version) -> Result<NodeVersion, Error> {
Ok(NodeVersion {
major: value.major,
minor: value.minor,
patch: value.patch,
release: unsafe {
CStr::from_ptr(value.release).to_str().map_err(|_| Error {
status: Status::StringExpected,
reason: "Invalid release name".to_owned(),
})?
},
})
}
}

View file

@ -0,0 +1,79 @@
//! The following directly was copied from [neon][].
//!
//! Rust port of [win_delay_load_hook.cc][].
//!
//! When the addon tries to load the "node.exe" DLL module, this module gives it the pointer to the
//! .exe we are running in instead. Typically, that will be the same value. But if the node executable
//! was renamed, you would not otherwise get the correct DLL.
//!
//! [neon]: https://github.com/neon-bindings/neon/blob/5ffa2d282177b63094c46e92b20b8e850d122e65/src/win_delay_load_hook.rs
//! [win_delay_load_hook.cc]: https://github.com/nodejs/node-gyp/blob/e18a61afc1669d4897e6c5c8a6694f4995a0f4d6/src/win_delay_load_hook.cc
use std::ffi::CStr;
use std::ptr::null_mut;
use winapi::shared::minwindef::{BOOL, DWORD, FARPROC, HMODULE, LPVOID};
use winapi::shared::ntdef::LPCSTR;
use winapi::um::libloaderapi::GetModuleHandleA;
// Structures hand-copied from
// https://docs.microsoft.com/en-us/cpp/build/reference/structure-and-constant-definitions
#[repr(C)]
#[allow(non_snake_case)]
struct DelayLoadProc {
fImportByName: BOOL,
// Technically this is `union{LPCSTR; DWORD;}` but we don't access it anyways.
szProcName: LPCSTR,
}
#[repr(C)]
#[allow(non_snake_case)]
struct DelayLoadInfo {
/// size of structure
cb: DWORD,
/// raw form of data (everything is there)
/// Officially a pointer to ImgDelayDescr but we don't access it.
pidd: LPVOID,
/// points to address of function to load
ppfn: *mut FARPROC,
/// name of dll
szDll: LPCSTR,
/// name or ordinal of procedure
dlp: DelayLoadProc,
/// the hInstance of the library we have loaded
hmodCur: HMODULE,
/// the actual function that will be called
pfnCur: FARPROC,
/// error received (if an error notification)
dwLastError: DWORD,
}
#[allow(non_snake_case)]
type PfnDliHook = unsafe extern "C" fn(dliNotify: usize, pdli: *const DelayLoadInfo) -> FARPROC;
const HOST_BINARIES: &[&[u8]] = &[b"node.exe", b"electron.exe"];
unsafe extern "C" fn load_exe_hook(event: usize, info: *const DelayLoadInfo) -> FARPROC {
if event != 0x01
/* dliNotePreLoadLibrary */
{
return null_mut();
}
let dll_name = CStr::from_ptr((*info).szDll);
if !HOST_BINARIES
.iter()
.any(|&host_name| host_name == dll_name.to_bytes())
{
return null_mut();
}
let exe_handle = GetModuleHandleA(null_mut());
// PfnDliHook sometimes has to return a FARPROC, sometimes an HMODULE, but only one
// of them could be specified in the header, so we have to cast our HMODULE to that.
exe_handle as FARPROC
}
#[no_mangle]
static mut __pfnDliNotifyHook2: *mut PfnDliHook = load_exe_hook as *mut PfnDliHook;