Introduce #[napi]
procedural macro to automation development boilerplate (#696)
* 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:
parent
b64677aaad
commit
2467b7139b
203 changed files with 5308 additions and 1500 deletions
crates/napi
Cargo.tomlREADME.md
src
async_cleanup_hook.rsasync_work.rs
bindgen_runtime
call_context.rscleanup_env.rsenv.rserror.rsjs_values
arraybuffer.rsbigint.rsboolean.rsbuffer.rsdate.rsde.rseither.rsescapable_handle_scope.rsfunction.rsglobal.rsmod.rsnumber.rsobject.rsobject_property.rsser.rs
lib.rspromise.rsstatus.rstask.rsthreadsafe_function.rsvalue_type.rsversion.rswin_delay_load_hook.rsstring
tagged_object.rsundefined.rsvalue.rsvalue_ref.rs
56
crates/napi/Cargo.toml
Normal file
56
crates/napi/Cargo.toml
Normal 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
228
crates/napi/README.md
Normal 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>
|
||||
|
||||
|
||||
<a href="https://swc.rs/" target="_blank">
|
||||
<img alt="swc" src="https://raw.githubusercontent.com/swc-project/logo/master/swc.png" height="50px">
|
||||
</a>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
|
||||
<img alt="nextjs.svg" src="./images/nextjs.svg" height="50px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Platform Support
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml)
|
||||
[](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 |
|
28
crates/napi/src/async_cleanup_hook.rs
Normal file
28
crates/napi/src/async_cleanup_hook.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
119
crates/napi/src/async_work.rs
Normal file
119
crates/napi/src/async_work.rs
Normal 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"
|
||||
);
|
||||
}
|
118
crates/napi/src/bindgen_runtime/callback_info.rs
Normal file
118
crates/napi/src/bindgen_runtime/callback_info.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
22
crates/napi/src/bindgen_runtime/env.rs
Normal file
22
crates/napi/src/bindgen_runtime/env.rs
Normal 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)
|
||||
}
|
||||
}
|
10
crates/napi/src/bindgen_runtime/error.rs
Normal file
10
crates/napi/src/bindgen_runtime/error.rs
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
145
crates/napi/src/bindgen_runtime/js_values.rs
Normal file
145
crates/napi/src/bindgen_runtime/js_values.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
172
crates/napi/src/bindgen_runtime/js_values/array.rs
Normal file
172
crates/napi/src/bindgen_runtime/js_values/array.rs
Normal 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]
|
||||
}
|
||||
}
|
39
crates/napi/src/bindgen_runtime/js_values/boolean.rs
Normal file
39
crates/napi/src/bindgen_runtime/js_values/boolean.rs
Normal 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)
|
||||
}
|
||||
}
|
99
crates/napi/src/bindgen_runtime/js_values/buffer.rs
Normal file
99
crates/napi/src/bindgen_runtime/js_values/buffer.rs
Normal 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]
|
||||
}
|
||||
}
|
100
crates/napi/src/bindgen_runtime/js_values/nil.rs
Normal file
100
crates/napi/src/bindgen_runtime/js_values/nil.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
crates/napi/src/bindgen_runtime/js_values/number.rs
Normal file
58
crates/napi/src/bindgen_runtime/js_values/number.rs
Normal 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),
|
||||
);
|
106
crates/napi/src/bindgen_runtime/js_values/object.rs
Normal file
106
crates/napi/src/bindgen_runtime/js_values/object.rs
Normal 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(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
235
crates/napi/src/bindgen_runtime/js_values/string.rs
Normal file
235
crates/napi/src/bindgen_runtime/js_values/string.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
50
crates/napi/src/bindgen_runtime/mod.rs
Normal file
50
crates/napi/src/bindgen_runtime/mod.rs
Normal 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));
|
||||
}
|
141
crates/napi/src/bindgen_runtime/module_register.rs
Normal file
141
crates/napi/src/bindgen_runtime/module_register.rs
Normal 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
|
||||
}
|
97
crates/napi/src/call_context.rs
Normal file
97
crates/napi/src/call_context.rs
Normal 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) }
|
||||
}
|
||||
}
|
9
crates/napi/src/cleanup_env.rs
Normal file
9
crates/napi/src/cleanup_env.rs
Normal 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
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
227
crates/napi/src/error.rs
Normal 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)*))),
|
||||
}
|
||||
}};
|
||||
}
|
285
crates/napi/src/js_values/arraybuffer.rs
Normal file
285
crates/napi/src/js_values/arraybuffer.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
249
crates/napi/src/js_values/bigint.rs
Normal file
249
crates/napi/src/js_values/bigint.rs
Normal 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))
|
||||
}
|
||||
}
|
24
crates/napi/src/js_values/boolean.rs
Normal file
24
crates/napi/src/js_values/boolean.rs
Normal 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()
|
||||
}
|
||||
}
|
91
crates/napi/src/js_values/buffer.rs
Normal file
91
crates/napi/src/js_values/buffer.rs
Normal 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()
|
||||
}
|
||||
}
|
12
crates/napi/src/js_values/date.rs
Normal file
12
crates/napi/src/js_values/date.rs
Normal 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)
|
||||
}
|
||||
}
|
364
crates/napi/src/js_values/de.rs
Normal file
364
crates/napi/src/js_values/de.rs
Normal 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)
|
||||
}
|
||||
}
|
37
crates/napi/src/js_values/either.rs
Normal file
37
crates/napi/src/js_values/either.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
37
crates/napi/src/js_values/escapable_handle_scope.rs
Normal file
37
crates/napi/src/js_values/escapable_handle_scope.rs
Normal 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
|
||||
}
|
||||
}
|
109
crates/napi/src/js_values/function.rs
Normal file
109
crates/napi/src/js_values/function.rs
Normal 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) })
|
||||
}
|
||||
}
|
54
crates/napi/src/js_values/global.rs
Normal file
54
crates/napi/src/js_values/global.rs
Normal 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())
|
||||
}
|
||||
}
|
642
crates/napi/src/js_values/mod.rs
Normal file
642
crates/napi/src/js_values/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
74
crates/napi/src/js_values/number.rs
Normal file
74
crates/napi/src/js_values/number.rs
Normal 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)
|
||||
}
|
||||
}
|
185
crates/napi/src/js_values/object.rs
Normal file
185
crates/napi/src/js_values/object.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
96
crates/napi/src/js_values/object_property.rs
Normal file
96
crates/napi/src/js_values/object_property.rs
Normal 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
|
||||
}
|
||||
}
|
487
crates/napi/src/js_values/ser.rs
Normal file
487
crates/napi/src/js_values/ser.rs
Normal 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)
|
||||
}
|
||||
}
|
46
crates/napi/src/js_values/string/latin1.rs
Normal file
46
crates/napi/src/js_values/string/latin1.rs
Normal 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()
|
||||
}
|
||||
}
|
108
crates/napi/src/js_values/string/mod.rs
Normal file
108
crates/napi/src/js_values/string/mod.rs
Normal 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)
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
63
crates/napi/src/js_values/string/utf16.rs
Normal file
63
crates/napi/src/js_values/string/utf16.rs
Normal 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()
|
||||
}
|
||||
}
|
57
crates/napi/src/js_values/string/utf8.rs
Normal file
57
crates/napi/src/js_values/string/utf8.rs
Normal 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()
|
||||
}
|
||||
}
|
16
crates/napi/src/js_values/tagged_object.rs
Normal file
16
crates/napi/src/js_values/tagged_object.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
4
crates/napi/src/js_values/undefined.rs
Normal file
4
crates/napi/src/js_values/undefined.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
use super::Value;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct JsUndefined(pub(crate) Value);
|
10
crates/napi/src/js_values/value.rs
Normal file
10
crates/napi/src/js_values/value.rs
Normal 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,
|
||||
}
|
63
crates/napi/src/js_values/value_ref.rs
Normal file
63
crates/napi/src/js_values/value_ref.rs
Normal 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
161
crates/napi/src/lib.rs
Normal 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
118
crates/napi/src/promise.rs
Normal 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
100
crates/napi/src/status.rs
Normal 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
17
crates/napi/src/task.rs
Normal 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)
|
||||
}
|
||||
}
|
544
crates/napi/src/threadsafe_function.rs
Normal file
544
crates/napi/src/threadsafe_function.rs
Normal 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;
|
46
crates/napi/src/value_type.rs
Normal file
46
crates/napi/src/value_type.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
29
crates/napi/src/version.rs
Normal file
29
crates/napi/src/version.rs
Normal 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(),
|
||||
})?
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
79
crates/napi/src/win_delay_load_hook.rs
Normal file
79
crates/napi/src/win_delay_load_hook.rs
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue