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
|
@ -196,11 +196,12 @@ overrides:
|
|||
parserOptions:
|
||||
project: ./tsconfig.json
|
||||
- files:
|
||||
- ./test_module/**/*.{ts,js}
|
||||
- ./examples/**/*.{ts,js}
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
parserOptions:
|
||||
project: ./test_module/tsconfig.json
|
||||
project:
|
||||
- ./examples/tsconfig.json
|
||||
|
||||
- files:
|
||||
- ./bench/**/*.{ts,js}
|
||||
|
|
2
.github/workflows/linux-aarch64-musl.yaml
vendored
2
.github/workflows/linux-aarch64-musl.yaml
vendored
|
@ -77,7 +77,7 @@ jobs:
|
|||
|
||||
- name: Cross build native tests
|
||||
run: |
|
||||
docker run -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/napi-rs -w /napi-rs -e CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc builder yarn --cwd ./test_module build --target aarch64-unknown-linux-musl
|
||||
docker run -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/napi-rs -w /napi-rs -e CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc builder sh -c "yarn --cwd ./examples/napi-compat-mode build --target aarch64-unknown-linux-musl && yarn --cwd ./examples/napi build --target aarch64-unknown-linux-musl"
|
||||
|
||||
- name: Setup and run tests
|
||||
uses: docker://multiarch/alpine:aarch64-latest-stable
|
||||
|
|
3
.github/workflows/napi3.yaml
vendored
3
.github/workflows/napi3.yaml
vendored
|
@ -70,7 +70,8 @@ jobs:
|
|||
|
||||
- name: Unit tests
|
||||
run: |
|
||||
yarn --cwd ./test_module --ignore-engines build-napi3
|
||||
yarn --cwd ./examples/napi-compat-mode --ignore-engines build-napi3
|
||||
yarn --cwd ./examples/napi --ignore-engines build-napi3
|
||||
yarn --ignore-engines test
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
|
5
.github/workflows/windows-i686.yml
vendored
5
.github/workflows/windows-i686.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
|||
- name: Install
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.51.0
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
|
@ -72,7 +72,8 @@ jobs:
|
|||
|
||||
- name: Build Tests
|
||||
run: |
|
||||
yarn --cwd ./test_module build-i686
|
||||
yarn --cwd ./examples/napi-compat-mode build-i686
|
||||
yarn --cwd ./examples/napi build-i686
|
||||
yarn test
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -1,11 +1,12 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"./build",
|
||||
"./napi",
|
||||
"./napi-derive",
|
||||
"./napi-derive-example",
|
||||
"./sys",
|
||||
"./test_module",
|
||||
"./crates/backend",
|
||||
"./crates/build",
|
||||
"./crates/macro",
|
||||
"./crates/napi",
|
||||
"./crates/sys",
|
||||
"./examples/napi",
|
||||
"./examples/napi-compat-mode",
|
||||
"./bench",
|
||||
"./memory-testing",
|
||||
]
|
||||
|
|
166
README.md
166
README.md
|
@ -81,52 +81,36 @@ One nice feature is that this crate allows you to build add-ons purely with the
|
|||
### Define JavaScript functions
|
||||
|
||||
```rust
|
||||
#[js_function(1)] // ------> arguments length
|
||||
fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
|
||||
let n = ctx.get::<JsNumber>(0)?.try_into()?;
|
||||
ctx.env.create_int64(fibonacci_native(n))
|
||||
}
|
||||
#[macro_use]
|
||||
extern crate napi;
|
||||
|
||||
#[inline(always)]
|
||||
fn fibonacci_native(n: i64) -> i64 {
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Register module
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
use napi::{JsObject, Result};
|
||||
|
||||
/// `exports` is `module.exports` object in NodeJS
|
||||
#[module_exports]
|
||||
fn init(mut exports: JsObject) -> Result<()> {
|
||||
exports.create_named_method("fibonacci", fibonacci)?;
|
||||
Ok(())
|
||||
/// 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<()>
|
||||
{}
|
||||
```
|
||||
|
||||
And you can also create `JavaScript` value while registering module:
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
use napi::{JsObject, Result, Env};
|
||||
|
||||
#[module_exports]
|
||||
fn init(mut exports: JsObject, env: Env) -> Result<()> {
|
||||
exports.create_named_method("fibonacci", fibonacci)?;
|
||||
exports.set_named_property("DEFAULT_VALUE", env.create_int64(100)?)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
Checkout more examples in [examples](./examples) folder
|
||||
|
||||
## Building
|
||||
|
||||
|
@ -142,8 +126,8 @@ name = "awesome"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
napi = "1"
|
||||
napi-derive = "1"
|
||||
napi = "2"
|
||||
napi-derive = "2"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "1"
|
||||
|
@ -162,8 +146,6 @@ fn main() {
|
|||
|
||||
So far, the `napi` build script has only been tested on `macOS` `Linux` `Windows x64 MSVC` and `FreeBSD`.
|
||||
|
||||
See the included [test_module](./test_module) for an example add-on.
|
||||
|
||||
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
|
||||
|
@ -221,84 +203,26 @@ yarn test
|
|||
|
||||
## Features table
|
||||
|
||||
### Create JavaScript values
|
||||
|
||||
| NAPI | NAPI Version | Minimal Node version | Status |
|
||||
| ------------------------------------------------------------------------------------------------------------ | ------------ | -------------------- | ------ |
|
||||
| [napi_create_array](https://nodejs.org/api/n-api.html#n_api_napi_create_array) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_array_with_length](https://nodejs.org/api/n-api.html#n_api_napi_create_array_with_length) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_arraybuffer) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_buffer](https://nodejs.org/api/n-api.html#n_api_napi_create_buffer) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_buffer_copy](https://nodejs.org/api/n-api.html#n_api_napi_create_buffer_copy) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_date](https://nodejs.org/api/n-api.html#n_api_napi_create_date) | 5 | v11.11.0 | ✅ |
|
||||
| [napi_create_external](https://nodejs.org/api/n-api.html#n_api_napi_create_external) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_external_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_arraybuffer) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_external_buffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_buffer) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_object](https://nodejs.org/api/n-api.html#n_api_napi_create_object) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_symbol](https://nodejs.org/api/n-api.html#n_api_napi_create_symbol) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_typedarray](https://nodejs.org/api/n-api.html#n_api_napi_create_typedarray) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_dataview](https://nodejs.org/api/n-api.html#n_api_napi_create_dataview) | 1 | v8.3.0 | ✅ |
|
||||
| [napi_create_int32](https://nodejs.org/api/n-api.html#n_api_napi_create_int32) | 1 | v8.4.0 | ✅ |
|
||||
| [napi_create_uint32](https://nodejs.org/api/n-api.html#n_api_napi_create_uint32) | 1 | v8.4.0 | ✅ |
|
||||
| [napi_create_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_int64) | 1 | v8.4.0 | ✅ |
|
||||
| [napi_create_double](https://nodejs.org/api/n-api.html#n_api_napi_create_double) | 1 | v8.4.0 | ✅ |
|
||||
| [napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64) | 6 | v10.7.0 | ✅ |
|
||||
| [napi_create_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_uint64) | 6 | v10.7.0 | ✅ |
|
||||
| [napi_create_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words) | 6 | v10.7.0 | ✅ |
|
||||
| [napi_create_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_create_string_latin1) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf16) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_create_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf8) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_type_tag](https://nodejs.org/api/n-api.html#n_api_napi_type_tag) | 8 | v14.8.0, v12.19.0 | ⚠️ |
|
||||
|
||||
> I have no plan to implement `nape_type_tag` and related API in `napi-rs`, because we have implemented a `rust` replacement in [TaggedObject](https://github.com/napi-rs/napi-rs/blob/main/napi/src/js_values/tagged_object.rs) which is more convenient and more compatible.
|
||||
|
||||
### [Functions to convert from Node-API to C types](https://nodejs.org/api/n-api.html#n_api_functions_to_convert_from_node_api_to_c_types)
|
||||
|
||||
| NAPI | NAPI Version | Minimal Node Version | Status |
|
||||
| ---------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ------ |
|
||||
| [napi_get_array_length](https://nodejs.org/api/n-api.html#n_api_napi_get_array_length) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_arraybuffer_info](https://nodejs.org/api/n-api.html#n_api_napi_get_arraybuffer_info) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_buffer_info](https://nodejs.org/api/n-api.html#n_api_napi_get_buffer_info) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_prototype](https://nodejs.org/api/n-api.html#n_api_napi_get_prototype) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_typedarray_info](https://nodejs.org/api/n-api.html#n_api_napi_get_typedarray_info) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_dataview_info](https://nodejs.org/api/n-api.html#n_api_napi_get_dataview_info) | 1 | v8.3.0 | ✅ |
|
||||
| [napi_get_date_value](https://nodejs.org/api/n-api.html#n_api_napi_get_date_value) | 5 | v11.11.0 | ✅ |
|
||||
| [napi_get_value_bool](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bool) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_double](https://nodejs.org/api/n-api.html#n_api_napi_get_value_double) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_int64) | 6 | v10.7.0 | ✅ |
|
||||
| [napi_get_value_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_uint64) | 6 | v10.7.0 | ✅ |
|
||||
| [napi_get_value_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words) | 6 | v10.7.0 | ✅ |
|
||||
| [napi_get_value_external](https://nodejs.org/api/n-api.html#n_api_napi_get_value_external) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_int32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int32) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int64) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_latin1) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf8) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf16) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_value_uint32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_uint32) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_boolean](https://nodejs.org/api/n-api.html#n_api_napi_get_boolean) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_global](https://nodejs.org/api/n-api.html#n_api_napi_get_global) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_null](https://nodejs.org/api/n-api.html#n_api_napi_get_null) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_get_undefined](https://nodejs.org/api/n-api.html#n_api_napi_get_undefined) | 1 | v8.0.0 | ✅ |
|
||||
|
||||
### [Working with JavaScript Values and Abstract Operations](https://nodejs.org/api/n-api.html#n_api_working_with_javascript_values_and_abstract_operations)
|
||||
|
||||
| NAPI | NAPI Version | Minimal Node Version | Status |
|
||||
| ---------------------------------------------------------------------------------------------------- | ------------ | -------------------- | ------ |
|
||||
| [napi_coerce_to_bool](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_bool) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_coerce_to_number](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_number) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_coerce_to_object](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_object) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_coerce_to_string](https://nodejs.org/api/n-api.html#n_api_napi_coerce_to_string) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_typeof](https://nodejs.org/api/n-api.html#n_api_napi_typeof) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_instanceof](https://nodejs.org/api/n-api.html#n_api_napi_instanceof) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_array](https://nodejs.org/api/n-api.html#n_api_napi_is_array) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_arraybuffer) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_buffer](https://nodejs.org/api/n-api.html#n_api_napi_is_buffer) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_date](https://nodejs.org/api/n-api.html#n_api_napi_is_date) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_error](https://nodejs.org/api/n-api.html#n_api_napi_is_error_1) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_typedarray](https://nodejs.org/api/n-api.html#n_api_napi_is_typedarray) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_is_dataview](https://nodejs.org/api/n-api.html#n_api_napi_is_dataview) | 1 | v8.3.0 | ✅ |
|
||||
| [napi_strict_equals](https://nodejs.org/api/n-api.html#n_api_napi_strict_equals) | 1 | v8.0.0 | ✅ |
|
||||
| [napi_detach_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_detach_arraybuffer) | 7 | v13.3.0 | ✅ |
|
||||
| [napi_is_detached_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_detached_arraybuffer) | 7 | v13.3.0 | ✅ |
|
||||
| [napi_object_freeze](https://nodejs.org/api/n-api.html#n_api_napi_object_freeze) | 8 | v14.14.0, v12.20.0 | ✅ |
|
||||
| [napi_object_seal](https://nodejs.org/api/n-api.html#n_api_napi_object_seal) | 8 | v14.14.0, v12.20.0 | ✅ |
|
||||
| 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 |
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const configuration = {
|
||||
extensions: ['ts', 'tsx'],
|
||||
files: ['test_module/__test__/**/*.spec.ts', 'cli/__test__/**/*.spec.ts'],
|
||||
files: ['examples/**/__test__/**/*.spec.ts'],
|
||||
require: ['ts-node/register/transpile-only'],
|
||||
environmentVariables: {
|
||||
TS_NODE_PROJECT: './test_module/tsconfig.json',
|
||||
TS_NODE_PROJECT: './examples/tsconfig.json',
|
||||
},
|
||||
timeout: '1m',
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ version = "0.1.0"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
napi = {path = "../napi", features = ["tokio_rt", "serde-json"]}
|
||||
napi-derive = {path = "../napi-derive"}
|
||||
napi = {path = "../crates/napi", features = ["tokio_rt", "serde-json", "compat-mode"]}
|
||||
napi-derive = {path = "../crates/macro", features = ["compat-mode"]}
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
|
||||
|
@ -17,4 +17,4 @@ serde_json = "1"
|
|||
mimalloc = {version = "0.1"}
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = {path = "../build"}
|
||||
napi-build = {path = "../crates/build"}
|
||||
|
|
|
@ -44,16 +44,16 @@ pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> {
|
|||
"TestClass",
|
||||
test_class_constructor,
|
||||
&[
|
||||
Property::new(env, "miterNative")?
|
||||
Property::new("miterNative")?
|
||||
.with_getter(get_miter_native)
|
||||
.with_setter(set_miter_native),
|
||||
Property::new(env, "miter")?
|
||||
Property::new("miter")?
|
||||
.with_getter(get_miter)
|
||||
.with_setter(set_miter),
|
||||
Property::new(env, "lineJoinNative")?
|
||||
Property::new("lineJoinNative")?
|
||||
.with_getter(get_line_join_native)
|
||||
.with_setter(set_line_join_native),
|
||||
Property::new(env, "lineJoin")?
|
||||
Property::new("lineJoin")?
|
||||
.with_getter(get_line_join)
|
||||
.with_setter(set_line_join),
|
||||
],
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
mkdirAsync,
|
||||
readFileAsync,
|
||||
unlinkAsync,
|
||||
writeFileAsync,
|
||||
} from './utils'
|
||||
|
||||
const debug = debugFactory('build')
|
||||
|
@ -50,7 +51,7 @@ export class BuildCommand extends Command {
|
|||
? join(process.cwd(), this.cargoCwd)
|
||||
: process.cwd()
|
||||
const releaseFlag = this.isRelease ? `--release` : ''
|
||||
const targetFLag = this.targetTripleDir
|
||||
const targetFlag = this.targetTripleDir
|
||||
? `--target ${this.targetTripleDir}`
|
||||
: ''
|
||||
const featuresFlag = this.features ? `--features ${this.features}` : ''
|
||||
|
@ -64,16 +65,20 @@ export class BuildCommand extends Command {
|
|||
debug(`Current triple is: ${chalk.green(triple.raw)}`)
|
||||
const externalFlags = [
|
||||
releaseFlag,
|
||||
targetFLag,
|
||||
targetFlag,
|
||||
featuresFlag,
|
||||
this.cargoFlags,
|
||||
]
|
||||
.filter((flag) => Boolean(flag))
|
||||
.join(' ')
|
||||
const cargoCommand = `cargo build ${externalFlags}`
|
||||
const intermediateTypeFile = join(__dirname, `type_def.${Date.now()}.tmp`)
|
||||
debug(`Run ${chalk.green(cargoCommand)}`)
|
||||
execSync(cargoCommand, {
|
||||
env: process.env,
|
||||
env: {
|
||||
...process.env,
|
||||
TYPE_DEF_TMP_PATH: intermediateTypeFile,
|
||||
},
|
||||
stdio: 'inherit',
|
||||
cwd,
|
||||
})
|
||||
|
@ -190,6 +195,11 @@ export class BuildCommand extends Command {
|
|||
|
||||
debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`)
|
||||
await copyFileAsync(sourcePath, distModulePath)
|
||||
|
||||
await processIntermediateTypeFile(
|
||||
intermediateTypeFile,
|
||||
join(this.destDir ?? '.', 'type.d.ts'),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,3 +215,60 @@ async function findUp(dir = process.cwd()): Promise<string | null> {
|
|||
dirs.pop()
|
||||
return findUp(dirs.join(sep))
|
||||
}
|
||||
|
||||
interface TypeDef {
|
||||
kind: 'fn' | 'struct' | 'impl' | 'enum'
|
||||
name: string
|
||||
def: string
|
||||
}
|
||||
|
||||
async function processIntermediateTypeFile(source: string, target: string) {
|
||||
if (!(await existsAsync(source))) {
|
||||
debug(`do not find tmp type file. skip type generation`)
|
||||
return
|
||||
}
|
||||
|
||||
const tmpFile = await readFileAsync(source, 'utf8')
|
||||
const lines = tmpFile
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
let dts = ''
|
||||
const classes = new Map<string, string>()
|
||||
const impls = new Map<string, string>()
|
||||
|
||||
lines.forEach((line) => {
|
||||
const def = JSON.parse(line) as TypeDef
|
||||
|
||||
switch (def.kind) {
|
||||
case 'fn':
|
||||
case 'enum':
|
||||
dts += def.def + '\n'
|
||||
break
|
||||
case 'struct':
|
||||
classes.set(def.name, def.def)
|
||||
break
|
||||
case 'impl':
|
||||
impls.set(def.name, def.def)
|
||||
}
|
||||
})
|
||||
|
||||
for (const [name, def] of impls.entries()) {
|
||||
const classDef = classes.get(name)
|
||||
|
||||
dts += `export class ${name} {
|
||||
${(classDef ?? '')
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.join('\n ')}
|
||||
${def
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.join('\n ')}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
await unlinkAsync(source)
|
||||
await writeFileAsync(target, dts, 'utf8')
|
||||
}
|
||||
|
|
22
crates/backend/Cargo.toml
Normal file
22
crates/backend/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "napi-derive-backend"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
type-def = ["regex", "once_cell"]
|
||||
strict = []
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = {version = "1.0", features = ["fold", "full"]}
|
||||
convert_case = "0.4.0"
|
||||
|
||||
[dependencies.regex]
|
||||
optional = true
|
||||
version = "1"
|
||||
|
||||
[dependencies.once_cell]
|
||||
optional = true
|
||||
version = "1"
|
3
crates/backend/README.md
Normal file
3
crates/backend/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# napi-derive-backend
|
||||
|
||||
Take care the ast parsing from `napi-derive` and generate "bridge" runtime code for both nodejs and rust.
|
85
crates/backend/src/ast.rs
Normal file
85
crates/backend/src/ast.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use proc_macro2::Ident;
|
||||
use syn::Attribute;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiFn {
|
||||
pub name: Ident,
|
||||
pub js_name: String,
|
||||
pub attrs: Vec<Attribute>,
|
||||
pub args: Vec<NapiFnArgKind>,
|
||||
pub ret: Option<syn::Type>,
|
||||
pub is_async: bool,
|
||||
pub fn_self: Option<FnSelf>,
|
||||
pub kind: FnKind,
|
||||
pub vis: syn::Visibility,
|
||||
pub parent: Option<Ident>,
|
||||
pub strict: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CallbackArg {
|
||||
pub pat: Box<syn::Pat>,
|
||||
pub args: Vec<syn::Type>,
|
||||
pub ret: Option<syn::Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NapiFnArgKind {
|
||||
PatType(Box<syn::PatType>),
|
||||
Callback(Box<CallbackArg>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FnKind {
|
||||
Normal,
|
||||
Constructor,
|
||||
Getter,
|
||||
Setter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FnSelf {
|
||||
Value,
|
||||
Ref,
|
||||
MutRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiStruct {
|
||||
pub name: Ident,
|
||||
pub js_name: String,
|
||||
pub vis: syn::Visibility,
|
||||
pub fields: Vec<NapiStructField>,
|
||||
pub is_tuple: bool,
|
||||
pub gen_default_ctor: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiStructField {
|
||||
pub name: syn::Member,
|
||||
pub js_name: String,
|
||||
pub ty: syn::Type,
|
||||
pub getter: bool,
|
||||
pub setter: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiImpl {
|
||||
pub name: Ident,
|
||||
pub js_name: String,
|
||||
pub items: Vec<NapiFn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiEnum {
|
||||
pub name: Ident,
|
||||
pub js_name: String,
|
||||
pub variants: Vec<NapiEnumVariant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NapiEnumVariant {
|
||||
pub name: Ident,
|
||||
pub val: i32,
|
||||
pub comments: Vec<String>,
|
||||
}
|
28
crates/backend/src/codegen.rs
Normal file
28
crates/backend/src/codegen.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
|
||||
use crate::BindgenResult;
|
||||
|
||||
mod r#enum;
|
||||
mod r#fn;
|
||||
mod r#struct;
|
||||
|
||||
pub trait TryToTokens {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>;
|
||||
|
||||
fn try_to_token_stream(&self) -> BindgenResult<TokenStream> {
|
||||
let mut tokens = TokenStream::default();
|
||||
self.try_to_tokens(&mut tokens)?;
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_intermediate_ident(name: &str) -> Ident {
|
||||
let new_name = format!("__napi__{}", name);
|
||||
Ident::new(&new_name, Span::call_site())
|
||||
}
|
||||
|
||||
fn get_register_ident(name: &str) -> Ident {
|
||||
let new_name = format!("__napi_register__{}", name);
|
||||
Ident::new(&new_name, Span::call_site())
|
||||
}
|
134
crates/backend/src/codegen/enum.rs
Normal file
134
crates/backend/src/codegen/enum.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::ToTokens;
|
||||
|
||||
use crate::{codegen::get_register_ident, BindgenResult, NapiEnum, TryToTokens};
|
||||
|
||||
impl TryToTokens for NapiEnum {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
let register = self.gen_module_register();
|
||||
let napi_value_conversion = self.gen_napi_value_map_impl();
|
||||
|
||||
(quote! {
|
||||
#napi_value_conversion
|
||||
#register
|
||||
})
|
||||
.to_tokens(tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiEnum {
|
||||
fn gen_napi_value_map_impl(&self) -> TokenStream {
|
||||
let name = &self.name;
|
||||
let name_str = self.name.to_string();
|
||||
let mut from_napi_branches = vec![];
|
||||
let mut to_napi_branches = vec![];
|
||||
|
||||
self.variants.iter().for_each(|v| {
|
||||
let val = Literal::i32_unsuffixed(v.val);
|
||||
let v_name = &v.name;
|
||||
|
||||
from_napi_branches.push(quote! { #val => Ok(#name::#v_name) });
|
||||
to_napi_branches.push(quote! { #name::#v_name => #val });
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl TypeName for #name {
|
||||
fn type_name() -> &'static str {
|
||||
#name_str
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidateNapiValue for #name {
|
||||
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<()> {
|
||||
assert_type_of!(env, napi_val, ValueType::Number)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNapiValue for #name {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
let val = i32::from_napi_value(env, napi_val).map_err(|e| {
|
||||
error!(
|
||||
e.status,
|
||||
"Failed to convert napi value into enum `{}`. {}",
|
||||
#name_str,
|
||||
e,
|
||||
)
|
||||
})?;
|
||||
|
||||
match val {
|
||||
#(#from_napi_branches,)*
|
||||
_ => {
|
||||
Err(error!(
|
||||
Status::InvalidArg,
|
||||
"value `{}` does not match any variant of enum `{}`",
|
||||
val,
|
||||
#name_str
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNapiValue for #name {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||
let val = match val {
|
||||
#(#to_napi_branches,)*
|
||||
};
|
||||
|
||||
i32::to_napi_value(env, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_module_register(&self) -> TokenStream {
|
||||
let name_str = self.name.to_string();
|
||||
let js_name_lit = Literal::string(&self.js_name);
|
||||
let register_name = get_register_ident(&name_str);
|
||||
|
||||
let mut define_properties = vec![];
|
||||
|
||||
for variant in self.variants.iter() {
|
||||
let name_lit = Literal::string(&variant.name.to_string());
|
||||
let val_lit = Literal::i32_unsuffixed(variant.val);
|
||||
|
||||
define_properties.push(quote! {
|
||||
{
|
||||
let name = CString::new(#name_lit).unwrap();
|
||||
check_status!(
|
||||
sys::napi_set_named_property(env, obj_ptr, name.as_ptr(), i32::to_napi_value(env, #val_lit)?),
|
||||
"Failed to defined enum `{}`",
|
||||
#js_name_lit
|
||||
)?;
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
#[ctor]
|
||||
fn #register_name() {
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
unsafe fn cb(env: sys::napi_env) -> Result<sys::napi_value> {
|
||||
let mut obj_ptr = ptr::null_mut();
|
||||
|
||||
check_status!(
|
||||
sys::napi_create_object(env, &mut obj_ptr),
|
||||
"Failed to create napi object"
|
||||
)?;
|
||||
|
||||
#(#define_properties)*
|
||||
|
||||
Ok(obj_ptr)
|
||||
}
|
||||
|
||||
register_module_export(#js_name_lit, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
259
crates/backend/src/codegen/fn.rs
Normal file
259
crates/backend/src/codegen/fn.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
|
||||
use crate::{
|
||||
codegen::{get_intermediate_ident, get_register_ident},
|
||||
BindgenResult, CallbackArg, FnKind, FnSelf, NapiFn, NapiFnArgKind, TryToTokens,
|
||||
};
|
||||
|
||||
impl TryToTokens for NapiFn {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
let name_str = self.name.to_string();
|
||||
let intermediate_ident = get_intermediate_ident(&name_str);
|
||||
let args_len = self.args.len();
|
||||
|
||||
let (arg_conversions, arg_names) = self.gen_arg_conversions();
|
||||
let receiver = self.gen_fn_receiver();
|
||||
let receiver_ret_name = Ident::new("_ret", Span::call_site());
|
||||
let ret = self.gen_fn_return(&receiver_ret_name);
|
||||
let register = self.gen_fn_register();
|
||||
|
||||
let attrs = &self.attrs;
|
||||
|
||||
(quote! {
|
||||
#(#attrs)*
|
||||
#[doc(hidden)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
extern "C" fn #intermediate_ident(
|
||||
env: sys::napi_env,
|
||||
cb: sys::napi_callback_info
|
||||
) -> sys::napi_value {
|
||||
unsafe {
|
||||
CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| {
|
||||
#(#arg_conversions)*
|
||||
let #receiver_ret_name = {
|
||||
#receiver(#(#arg_names),*)
|
||||
};
|
||||
|
||||
#ret
|
||||
}).unwrap_or_else(|e| {
|
||||
JsError::from(e).throw_into(env);
|
||||
std::ptr::null_mut::<sys::napi_value__>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#register
|
||||
})
|
||||
.to_tokens(tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiFn {
|
||||
fn gen_arg_conversions(&self) -> (Vec<TokenStream>, Vec<TokenStream>) {
|
||||
let mut arg_conversions = vec![];
|
||||
let mut args = vec![];
|
||||
|
||||
// fetch this
|
||||
if let Some(parent) = &self.parent {
|
||||
match self.fn_self {
|
||||
Some(FnSelf::Ref) => {
|
||||
arg_conversions.push(quote! { let this = cb.unwrap_borrow::<#parent>()?; });
|
||||
}
|
||||
Some(FnSelf::MutRef) => {
|
||||
arg_conversions.push(quote! { let this = cb.unwrap_borrow_mut::<#parent>()?; });
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
let mut skipped_arg_count = 0;
|
||||
self.args.iter().enumerate().for_each(|(i, arg)| {
|
||||
let i = i - skipped_arg_count;
|
||||
let ident = Ident::new(&format!("arg{}", i), Span::call_site());
|
||||
|
||||
match arg {
|
||||
NapiFnArgKind::PatType(path) => {
|
||||
if &path.ty.to_token_stream().to_string() == "Env" {
|
||||
args.push(quote! { Env::from(env) });
|
||||
skipped_arg_count += 1;
|
||||
} else {
|
||||
arg_conversions.push(self.gen_ty_arg_conversion(&ident, i, path));
|
||||
args.push(quote! { #ident });
|
||||
}
|
||||
}
|
||||
NapiFnArgKind::Callback(cb) => {
|
||||
arg_conversions.push(self.gen_cb_arg_conversion(&ident, i, cb));
|
||||
args.push(quote! { #ident });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(arg_conversions, args)
|
||||
}
|
||||
|
||||
fn gen_ty_arg_conversion(
|
||||
&self,
|
||||
arg_name: &Ident,
|
||||
index: usize,
|
||||
path: &syn::PatType,
|
||||
) -> TokenStream {
|
||||
let ty = &*path.ty;
|
||||
match ty {
|
||||
syn::Type::Reference(syn::TypeReference {
|
||||
mutability: Some(_),
|
||||
elem,
|
||||
..
|
||||
}) => {
|
||||
quote! {
|
||||
let #arg_name = unsafe { <#elem as FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))? };
|
||||
}
|
||||
}
|
||||
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
|
||||
quote! {
|
||||
let #arg_name = unsafe { <#elem as FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))? };
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let type_check = if self.strict {
|
||||
quote! {
|
||||
<#ty as ValidateNapiValue>::validate(env, cb.get_arg(#index))?;
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
quote! {
|
||||
let #arg_name = unsafe {
|
||||
#type_check
|
||||
<#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_cb_arg_conversion(&self, arg_name: &Ident, index: usize, cb: &CallbackArg) -> TokenStream {
|
||||
let mut inputs = vec![];
|
||||
let mut arg_conversions = vec![];
|
||||
|
||||
for (i, ty) in cb.args.iter().enumerate() {
|
||||
let cb_arg_ident = Ident::new(&format!("callback_arg_{}", i), Span::call_site());
|
||||
inputs.push(quote! { #cb_arg_ident: #ty });
|
||||
arg_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #cb_arg_ident)? });
|
||||
}
|
||||
|
||||
let ret = match &cb.ret {
|
||||
Some(ty) => {
|
||||
quote! {
|
||||
let ret = <#ty as FromNapiValue>::from_napi_value(env, ret_ptr)?;
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
None => quote! { Ok(()) },
|
||||
};
|
||||
|
||||
quote! {
|
||||
assert_type_of!(env, cb.get_arg(#index), ValueType::Function)?;
|
||||
let #arg_name = |#(#inputs),*| {
|
||||
let args = vec![
|
||||
#(#arg_conversions),*
|
||||
];
|
||||
|
||||
let mut ret_ptr = std::ptr::null_mut();
|
||||
|
||||
check_status!(
|
||||
sys::napi_call_function(
|
||||
env,
|
||||
cb.this(),
|
||||
cb.get_arg(#index),
|
||||
args.len(),
|
||||
args.as_ptr(),
|
||||
&mut ret_ptr
|
||||
),
|
||||
"Failed to call napi callback",
|
||||
)?;
|
||||
|
||||
#ret
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_fn_receiver(&self) -> TokenStream {
|
||||
let name = &self.name;
|
||||
|
||||
match self.fn_self {
|
||||
Some(FnSelf::Value) => {
|
||||
// impossible, errord in parser
|
||||
unimplemented!();
|
||||
}
|
||||
Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name },
|
||||
None => match &self.parent {
|
||||
Some(class) => quote! { #class::#name },
|
||||
None => quote! { #name },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_fn_return(&self, ret: &Ident) -> TokenStream {
|
||||
let js_name = &self.js_name;
|
||||
let ret_ty = &self.ret;
|
||||
|
||||
if self.kind == FnKind::Constructor {
|
||||
quote! { cb.construct(#js_name, #ret) }
|
||||
} else if let Some(ref ty) = ret_ty {
|
||||
quote! {
|
||||
<#ty as ToNapiValue>::to_napi_value(env, #ret)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
<() as ToNapiValue>::to_napi_value(env, ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_fn_register(&self) -> TokenStream {
|
||||
if self.parent.is_some() {
|
||||
quote! {}
|
||||
} else {
|
||||
let name_str = self.name.to_string();
|
||||
let name_len = name_str.len();
|
||||
let js_name = &self.js_name;
|
||||
let module_register_name = get_register_ident(&name_str);
|
||||
let intermediate_ident = get_intermediate_ident(&name_str);
|
||||
|
||||
quote! {
|
||||
#[allow(clippy::all)]
|
||||
#[allow(non_snake_case)]
|
||||
#[ctor]
|
||||
fn #module_register_name() {
|
||||
unsafe fn cb(env: sys::napi_env) -> Result<sys::napi_value> {
|
||||
let mut fn_ptr = std::ptr::null_mut();
|
||||
let js_name = std::ffi::CString::new(#js_name).unwrap();
|
||||
|
||||
check_status!(
|
||||
sys::napi_create_function(
|
||||
env,
|
||||
js_name.as_ptr(),
|
||||
#name_len,
|
||||
Some(#intermediate_ident),
|
||||
std::ptr::null_mut(),
|
||||
&mut fn_ptr,
|
||||
),
|
||||
"Failed to register function `{}`",
|
||||
#name_str,
|
||||
)?;
|
||||
|
||||
Ok(fn_ptr)
|
||||
}
|
||||
|
||||
register_module_export(#js_name, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
386
crates/backend/src/codegen/struct.rs
Normal file
386
crates/backend/src/codegen/struct.rs
Normal file
|
@ -0,0 +1,386 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||
use quote::ToTokens;
|
||||
|
||||
use crate::{
|
||||
codegen::{get_intermediate_ident, get_register_ident},
|
||||
BindgenResult, FnKind, NapiImpl, NapiStruct, TryToTokens,
|
||||
};
|
||||
|
||||
// Generate trait implementations for given Struct.
|
||||
fn gen_napi_value_map_impl(name: &Ident, to_napi_val_impl: TokenStream) -> TokenStream {
|
||||
let name_str = name.to_string();
|
||||
quote! {
|
||||
impl TypeName for #name {
|
||||
fn type_name() -> &'static str {
|
||||
#name_str
|
||||
}
|
||||
}
|
||||
|
||||
#to_napi_val_impl
|
||||
|
||||
impl FromNapiRef for #name {
|
||||
unsafe fn from_napi_ref(env: sys::napi_env, napi_val: sys::napi_value) -> Result<&'static Self> {
|
||||
let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut();
|
||||
|
||||
check_status!(
|
||||
sys::napi_unwrap(env, napi_val, &mut wrapped_val),
|
||||
"Failed to recover `{}` type from napi value",
|
||||
#name_str,
|
||||
)?;
|
||||
|
||||
Ok(&*(wrapped_val as *const #name))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNapiMutRef for #name {
|
||||
unsafe fn from_napi_mut_ref(env: sys::napi_env, napi_val: sys::napi_value) -> Result<&'static mut Self> {
|
||||
let mut wrapped_val: *mut std::ffi::c_void = std::ptr::null_mut();
|
||||
|
||||
check_status!(
|
||||
sys::napi_unwrap(env, napi_val, &mut wrapped_val),
|
||||
"Failed to recover `{}` type from napi value",
|
||||
#name_str,
|
||||
)?;
|
||||
|
||||
Ok(&mut *(wrapped_val as *mut #name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryToTokens for NapiStruct {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
let napi_value_map_impl = self.gen_napi_value_map_impl();
|
||||
let class_helper_mod = self.gen_helper_mod();
|
||||
|
||||
(quote! {
|
||||
#napi_value_map_impl
|
||||
#class_helper_mod
|
||||
})
|
||||
.to_tokens(tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiStruct {
|
||||
fn gen_helper_mod(&self) -> TokenStream {
|
||||
let mod_name = Ident::new(
|
||||
&format!("__napi_helper__{}", self.name.to_string()),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
let ctor = if self.gen_default_ctor {
|
||||
self.gen_default_ctor()
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let getters_setters = self.gen_default_getters_setters();
|
||||
let register = self.gen_register();
|
||||
|
||||
quote! {
|
||||
#[allow(clippy::all)]
|
||||
#[allow(non_snake_case)]
|
||||
mod #mod_name {
|
||||
use std::ptr;
|
||||
use super::*;
|
||||
|
||||
#ctor
|
||||
#(#getters_setters)*
|
||||
#register
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_default_ctor(&self) -> TokenStream {
|
||||
let name = &self.name;
|
||||
let js_name_str = &self.js_name;
|
||||
let fields_len = self.fields.len();
|
||||
let mut fields = vec![];
|
||||
|
||||
for (i, field) in self.fields.iter().enumerate() {
|
||||
let ty = &field.ty;
|
||||
match &field.name {
|
||||
syn::Member::Named(ident) => fields
|
||||
.push(quote! { #ident: <#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? }),
|
||||
syn::Member::Unnamed(_) => {
|
||||
fields.push(quote! { <#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#i))? });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let construct = if self.is_tuple {
|
||||
quote! { #name (#(#fields),*) }
|
||||
} else {
|
||||
quote! { #name {#(#fields),*} }
|
||||
};
|
||||
|
||||
quote! {
|
||||
extern "C" fn constructor(
|
||||
env: sys::napi_env,
|
||||
cb: sys::napi_callback_info
|
||||
) -> sys::napi_value {
|
||||
CallbackInfo::<#fields_len>::new(env, cb, None)
|
||||
.and_then(|cb| unsafe { cb.construct(#js_name_str, #construct) })
|
||||
.unwrap_or_else(|e| {
|
||||
unsafe { JsError::from(e).throw_into(env) };
|
||||
std::ptr::null_mut::<sys::napi_value__>()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_napi_value_map_impl(&self) -> TokenStream {
|
||||
if !self.gen_default_ctor {
|
||||
return gen_napi_value_map_impl(&self.name, quote! {});
|
||||
}
|
||||
|
||||
let name = &self.name;
|
||||
let js_name_str = &self.js_name;
|
||||
|
||||
let mut fields_conversions = vec![];
|
||||
let mut field_destructions = vec![];
|
||||
|
||||
for field in self.fields.iter() {
|
||||
let ty = &field.ty;
|
||||
|
||||
match &field.name {
|
||||
syn::Member::Named(ident) => {
|
||||
field_destructions.push(quote! { #ident });
|
||||
fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #ident)? });
|
||||
}
|
||||
syn::Member::Unnamed(i) => {
|
||||
field_destructions.push(quote! { arg#i });
|
||||
fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, arg#i)? });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let destructed_fields = if self.is_tuple {
|
||||
quote! {
|
||||
let Self (#(#field_destructions),*)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let Self {#(#field_destructions),*}
|
||||
}
|
||||
};
|
||||
|
||||
gen_napi_value_map_impl(
|
||||
name,
|
||||
quote! {
|
||||
impl ToNapiValue for #name {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result<sys::napi_value> {
|
||||
if let Some(ctor_ref) = get_class_constructor(#js_name_str) {
|
||||
let mut ctor = std::ptr::null_mut();
|
||||
|
||||
check_status!(
|
||||
sys::napi_get_reference_value(env, ctor_ref, &mut ctor),
|
||||
"Failed to get constructor of class `{}`",
|
||||
#js_name_str
|
||||
)?;
|
||||
|
||||
let mut result = std::ptr::null_mut();
|
||||
#destructed_fields = val;
|
||||
let args = vec![#(#fields_conversions),*];
|
||||
|
||||
check_status!(
|
||||
sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result),
|
||||
"Failed to construct class `{}`",
|
||||
#js_name_str
|
||||
)?;
|
||||
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(Error::new(Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str)))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn gen_default_getters_setters(&self) -> Vec<TokenStream> {
|
||||
let mut getters_setters = vec![];
|
||||
let struct_name = &self.name;
|
||||
|
||||
for field in self.fields.iter() {
|
||||
let field_ident = &field.name;
|
||||
let field_name = match &field.name {
|
||||
syn::Member::Named(ident) => ident.to_string(),
|
||||
syn::Member::Unnamed(i) => format!("field{}", i.index),
|
||||
};
|
||||
let ty = &field.ty;
|
||||
|
||||
let getter_name = Ident::new(&format!("get_{}", field_name), Span::call_site());
|
||||
let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site());
|
||||
|
||||
if field.getter {
|
||||
getters_setters.push(quote! {
|
||||
extern "C" fn #getter_name(
|
||||
env: sys::napi_env,
|
||||
cb: sys::napi_callback_info
|
||||
) -> sys::napi_value {
|
||||
CallbackInfo::<0>::new(env, cb, Some(0))
|
||||
.and_then(|mut cb| unsafe { cb.unwrap_borrow::<#struct_name>() })
|
||||
.and_then(|obj| {
|
||||
let val = obj.#field_ident.to_owned();
|
||||
unsafe { <#ty as ToNapiValue>::to_napi_value(env, val) }
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
unsafe { JsError::from(e).throw_into(env) };
|
||||
std::ptr::null_mut::<sys::napi_value__>()
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if field.setter {
|
||||
getters_setters.push(quote! {
|
||||
extern "C" fn #setter_name(
|
||||
env: sys::napi_env,
|
||||
cb: sys::napi_callback_info
|
||||
) -> sys::napi_value {
|
||||
CallbackInfo::<1>::new(env, cb, Some(1))
|
||||
.and_then(|mut cb_info| unsafe {
|
||||
cb_info.unwrap_borrow_mut::<#struct_name>()
|
||||
.and_then(|obj| {
|
||||
<#ty as FromNapiValue>::from_napi_value(env, cb_info.get_arg(0))
|
||||
.and_then(move |val| {
|
||||
obj.#field_ident = val;
|
||||
<() as ToNapiValue>::to_napi_value(env, ())
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
unsafe { JsError::from(e).throw_into(env) };
|
||||
std::ptr::null_mut::<sys::napi_value__>()
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getters_setters
|
||||
}
|
||||
|
||||
fn gen_register(&self) -> TokenStream {
|
||||
let name_str = self.name.to_string();
|
||||
let struct_register_name = get_register_ident(&format!("{}_struct", name_str));
|
||||
let js_name = &self.js_name;
|
||||
let mut props = vec![];
|
||||
|
||||
if self.gen_default_ctor {
|
||||
props.push(quote! { Property::new("constructor").unwrap().with_ctor(constructor) });
|
||||
}
|
||||
|
||||
for field in self.fields.iter() {
|
||||
let field_name = match &field.name {
|
||||
syn::Member::Named(ident) => ident.to_string(),
|
||||
syn::Member::Unnamed(i) => format!("field{}", i.index),
|
||||
};
|
||||
|
||||
if !field.getter {
|
||||
continue;
|
||||
}
|
||||
|
||||
let js_name = &field.js_name;
|
||||
let mut prop = quote! {
|
||||
Property::new(#js_name)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
if field.getter {
|
||||
let getter_name = Ident::new(&format!("get_{}", field_name), Span::call_site());
|
||||
(quote! { .with_getter(#getter_name) }).to_tokens(&mut prop);
|
||||
}
|
||||
|
||||
if field.setter {
|
||||
let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site());
|
||||
(quote! { .with_setter(#setter_name) }).to_tokens(&mut prop);
|
||||
}
|
||||
|
||||
props.push(prop);
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
#[ctor]
|
||||
fn #struct_register_name() {
|
||||
register_class(#name_str, #js_name, vec![#(#props),*]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryToTokens for NapiImpl {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
self.gen_helper_mod()?.to_tokens(tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiImpl {
|
||||
fn gen_helper_mod(&self) -> BindgenResult<TokenStream> {
|
||||
let name_str = self.name.to_string();
|
||||
let js_name = &self.js_name;
|
||||
let mod_name = Ident::new(
|
||||
&format!("__napi_impl_helper__{}", name_str),
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
let register_name = get_register_ident(&format!("{}_impl", name_str));
|
||||
|
||||
let mut methods = vec![];
|
||||
let mut props = HashMap::new();
|
||||
|
||||
for item in self.items.iter() {
|
||||
let js_name = Literal::string(&item.js_name);
|
||||
let item_str = item.name.to_string();
|
||||
let intermediate_name = get_intermediate_ident(&item_str);
|
||||
methods.push(item.try_to_token_stream()?);
|
||||
|
||||
let prop = props.entry(&item.js_name).or_insert_with(|| {
|
||||
quote! {
|
||||
Property::new(#js_name).unwrap()
|
||||
}
|
||||
});
|
||||
|
||||
let appendix = match item.kind {
|
||||
FnKind::Constructor => quote! { .with_ctor(#intermediate_name) },
|
||||
FnKind::Getter => quote! { .with_getter(#intermediate_name) },
|
||||
FnKind::Setter => quote! { .with_setter(#intermediate_name) },
|
||||
_ => {
|
||||
if item.fn_self.is_some() {
|
||||
quote! { .with_method(#intermediate_name) }
|
||||
} else {
|
||||
quote! { .with_method(#intermediate_name).with_property_attributes(PropertyAttributes::Static) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
appendix.to_tokens(prop);
|
||||
}
|
||||
|
||||
let props: Vec<_> = props.values().collect();
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::all)]
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#(#methods)*
|
||||
|
||||
#[ctor]
|
||||
fn #register_name() {
|
||||
register_class(#name_str, #js_name, vec![#(#props),*]);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
136
crates/backend/src/error.rs
Normal file
136
crates/backend/src/error.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use proc_macro2::*;
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use syn::parse::Error;
|
||||
|
||||
/// Provide a Diagnostic with the given span and message
|
||||
#[macro_export]
|
||||
macro_rules! err_span {
|
||||
($span:expr, $($msg:tt)*) => (
|
||||
$crate::Diagnostic::spanned_error(&$span, format!($($msg)*))
|
||||
)
|
||||
}
|
||||
|
||||
/// Immediately fail and return an Err, with the arguments passed to err_span!
|
||||
#[macro_export]
|
||||
macro_rules! bail_span {
|
||||
($($t:tt)*) => (
|
||||
return Err(err_span!($($t)*).into())
|
||||
)
|
||||
}
|
||||
|
||||
/// A struct representing a diagnostic to emit to the end-user as an error.
|
||||
#[derive(Debug)]
|
||||
pub struct Diagnostic {
|
||||
inner: Repr,
|
||||
}
|
||||
|
||||
pub type BindgenResult<T> = Result<T, Diagnostic>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Repr {
|
||||
Single {
|
||||
text: String,
|
||||
span: Option<(Span, Span)>,
|
||||
},
|
||||
SynError(Error),
|
||||
Multi {
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Generate a `Diagnostic` from an informational message with no Span
|
||||
pub fn error<T: Into<String>>(text: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::Single {
|
||||
text: text.into(),
|
||||
span: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `Diagnostic` from a Span and an informational message
|
||||
pub fn span_error<T: Into<String>>(span: Span, text: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::Single {
|
||||
text: text.into(),
|
||||
span: Some((span, span)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `Diagnostic` from the span of any tokenizable object and a message
|
||||
pub fn spanned_error<T: Into<String>>(node: &dyn ToTokens, text: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::Single {
|
||||
text: text.into(),
|
||||
span: extract_spans(node),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to generate a `Diagnostic` from a vector of other `Diagnostic` instances.
|
||||
/// If the `Vec` is empty, returns `Ok(())`, otherwise returns the new `Diagnostic`
|
||||
pub fn from_vec(diagnostics: Vec<Diagnostic>) -> BindgenResult<()> {
|
||||
if diagnostics.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Diagnostic {
|
||||
inner: Repr::Multi { diagnostics },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Immediately trigger a panic from this `Diagnostic`
|
||||
#[allow(unconditional_recursion)]
|
||||
pub fn panic(&self) -> ! {
|
||||
match &self.inner {
|
||||
Repr::Single { text, .. } => panic!("{}", text),
|
||||
Repr::SynError(error) => panic!("{}", error),
|
||||
Repr::Multi { diagnostics } => diagnostics[0].panic(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Diagnostic {
|
||||
fn from(err: Error) -> Diagnostic {
|
||||
Diagnostic {
|
||||
inner: Repr::SynError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> {
|
||||
let mut t = TokenStream::new();
|
||||
node.to_tokens(&mut t);
|
||||
let mut tokens = t.into_iter();
|
||||
let start = tokens.next().map(|t| t.span());
|
||||
let end = tokens.last().map(|t| t.span());
|
||||
start.map(|start| (start, end.unwrap_or(start)))
|
||||
}
|
||||
|
||||
impl ToTokens for Diagnostic {
|
||||
fn to_tokens(&self, dst: &mut TokenStream) {
|
||||
match &self.inner {
|
||||
Repr::Single { text, span } => {
|
||||
let cs2 = (Span::call_site(), Span::call_site());
|
||||
let (start, end) = span.unwrap_or(cs2);
|
||||
dst.append(Ident::new("compile_error", start));
|
||||
dst.append(Punct::new('!', Spacing::Alone));
|
||||
let mut message = TokenStream::new();
|
||||
message.append(Literal::string(text));
|
||||
let mut group = Group::new(Delimiter::Brace, message);
|
||||
group.set_span(end);
|
||||
dst.append(group);
|
||||
}
|
||||
Repr::Multi { diagnostics } => {
|
||||
for diagnostic in diagnostics {
|
||||
diagnostic.to_tokens(dst);
|
||||
}
|
||||
}
|
||||
Repr::SynError(err) => {
|
||||
err.to_compile_error().to_tokens(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
crates/backend/src/lib.rs
Normal file
56
crates/backend/src/lib.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
#[macro_use]
|
||||
extern crate quote;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
pub mod ast;
|
||||
pub mod codegen;
|
||||
#[cfg(feature = "type-def")]
|
||||
pub mod typegen;
|
||||
|
||||
pub use ast::*;
|
||||
pub use codegen::*;
|
||||
pub use error::{BindgenResult, Diagnostic};
|
||||
#[cfg(feature = "type-def")]
|
||||
pub use typegen::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Napi {
|
||||
pub comments: Vec<String>,
|
||||
pub item: NapiItem,
|
||||
}
|
||||
|
||||
macro_rules! napi_ast_impl {
|
||||
( $( ($v:ident, $ast:ident), )* ) => {
|
||||
#[derive(Debug)]
|
||||
pub enum NapiItem {
|
||||
$($v($ast)),*
|
||||
}
|
||||
|
||||
impl TryToTokens for Napi {
|
||||
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
|
||||
match self.item {
|
||||
$( NapiItem::$v(ref ast) => ast.try_to_tokens(tokens) ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "type-def")]
|
||||
impl ToTypeDef for Napi {
|
||||
fn to_type_def(&self) -> TypeDef {
|
||||
match self.item {
|
||||
$( NapiItem::$v(ref ast) => ast.to_type_def() ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
napi_ast_impl! {
|
||||
(Fn, NapiFn),
|
||||
(Struct, NapiStruct),
|
||||
(Impl, NapiImpl),
|
||||
(Enum, NapiEnum),
|
||||
}
|
78
crates/backend/src/typegen.rs
Normal file
78
crates/backend/src/typegen.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
mod r#enum;
|
||||
mod r#fn;
|
||||
mod r#struct;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use quote::ToTokens;
|
||||
use regex::Regex;
|
||||
use syn::Type;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TypeDef {
|
||||
pub kind: String,
|
||||
pub name: String,
|
||||
pub def: String,
|
||||
}
|
||||
|
||||
impl ToString for TypeDef {
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
r#"{{"kind": "{}", "name": "{}", "def": "{}"}}"#,
|
||||
self.kind, self.name, self.def,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToTypeDef {
|
||||
fn to_type_def(&self) -> TypeDef;
|
||||
}
|
||||
|
||||
pub static VEC_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^Vec < (.*) >$").unwrap());
|
||||
pub static OPTION_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^Option < (.*) >").unwrap());
|
||||
|
||||
pub fn ty_to_ts_type(ty: &Type) -> String {
|
||||
match ty {
|
||||
Type::Reference(r) => ty_to_ts_type(&r.elem),
|
||||
Type::Tuple(tuple) => {
|
||||
format!(
|
||||
"[{}]",
|
||||
tuple
|
||||
.elems
|
||||
.iter()
|
||||
.map(|elem| ty_to_ts_type(elem))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
Type::Path(syn::TypePath { qself: None, path }) => {
|
||||
str_to_ts_type(path.to_token_stream().to_string().as_str())
|
||||
}
|
||||
|
||||
_ => "any".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str_to_ts_type(ty: &str) -> String {
|
||||
match ty {
|
||||
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" => "number".to_owned(),
|
||||
"i128" | "isize" | "u64" | "u128" | "usize" => "BigInt".to_owned(),
|
||||
"bool" => "boolean".to_owned(),
|
||||
"String" | "str" | "char" => "string".to_owned(),
|
||||
"Object" => "object".to_owned(),
|
||||
// nothing but `& 'lifetime str` could ends with ` str`
|
||||
s if s.ends_with(" str") => "string".to_owned(),
|
||||
s if s.starts_with("Vec") && VEC_REGEX.is_match(s) => {
|
||||
let captures = VEC_REGEX.captures(s).unwrap();
|
||||
let inner = captures.get(1).unwrap().as_str();
|
||||
|
||||
format!("Array<{}>", str_to_ts_type(inner))
|
||||
}
|
||||
s if s.starts_with("Option") && OPTION_REGEX.is_match(s) => {
|
||||
let captures = OPTION_REGEX.captures(s).unwrap();
|
||||
let inner = captures.get(1).unwrap().as_str();
|
||||
|
||||
format!("{} | undefined", str_to_ts_type(inner))
|
||||
}
|
||||
s => s.to_owned(),
|
||||
}
|
||||
}
|
27
crates/backend/src/typegen/enum.rs
Normal file
27
crates/backend/src/typegen/enum.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use super::{ToTypeDef, TypeDef};
|
||||
use crate::NapiEnum;
|
||||
|
||||
impl ToTypeDef for NapiEnum {
|
||||
fn to_type_def(&self) -> TypeDef {
|
||||
TypeDef {
|
||||
kind: "enum".to_owned(),
|
||||
name: self.js_name.to_owned(),
|
||||
def: format!(
|
||||
r"export enum {js_name} {{ {variants} }}",
|
||||
js_name = &self.js_name,
|
||||
variants = self.gen_ts_variants()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiEnum {
|
||||
fn gen_ts_variants(&self) -> String {
|
||||
self
|
||||
.variants
|
||||
.iter()
|
||||
.map(|v| format!("{} = {}", v.name, v.val))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
}
|
104
crates/backend/src/typegen/fn.rs
Normal file
104
crates/backend/src/typegen/fn.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use convert_case::{Case, Casing};
|
||||
use quote::ToTokens;
|
||||
|
||||
use super::{ty_to_ts_type, ToTypeDef, TypeDef};
|
||||
use crate::{CallbackArg, FnKind, NapiFn};
|
||||
|
||||
impl ToTypeDef for NapiFn {
|
||||
fn to_type_def(&self) -> TypeDef {
|
||||
let def = format!(
|
||||
r#"{prefix} {name}({args}){ret}"#,
|
||||
prefix = self.gen_ts_func_prefix(),
|
||||
name = &self.js_name,
|
||||
args = self.gen_ts_func_args(),
|
||||
ret = self.gen_ts_func_ret(),
|
||||
);
|
||||
|
||||
TypeDef {
|
||||
kind: "fn".to_owned(),
|
||||
name: self.js_name.clone(),
|
||||
def,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_callback_type(callback: &CallbackArg) -> String {
|
||||
format!(
|
||||
"({args}) => {ret}",
|
||||
args = &callback
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, arg)| { format!("arg{}: {}", i, ty_to_ts_type(arg)) })
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
ret = match &callback.ret {
|
||||
Some(ty) => ty_to_ts_type(ty),
|
||||
None => "void".to_owned(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl NapiFn {
|
||||
fn gen_ts_func_args(&self) -> String {
|
||||
self
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
crate::NapiFnArgKind::PatType(path) => {
|
||||
if path.ty.to_token_stream().to_string() == "Env" {
|
||||
return None;
|
||||
}
|
||||
let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel);
|
||||
arg.push_str(": ");
|
||||
arg.push_str(&ty_to_ts_type(&path.ty));
|
||||
|
||||
Some(arg)
|
||||
}
|
||||
crate::NapiFnArgKind::Callback(cb) => {
|
||||
let mut arg = cb.pat.to_token_stream().to_string().to_case(Case::Camel);
|
||||
arg.push_str(": ");
|
||||
arg.push_str(&gen_callback_type(cb));
|
||||
|
||||
Some(arg)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn gen_ts_func_prefix(&self) -> &'static str {
|
||||
if self.parent.is_some() {
|
||||
match self.kind {
|
||||
crate::FnKind::Normal => match self.fn_self {
|
||||
Some(_) => "",
|
||||
None => "static",
|
||||
},
|
||||
crate::FnKind::Constructor => "constructor",
|
||||
crate::FnKind::Getter => "get",
|
||||
crate::FnKind::Setter => "set",
|
||||
}
|
||||
} else {
|
||||
"export function"
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_ts_func_ret(&self) -> String {
|
||||
match self.kind {
|
||||
FnKind::Constructor | FnKind::Setter => "".to_owned(),
|
||||
_ => {
|
||||
let ret = if let Some(ref ret) = self.ret {
|
||||
ty_to_ts_type(ret)
|
||||
} else {
|
||||
"void".to_owned()
|
||||
};
|
||||
|
||||
if self.is_async {
|
||||
format!(": Promise<{}>", ret)
|
||||
} else {
|
||||
format!(": {}", ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
crates/backend/src/typegen/struct.rs
Normal file
59
crates/backend/src/typegen/struct.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use super::{ToTypeDef, TypeDef};
|
||||
use crate::{ty_to_ts_type, NapiImpl, NapiStruct};
|
||||
|
||||
impl ToTypeDef for NapiStruct {
|
||||
fn to_type_def(&self) -> TypeDef {
|
||||
TypeDef {
|
||||
kind: "struct".to_owned(),
|
||||
name: self.js_name.to_owned(),
|
||||
def: self.gen_ts_class_fields(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTypeDef for NapiImpl {
|
||||
fn to_type_def(&self) -> TypeDef {
|
||||
TypeDef {
|
||||
kind: "impl".to_owned(),
|
||||
name: self.js_name.to_owned(),
|
||||
def: self
|
||||
.items
|
||||
.iter()
|
||||
.map(|f| f.to_type_def().def)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\\n"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiStruct {
|
||||
fn gen_ts_class_fields(&self) -> String {
|
||||
let mut ctor_args = vec![];
|
||||
let def = self
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| f.getter)
|
||||
.map(|f| {
|
||||
let mut field_str = String::from("");
|
||||
|
||||
if !f.setter {
|
||||
field_str.push_str("readonly ")
|
||||
}
|
||||
let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty));
|
||||
if self.gen_default_ctor {
|
||||
ctor_args.push(arg.clone());
|
||||
}
|
||||
field_str.push_str(&arg);
|
||||
|
||||
field_str
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\\n");
|
||||
|
||||
if self.gen_default_ctor {
|
||||
format!("{}\\nconstructor({})", def, ctor_args.join(", "))
|
||||
} else {
|
||||
def
|
||||
}
|
||||
}
|
||||
}
|
27
crates/macro/Cargo.toml
Normal file
27
crates/macro/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
authors = ["LongYinan <lynweklm@gmail.com>", "Forehalo <forehalo@gmail.com>"]
|
||||
description = "N-API procedural macros"
|
||||
edition = "2018"
|
||||
keywords = ["NodeJS", "FFI", "NAPI", "n-api"]
|
||||
license = "MIT"
|
||||
name = "napi-derive"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/napi-rs/napi-rs"
|
||||
version = "2.0.0-alpha.0"
|
||||
|
||||
[features]
|
||||
compat-mode = []
|
||||
default = ["compat-mode", "full"]
|
||||
full = ["type-def", "strict"]
|
||||
strict = ["napi-derive-backend/strict"]
|
||||
type-def = ["napi-derive-backend/type-def"]
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.4"
|
||||
napi-derive-backend = {path = "../backend"}
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = {version = "1.0", features = ["fold", "full", "extra-traits"]}
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
29
crates/macro/README.md
Normal file
29
crates/macro/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# napi-derive
|
||||
|
||||
<a href="https://docs.rs/crate/napi-derive"><img src="https://docs.rs/napi-derive/badge.svg"></img></a>
|
||||
<a href="https://crates.io/crates/napi-derive"><img src="https://img.shields.io/crates/v/napi-derive.svg"></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>
|
||||
|
||||
Checkout more examples in [examples](../../examples) folder
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
use napi::bindgen_prelude::*;
|
||||
|
||||
#[napi]
|
||||
fn fibonacci(n: u32) -> u32 {
|
||||
match n {
|
||||
1 | 2 => 1,
|
||||
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
|
||||
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
|
||||
}
|
||||
```
|
110
crates/macro/src/compat_macro.rs
Normal file
110
crates/macro/src/compat_macro.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use proc_macro2::{Ident, Literal, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::fold::{fold_fn_arg, fold_signature, Fold};
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Block, FnArg, Signature, Token, Visibility};
|
||||
|
||||
pub struct ArgLength {
|
||||
pub length: Literal,
|
||||
}
|
||||
|
||||
impl Parse for ArgLength {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let vars = Punctuated::<Literal, Token![,]>::parse_terminated(input)?;
|
||||
Ok(ArgLength {
|
||||
length: vars
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Literal::usize_unsuffixed(0)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JsFunction {
|
||||
pub args: Vec<FnArg>,
|
||||
pub name: Option<Ident>,
|
||||
pub signature: Option<Signature>,
|
||||
pub signature_raw: Option<Signature>,
|
||||
pub block: Vec<Block>,
|
||||
pub visibility: Visibility,
|
||||
}
|
||||
|
||||
impl JsFunction {
|
||||
pub fn new() -> Self {
|
||||
JsFunction {
|
||||
args: vec![],
|
||||
name: None,
|
||||
signature: None,
|
||||
signature_raw: None,
|
||||
visibility: Visibility::Inherited,
|
||||
block: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for JsFunction {
|
||||
fn fold_fn_arg(&mut self, arg: FnArg) -> FnArg {
|
||||
self.args.push(arg.clone());
|
||||
fold_fn_arg(self, arg)
|
||||
}
|
||||
|
||||
fn fold_signature(&mut self, signature: Signature) -> Signature {
|
||||
self.name = Some(format_ident!("{}", signature.ident));
|
||||
let mut new_signature = signature.clone();
|
||||
new_signature.ident = format_ident!("_generated_{}_generated_", signature.ident);
|
||||
self.signature = Some(new_signature);
|
||||
self.signature_raw = Some(signature.clone());
|
||||
fold_signature(self, signature)
|
||||
}
|
||||
|
||||
fn fold_visibility(&mut self, v: Visibility) -> Visibility {
|
||||
self.visibility = v.clone();
|
||||
v
|
||||
}
|
||||
|
||||
fn fold_block(&mut self, node: Block) -> Block {
|
||||
self.block.push(node.clone());
|
||||
node
|
||||
}
|
||||
}
|
||||
pub enum FunctionKind {
|
||||
Contextless,
|
||||
JsFunction,
|
||||
}
|
||||
|
||||
pub fn get_execute_js_code(new_fn_name: Ident, function_kind: FunctionKind) -> TokenStream {
|
||||
let return_token_stream = match function_kind {
|
||||
FunctionKind::Contextless => {
|
||||
quote! {
|
||||
Ok(Some(v)) => unsafe { v.raw() },
|
||||
Ok(None) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
FunctionKind::JsFunction => {
|
||||
quote! {
|
||||
Ok(v) => unsafe { v.raw() },
|
||||
}
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
match panic::catch_unwind(AssertUnwindSafe(move || #new_fn_name(ctx))).map_err(|e| {
|
||||
let message = {
|
||||
if let Some(string) = e.downcast_ref::<String>() {
|
||||
string.clone()
|
||||
} else if let Some(string) = e.downcast_ref::<&str>() {
|
||||
string.to_string()
|
||||
} else {
|
||||
format!("panic from Rust code: {:?}", e)
|
||||
}
|
||||
};
|
||||
Error::from_reason(message)
|
||||
}).and_then(|v| v) {
|
||||
#return_token_stream
|
||||
Err(e) => {
|
||||
unsafe { napi::JsError::from(e).throw_into(raw_env) };
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
209
crates/macro/src/lib.rs
Normal file
209
crates/macro/src/lib.rs
Normal file
|
@ -0,0 +1,209 @@
|
|||
#[cfg(feature = "compat-mode")]
|
||||
mod compat_macro;
|
||||
mod parser;
|
||||
|
||||
#[macro_use]
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate napi_derive_backend;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
use napi_derive_backend::{BindgenResult, TryToTokens};
|
||||
|
||||
#[cfg(feature = "type-def")]
|
||||
use napi_derive_backend::{ToTypeDef, TypeDef};
|
||||
use parser::ParseNapi;
|
||||
use proc_macro::TokenStream as RawStream;
|
||||
use proc_macro2::TokenStream;
|
||||
use std::env;
|
||||
#[cfg(feature = "type-def")]
|
||||
use std::{
|
||||
fs,
|
||||
io::{BufWriter, Result as IOResult, Write},
|
||||
};
|
||||
#[cfg(feature = "compat-mode")]
|
||||
use syn::{fold::Fold, parse_macro_input, ItemFn};
|
||||
|
||||
/// ```ignore
|
||||
/// #[napi]
|
||||
/// fn test(ctx: CallContext, name: String) {
|
||||
/// "hello" + name
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn napi(attr: RawStream, input: RawStream) -> RawStream {
|
||||
match expand(attr.into(), input.into()) {
|
||||
Ok(tokens) => {
|
||||
if env::var("DEBUG_GENERATED_CODE").is_ok() {
|
||||
println!("{}", tokens.to_string());
|
||||
}
|
||||
tokens.into()
|
||||
}
|
||||
Err(diagnostic) => {
|
||||
println!("`napi` macro expand failed.");
|
||||
|
||||
(quote! { #diagnostic }).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand(attr: TokenStream, input: TokenStream) -> BindgenResult<TokenStream> {
|
||||
let mut item = syn::parse2::<syn::Item>(input)?;
|
||||
let opts = syn::parse2(attr)?;
|
||||
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
|
||||
let napi = item.parse_napi(&mut tokens, opts)?;
|
||||
napi.try_to_tokens(&mut tokens)?;
|
||||
|
||||
#[cfg(feature = "type-def")]
|
||||
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
|
||||
if let Err(e) = output_type_def(type_def_file, napi.to_type_def()) {
|
||||
println!("Failed to write type def file: {:?}", e);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
#[cfg(feature = "type-def")]
|
||||
fn output_type_def(type_def_file: String, type_def: TypeDef) -> IOResult<()> {
|
||||
let file = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(type_def_file)?;
|
||||
|
||||
let mut writer = BufWriter::<fs::File>::new(file);
|
||||
writer.write_all(type_def.to_string().as_bytes())?;
|
||||
writer.write_all("\n".as_bytes())
|
||||
}
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
#[proc_macro_attribute]
|
||||
pub fn contextless_function(_attr: RawStream, input: RawStream) -> RawStream {
|
||||
let input = parse_macro_input!(input as ItemFn);
|
||||
let mut js_fn = compat_macro::JsFunction::new();
|
||||
js_fn.fold_item_fn(input);
|
||||
let fn_name = js_fn.name.unwrap();
|
||||
let fn_block = js_fn.block;
|
||||
let signature = js_fn.signature.unwrap();
|
||||
let visibility = js_fn.visibility;
|
||||
let new_fn_name = signature.ident.clone();
|
||||
let execute_js_function =
|
||||
compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::Contextless);
|
||||
|
||||
let expanded = quote! {
|
||||
#[inline(always)]
|
||||
#signature #(#fn_block)*
|
||||
|
||||
#visibility extern "C" fn #fn_name(
|
||||
raw_env: napi::sys::napi_env,
|
||||
cb_info: napi::sys::napi_callback_info,
|
||||
) -> napi::sys::napi_value {
|
||||
use std::ptr;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ffi::CString;
|
||||
use napi::{Env, NapiValue, NapiRaw, Error, Status};
|
||||
|
||||
let ctx = unsafe { Env::from_raw(raw_env) };
|
||||
#execute_js_function
|
||||
}
|
||||
};
|
||||
// Hand the output tokens back to the compiler
|
||||
RawStream::from(expanded)
|
||||
}
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
#[proc_macro_attribute]
|
||||
pub fn js_function(attr: RawStream, input: RawStream) -> RawStream {
|
||||
let arg_len = parse_macro_input!(attr as compat_macro::ArgLength);
|
||||
let arg_len_span = arg_len.length;
|
||||
let input = parse_macro_input!(input as ItemFn);
|
||||
let mut js_fn = compat_macro::JsFunction::new();
|
||||
js_fn.fold_item_fn(input);
|
||||
let fn_name = js_fn.name.unwrap();
|
||||
let fn_block = js_fn.block;
|
||||
let signature = js_fn.signature.unwrap();
|
||||
let visibility = js_fn.visibility;
|
||||
let new_fn_name = signature.ident.clone();
|
||||
let execute_js_function =
|
||||
compat_macro::get_execute_js_code(new_fn_name, compat_macro::FunctionKind::JsFunction);
|
||||
let expanded = quote! {
|
||||
#[inline(always)]
|
||||
#signature #(#fn_block)*
|
||||
|
||||
#visibility extern "C" fn #fn_name(
|
||||
raw_env: napi::sys::napi_env,
|
||||
cb_info: napi::sys::napi_callback_info,
|
||||
) -> napi::sys::napi_value {
|
||||
use std::ptr;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ffi::CString;
|
||||
use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext};
|
||||
let mut argc = #arg_len_span as usize;
|
||||
let mut raw_args: [napi::sys::napi_value; #arg_len_span] = [ptr::null_mut(); #arg_len_span];
|
||||
let mut raw_this = ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
let status = napi::sys::napi_get_cb_info(
|
||||
raw_env,
|
||||
cb_info,
|
||||
&mut argc,
|
||||
raw_args.as_mut_ptr(),
|
||||
&mut raw_this,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
debug_assert!(Status::from(status) == Status::Ok, "napi_get_cb_info failed");
|
||||
}
|
||||
|
||||
let mut env = unsafe { Env::from_raw(raw_env) };
|
||||
let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
|
||||
#execute_js_function
|
||||
}
|
||||
};
|
||||
// Hand the output tokens back to the compiler
|
||||
RawStream::from(expanded)
|
||||
}
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
#[proc_macro_attribute]
|
||||
pub fn module_exports(_attr: RawStream, input: RawStream) -> RawStream {
|
||||
let input = parse_macro_input!(input as ItemFn);
|
||||
let mut js_fn = compat_macro::JsFunction::new();
|
||||
js_fn.fold_item_fn(input);
|
||||
let fn_block = js_fn.block;
|
||||
let fn_name = js_fn.name.unwrap();
|
||||
let signature = js_fn.signature_raw.unwrap();
|
||||
let args_len = js_fn.args.len();
|
||||
let call_expr = if args_len == 1 {
|
||||
quote! { #fn_name(exports) }
|
||||
} else if args_len == 2 {
|
||||
quote! { #fn_name(exports, env) }
|
||||
} else {
|
||||
panic!("Arguments length of #[module_exports] function must be 1 or 2");
|
||||
};
|
||||
|
||||
let register = quote! {
|
||||
#[napi::bindgen_prelude::ctor]
|
||||
fn __napi__explicit_module_register() {
|
||||
unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
|
||||
use napi::{Env, JsObject, NapiValue};
|
||||
|
||||
let env = Env::from_raw(raw_env);
|
||||
let exports = JsObject::from_raw_unchecked(raw_env, raw_exports);
|
||||
|
||||
#call_expr
|
||||
}
|
||||
|
||||
napi::bindgen_prelude::register_module_exports(register)
|
||||
}
|
||||
};
|
||||
|
||||
(quote! {
|
||||
#[inline]
|
||||
#signature #(#fn_block)*
|
||||
|
||||
#register
|
||||
})
|
||||
.into()
|
||||
}
|
267
crates/macro/src/parser/attrs.rs
Normal file
267
crates/macro/src/parser/attrs.rs
Normal file
|
@ -0,0 +1,267 @@
|
|||
use napi_derive_backend::{bail_span, BindgenResult, Diagnostic};
|
||||
use proc_macro2::{Delimiter, Ident, Span, TokenTree};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
};
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
thread_local! {
|
||||
static ATTRS: AttributeParseState = Default::default();
|
||||
static STRUCTS: StructParseState = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StructParseState {
|
||||
parsed: RefCell<HashMap<String, ParsedStruct>>,
|
||||
}
|
||||
|
||||
struct ParsedStruct {
|
||||
js_name: String,
|
||||
ctor_defined: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AttributeParseState {
|
||||
parsed: Cell<usize>,
|
||||
checks: Cell<usize>,
|
||||
}
|
||||
|
||||
/// Parsed attributes from a `#[napi(..)]`.
|
||||
pub struct BindgenAttrs {
|
||||
/// Whether `#[napi]` attribute exists
|
||||
pub exists: bool,
|
||||
/// List of parsed attributes
|
||||
pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
|
||||
/// Span of original attribute
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
// NOTE: borrowed from wasm-bindgen
|
||||
// some of them may useless is #[napi] macro
|
||||
macro_rules! attrgen {
|
||||
($mac:ident) => {
|
||||
$mac! {
|
||||
(js_name, JsName(Span, String, Span)),
|
||||
(constructor, Constructor(Span)),
|
||||
(getter, Getter(Span, Option<Ident>)),
|
||||
(setter, Setter(Span, Option<Ident>)),
|
||||
(readonly, Readonly(Span)),
|
||||
(skip, Skip(Span)),
|
||||
(strict, Strict(Span)),
|
||||
|
||||
// impl later
|
||||
// (inspectable, Inspectable(Span)),
|
||||
// (typescript_custom_section, TypescriptCustomSection(Span)),
|
||||
// (skip_typescript, SkipTypescript(Span)),
|
||||
// (typescript_type, TypeScriptType(Span, String, Span)),
|
||||
// (getter_with_clone, GetterWithClone(Span)),
|
||||
|
||||
// For testing purposes only.
|
||||
// (assert_no_shim, AssertNoShim(Span)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! methods {
|
||||
($(($name:ident, $variant:ident($($contents:tt)*)),)*) => {
|
||||
$(methods!(@method $name, $variant($($contents)*));)*
|
||||
|
||||
#[cfg(feature = "strict")]
|
||||
fn check_used(self) -> Result<(), Diagnostic> {
|
||||
// Account for the fact this method was called
|
||||
ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
|
||||
|
||||
let mut errors = Vec::new();
|
||||
for (used, attr) in self.attrs.iter() {
|
||||
if used.get() {
|
||||
continue
|
||||
}
|
||||
let span = match attr {
|
||||
$(BindgenAttr::$variant(span, ..) => span,)*
|
||||
};
|
||||
errors.push(Diagnostic::span_error(*span, "unused #[napi] attribute"));
|
||||
}
|
||||
Diagnostic::from_vec(errors)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "strict"))]
|
||||
fn check_used(self) -> Result<(), Diagnostic> {
|
||||
// Account for the fact this method was called
|
||||
ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
(@method $name:ident, $variant:ident(Span, String, Span)) => {
|
||||
pub fn $name(&self) -> Option<(&str, Span)> {
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter_map(|a| match &a.1 {
|
||||
BindgenAttr::$variant(_, s, span) => {
|
||||
a.0.set(true);
|
||||
Some((&s[..], *span))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
}
|
||||
};
|
||||
|
||||
(@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
|
||||
pub fn $name(&self) -> Option<(&[String], &[Span])> {
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter_map(|a| match &a.1 {
|
||||
BindgenAttr::$variant(_, ss, spans) => {
|
||||
a.0.set(true);
|
||||
Some((&ss[..], &spans[..]))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
}
|
||||
};
|
||||
|
||||
(@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
|
||||
#[allow(unused)]
|
||||
pub fn $name(&self) -> Option<&$($other)*> {
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter_map(|a| match &a.1 {
|
||||
BindgenAttr::$variant(_, s) => {
|
||||
a.0.set(true);
|
||||
Some(s)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
}
|
||||
};
|
||||
|
||||
(@method $name:ident, $variant:ident($($other:tt)*)) => {
|
||||
#[allow(unused)]
|
||||
pub fn $name(&self) -> Option<&$($other)*> {
|
||||
self.attrs
|
||||
.iter()
|
||||
.filter_map(|a| match &a.1 {
|
||||
BindgenAttr::$variant(s) => {
|
||||
a.0.set(true);
|
||||
Some(s)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl BindgenAttrs {
|
||||
/// Find and parse the napi attributes.
|
||||
pub fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
|
||||
let mut ret = BindgenAttrs::default();
|
||||
loop {
|
||||
let napi_attr = attrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, m)| m.path.segments[0].ident == "napi");
|
||||
|
||||
let pos = match &napi_attr {
|
||||
Some((pos, raw_attr)) => {
|
||||
ret.exists = true;
|
||||
ret.span = raw_attr.tokens.span();
|
||||
*pos
|
||||
}
|
||||
None => return Ok(ret),
|
||||
};
|
||||
let attr = attrs.remove(pos);
|
||||
let mut tts = attr.tokens.clone().into_iter();
|
||||
let group = match tts.next() {
|
||||
Some(TokenTree::Group(d)) => d,
|
||||
Some(_) => bail_span!(attr, "malformed #[napi] attribute"),
|
||||
None => continue,
|
||||
};
|
||||
if tts.next().is_some() {
|
||||
bail_span!(attr, "malformed #[napi] attribute");
|
||||
}
|
||||
if group.delimiter() != Delimiter::Parenthesis {
|
||||
bail_span!(attr, "malformed #[napi] attribute");
|
||||
}
|
||||
let mut attrs: BindgenAttrs = syn::parse2(group.stream())?;
|
||||
ret.attrs.append(&mut attrs.attrs);
|
||||
attrs.check_used()?;
|
||||
}
|
||||
}
|
||||
|
||||
attrgen!(methods);
|
||||
}
|
||||
|
||||
impl Default for BindgenAttrs {
|
||||
fn default() -> BindgenAttrs {
|
||||
// Add 1 to the list of parsed attribute sets. We'll use this counter to
|
||||
// sanity check that we call `check_used` an appropriate number of
|
||||
// times.
|
||||
ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
|
||||
BindgenAttrs {
|
||||
span: Span::call_site(),
|
||||
attrs: Vec::new(),
|
||||
exists: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! gen_bindgen_attr {
|
||||
($( ($method:ident, $($variants:tt)*) ,)*) => {
|
||||
/// The possible attributes in the `#[napi]`.
|
||||
pub enum BindgenAttr {
|
||||
$($($variants)*,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attrgen!(gen_bindgen_attr);
|
||||
|
||||
pub fn record_struct(ident: &Ident, js_name: String, opts: &BindgenAttrs) {
|
||||
STRUCTS.with(|state| {
|
||||
let struct_name = ident.to_string();
|
||||
|
||||
let mut map = state.parsed.borrow_mut();
|
||||
|
||||
map.insert(
|
||||
struct_name,
|
||||
ParsedStruct {
|
||||
js_name,
|
||||
ctor_defined: opts.constructor().is_some(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn check_recorded_struct_for_impl(ident: &Ident, opts: &BindgenAttrs) -> BindgenResult<String> {
|
||||
STRUCTS.with(|state| {
|
||||
let struct_name = ident.to_string();
|
||||
|
||||
let mut map = state.parsed.borrow_mut();
|
||||
if let Some(parsed) = map.get_mut(&struct_name) {
|
||||
if opts.constructor().is_some() {
|
||||
if parsed.ctor_defined {
|
||||
bail_span!(
|
||||
ident,
|
||||
"Constructor has already been defined for struct `{}`",
|
||||
&struct_name
|
||||
);
|
||||
} else {
|
||||
parsed.ctor_defined = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parsed.js_name.clone())
|
||||
} else {
|
||||
bail_span!(
|
||||
ident,
|
||||
"Did not find struct `{}` parsed before expand #[napi] for impl",
|
||||
&struct_name,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
868
crates/macro/src/parser/mod.rs
Normal file
868
crates/macro/src/parser/mod.rs
Normal file
|
@ -0,0 +1,868 @@
|
|||
#[macro_use]
|
||||
pub mod attrs;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::str::Chars;
|
||||
|
||||
use attrs::{BindgenAttr, BindgenAttrs};
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use napi_derive_backend::{
|
||||
BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiEnum, NapiEnumVariant, NapiFn,
|
||||
NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField,
|
||||
};
|
||||
use proc_macro2::{Ident, TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use syn::parse::{Parse, ParseStream, Result as SynResult};
|
||||
use syn::{Attribute, Signature, Visibility};
|
||||
|
||||
use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct};
|
||||
|
||||
struct AnyIdent(Ident);
|
||||
|
||||
impl Parse for AnyIdent {
|
||||
fn parse(input: ParseStream) -> SynResult<Self> {
|
||||
input.step(|cursor| match cursor.ident() {
|
||||
Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
|
||||
None => Err(cursor.error("expected an identifier")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for BindgenAttrs {
|
||||
fn parse(input: ParseStream) -> SynResult<Self> {
|
||||
let mut attrs = BindgenAttrs::default();
|
||||
if input.is_empty() {
|
||||
return Ok(attrs);
|
||||
}
|
||||
|
||||
let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
|
||||
attrs.attrs = opts.into_iter().map(|c| (Cell::new(false), c)).collect();
|
||||
Ok(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for BindgenAttr {
|
||||
fn parse(input: ParseStream) -> SynResult<Self> {
|
||||
let original = input.fork();
|
||||
let attr: AnyIdent = input.parse()?;
|
||||
let attr = attr.0;
|
||||
let attr_span = attr.span();
|
||||
let attr_string = attr.to_string();
|
||||
let raw_attr_string = format!("r#{}", attr_string);
|
||||
|
||||
macro_rules! parsers {
|
||||
($(($name:ident, $($contents:tt)*),)*) => {
|
||||
$(
|
||||
if attr_string == stringify!($name) || raw_attr_string == stringify!($name) {
|
||||
parsers!(
|
||||
@parser
|
||||
$($contents)*
|
||||
);
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
(@parser $variant:ident(Span)) => ({
|
||||
return Ok(BindgenAttr::$variant(attr_span));
|
||||
});
|
||||
|
||||
(@parser $variant:ident(Span, Ident)) => ({
|
||||
input.parse::<Token![=]>()?;
|
||||
let ident = input.parse::<AnyIdent>()?.0;
|
||||
return Ok(BindgenAttr::$variant(attr_span, ident))
|
||||
});
|
||||
|
||||
(@parser $variant:ident(Span, Option<Ident>)) => ({
|
||||
if input.parse::<Token![=]>().is_ok() {
|
||||
let ident = input.parse::<AnyIdent>()?.0;
|
||||
return Ok(BindgenAttr::$variant(attr_span, Some(ident)))
|
||||
} else {
|
||||
return Ok(BindgenAttr::$variant(attr_span, None));
|
||||
}
|
||||
});
|
||||
|
||||
(@parser $variant:ident(Span, syn::Path)) => ({
|
||||
input.parse::<Token![=]>()?;
|
||||
return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
|
||||
});
|
||||
|
||||
(@parser $variant:ident(Span, syn::Expr)) => ({
|
||||
input.parse::<Token![=]>()?;
|
||||
return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
|
||||
});
|
||||
|
||||
(@parser $variant:ident(Span, String, Span)) => ({
|
||||
input.parse::<Token![=]>()?;
|
||||
let (val, span) = match input.parse::<syn::LitStr>() {
|
||||
Ok(str) => (str.value(), str.span()),
|
||||
Err(_) => {
|
||||
let ident = input.parse::<AnyIdent>()?.0;
|
||||
(ident.to_string(), ident.span())
|
||||
}
|
||||
};
|
||||
return Ok(BindgenAttr::$variant(attr_span, val, span))
|
||||
});
|
||||
|
||||
(@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
|
||||
input.parse::<Token![=]>()?;
|
||||
let (vals, spans) = match input.parse::<syn::ExprArray>() {
|
||||
Ok(exprs) => {
|
||||
let mut vals = vec![];
|
||||
let mut spans = vec![];
|
||||
|
||||
for expr in exprs.elems.iter() {
|
||||
if let syn::Expr::Lit(syn::ExprLit {
|
||||
lit: syn::Lit::Str(ref str),
|
||||
..
|
||||
}) = expr {
|
||||
vals.push(str.value());
|
||||
spans.push(str.span());
|
||||
} else {
|
||||
return Err(syn::Error::new(expr.span(), "expected string literals"));
|
||||
}
|
||||
}
|
||||
|
||||
(vals, spans)
|
||||
},
|
||||
Err(_) => {
|
||||
let ident = input.parse::<AnyIdent>()?.0;
|
||||
(vec![ident.to_string()], vec![ident.span()])
|
||||
}
|
||||
};
|
||||
return Ok(BindgenAttr::$variant(attr_span, vals, spans))
|
||||
});
|
||||
}
|
||||
|
||||
attrgen!(parsers);
|
||||
|
||||
Err(original.error("unknown attribute"))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ConvertToAST {
|
||||
fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult<Napi>;
|
||||
}
|
||||
|
||||
pub trait ParseNapi {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi>;
|
||||
}
|
||||
|
||||
fn get_ty(mut ty: &syn::Type) -> &syn::Type {
|
||||
while let syn::Type::Group(g) = ty {
|
||||
ty = &g.elem;
|
||||
}
|
||||
|
||||
ty
|
||||
}
|
||||
|
||||
fn replace_self(ty: syn::Type, self_ty: Option<&Ident>) -> syn::Type {
|
||||
let self_ty = match self_ty {
|
||||
Some(i) => i,
|
||||
None => return ty,
|
||||
};
|
||||
let path = match get_ty(&ty) {
|
||||
syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(),
|
||||
other => return other.clone(),
|
||||
};
|
||||
let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
|
||||
self_ty.clone().into()
|
||||
} else {
|
||||
path
|
||||
};
|
||||
syn::Type::Path(syn::TypePath {
|
||||
qself: None,
|
||||
path: new_path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extracts the last ident from the path
|
||||
fn extract_path_ident(path: &syn::Path) -> BindgenResult<Ident> {
|
||||
for segment in path.segments.iter() {
|
||||
match segment.arguments {
|
||||
syn::PathArguments::None => {}
|
||||
_ => bail_span!(path, "paths with type parameters are not supported yet"),
|
||||
}
|
||||
}
|
||||
|
||||
match path.segments.last() {
|
||||
Some(value) => Ok(value.ident.clone()),
|
||||
None => {
|
||||
bail_span!(path, "empty idents are not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_fn_types(
|
||||
arguments: &syn::PathArguments,
|
||||
) -> BindgenResult<(Vec<syn::Type>, Option<syn::Type>)> {
|
||||
match arguments {
|
||||
// <T: Fn>
|
||||
syn::PathArguments::None => Ok((vec![], None)),
|
||||
syn::PathArguments::AngleBracketed(_) => {
|
||||
bail_span!(arguments, "use parentheses for napi callback trait")
|
||||
}
|
||||
syn::PathArguments::Parenthesized(arguments) => {
|
||||
let args = arguments.inputs.iter().cloned().collect::<Vec<_>>();
|
||||
|
||||
let ret = match &arguments.output {
|
||||
syn::ReturnType::Type(_, ret_ty) => {
|
||||
let ret_ty = &**ret_ty;
|
||||
match ret_ty {
|
||||
syn::Type::Path(syn::TypePath {
|
||||
qself: None,
|
||||
ref path,
|
||||
}) if path.segments.len() == 1 => {
|
||||
let segment = path.segments.first().unwrap();
|
||||
|
||||
if segment.ident != "Result" {
|
||||
bail_span!(ret_ty, "The return type of callback can only be `Result`");
|
||||
} else {
|
||||
match &segment.arguments {
|
||||
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||
args,
|
||||
..
|
||||
}) => {
|
||||
// fast test
|
||||
if args.to_token_stream().to_string() == "()" {
|
||||
None
|
||||
} else {
|
||||
let ok_arg = args.first().unwrap();
|
||||
match ok_arg {
|
||||
syn::GenericArgument::Type(ty) => Some(ty.clone()),
|
||||
_ => bail_span!(ok_arg, "unsupported generic type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
bail_span!(segment, "Too many arguments")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => bail_span!(ret_ty, "The return type of callback can only be `Result`"),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
bail_span!(
|
||||
arguments,
|
||||
"The return type of callback can only be `Result`. Try with `Result<()>`"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Ok((args, ret))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
|
||||
while let syn::Expr::Group(g) = expr {
|
||||
expr = &g.expr;
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
/// Extract the documentation comments from a Vec of attributes
|
||||
fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter_map(|a| {
|
||||
// if the path segments include an ident of "doc" we know this
|
||||
// this is a doc comment
|
||||
if a.path.segments.iter().any(|s| s.ident == "doc") {
|
||||
Some(
|
||||
// We want to filter out any Puncts so just grab the Literals
|
||||
a.tokens.clone().into_iter().filter_map(|t| match t {
|
||||
TokenTree::Literal(lit) => {
|
||||
let quoted = lit.to_string();
|
||||
Some(try_unescape("ed).unwrap_or(quoted))
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
//Fold up the [[String]] iter we created into Vec<String>
|
||||
.fold(vec![], |mut acc, a| {
|
||||
acc.extend(a);
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
// Unescapes a quoted string. char::escape_debug() was used to escape the text.
|
||||
fn try_unescape(s: &str) -> Option<String> {
|
||||
if s.is_empty() {
|
||||
return Some(String::new());
|
||||
}
|
||||
let mut result = String::with_capacity(s.len());
|
||||
let mut chars = s.chars();
|
||||
for i in 0.. {
|
||||
let c = match chars.next() {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
if result.ends_with('"') {
|
||||
result.pop();
|
||||
}
|
||||
return Some(result);
|
||||
}
|
||||
};
|
||||
if i == 0 && c == '"' {
|
||||
// ignore it
|
||||
} else if c == '\\' {
|
||||
let c = chars.next()?;
|
||||
match c {
|
||||
't' => result.push('\t'),
|
||||
'r' => result.push('\r'),
|
||||
'n' => result.push('\n'),
|
||||
'\\' | '\'' | '"' => result.push(c),
|
||||
'u' => {
|
||||
if chars.next() != Some('{') {
|
||||
return None;
|
||||
}
|
||||
let (c, next) = unescape_unicode(&mut chars)?;
|
||||
result.push(c);
|
||||
if next != '}' {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> {
|
||||
let mut value = 0;
|
||||
for i in 0..7 {
|
||||
let c = chars.next()?;
|
||||
let num = match c {
|
||||
'0'..='9' => c as u32 - '0' as u32,
|
||||
'a'..='f' => c as u32 - 'a' as u32,
|
||||
'A'..='F' => c as u32 - 'A' as u32,
|
||||
_ => {
|
||||
if i == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
return None;
|
||||
}
|
||||
let decoded = char::from_u32(value)?;
|
||||
return Some((decoded, c));
|
||||
}
|
||||
};
|
||||
|
||||
if i >= 6 {
|
||||
return None;
|
||||
}
|
||||
value = (value << 4) | num;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn extract_fn_closure_generics(
|
||||
generics: &syn::Generics,
|
||||
) -> BindgenResult<HashMap<String, syn::PathArguments>> {
|
||||
let mut errors = vec![];
|
||||
|
||||
let mut map = HashMap::default();
|
||||
if generics.params.is_empty() {
|
||||
return Ok(map);
|
||||
}
|
||||
|
||||
if let Some(where_clause) = &generics.where_clause {
|
||||
for prediction in where_clause.predicates.iter() {
|
||||
match prediction {
|
||||
syn::WherePredicate::Type(syn::PredicateType {
|
||||
bounded_ty, bounds, ..
|
||||
}) => {
|
||||
for bound in bounds {
|
||||
match bound {
|
||||
syn::TypeParamBound::Trait(t) => {
|
||||
for segment in t.path.segments.iter() {
|
||||
match segment.ident.to_string().as_str() {
|
||||
"Fn" | "FnOnce" | "FnMut" => {
|
||||
map.insert(
|
||||
bounded_ty.to_token_stream().to_string(),
|
||||
segment.arguments.clone(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
syn::TypeParamBound::Lifetime(lifetime) => {
|
||||
if lifetime.ident != "static" {
|
||||
errors.push(err_span!(
|
||||
bound,
|
||||
"only 'static is supported in lifetime bound for fn arguments"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => errors.push(err_span! {
|
||||
prediction,
|
||||
"unsupported where clause prediction in napi"
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for param in generics.params.iter() {
|
||||
match param {
|
||||
syn::GenericParam::Type(syn::TypeParam { ident, bounds, .. }) => {
|
||||
for bound in bounds {
|
||||
match bound {
|
||||
syn::TypeParamBound::Trait(t) => {
|
||||
for segment in t.path.segments.iter() {
|
||||
match segment.ident.to_string().as_str() {
|
||||
"Fn" | "FnOnce" | "FnMut" => {
|
||||
map.insert(ident.to_string(), segment.arguments.clone());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
syn::TypeParamBound::Lifetime(lifetime) => {
|
||||
if lifetime.ident != "static" {
|
||||
errors.push(err_span!(
|
||||
bound,
|
||||
"only 'static is supported in lifetime bound for fn arguments"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
errors.push(err_span!(param, "unsupported napi generic param for fn"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Diagnostic::from_vec(errors).and(Ok(map))
|
||||
}
|
||||
|
||||
fn napi_fn_from_decl(
|
||||
sig: Signature,
|
||||
opts: &BindgenAttrs,
|
||||
attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
parent: Option<&Ident>,
|
||||
) -> BindgenResult<NapiFn> {
|
||||
let mut errors = vec![];
|
||||
|
||||
let syn::Signature {
|
||||
ident,
|
||||
asyncness,
|
||||
inputs,
|
||||
output,
|
||||
generics,
|
||||
..
|
||||
} = sig;
|
||||
|
||||
let mut fn_self = None;
|
||||
let callback_traits = extract_fn_closure_generics(&generics)?;
|
||||
|
||||
let args = inputs
|
||||
.into_iter()
|
||||
.filter_map(|arg| match arg {
|
||||
syn::FnArg::Typed(mut p) => {
|
||||
let ty_str = p.ty.to_token_stream().to_string();
|
||||
if let Some(path_arguments) = callback_traits.get(&ty_str) {
|
||||
match extract_fn_types(path_arguments) {
|
||||
Ok((fn_args, fn_ret)) => Some(NapiFnArgKind::Callback(Box::new(CallbackArg {
|
||||
pat: p.pat,
|
||||
args: fn_args,
|
||||
ret: fn_ret,
|
||||
}))),
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let ty = replace_self(*p.ty, parent);
|
||||
p.ty = Box::new(ty);
|
||||
Some(NapiFnArgKind::PatType(Box::new(p)))
|
||||
}
|
||||
}
|
||||
syn::FnArg::Receiver(r) => {
|
||||
if parent.is_some() {
|
||||
assert!(fn_self.is_none());
|
||||
if r.reference.is_none() {
|
||||
errors.push(err_span!(
|
||||
r,
|
||||
"The native methods can't move values from napi. Try `&self` or `&mut self` instead."
|
||||
));
|
||||
} else if r.mutability.is_some() {
|
||||
fn_self = Some(FnSelf::MutRef);
|
||||
} else {
|
||||
fn_self = Some(FnSelf::Ref);
|
||||
}
|
||||
} else {
|
||||
errors.push(err_span!(r, "arguments cannot be `self`"));
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ret = match output {
|
||||
syn::ReturnType::Default => None,
|
||||
syn::ReturnType::Type(_, ty) => Some(replace_self(*ty, parent)),
|
||||
};
|
||||
|
||||
Diagnostic::from_vec(errors).map(|_| {
|
||||
let js_name = if let Some(prop_name) = opts.getter() {
|
||||
if let Some(ident) = prop_name {
|
||||
ident.to_string()
|
||||
} else {
|
||||
ident
|
||||
.to_string()
|
||||
.trim_start_matches("get_")
|
||||
.to_case(Case::Camel)
|
||||
}
|
||||
} else if let Some(prop_name) = opts.setter() {
|
||||
if let Some(ident) = prop_name {
|
||||
ident.to_string()
|
||||
} else {
|
||||
ident
|
||||
.to_string()
|
||||
.trim_start_matches("set_")
|
||||
.to_case(Case::Camel)
|
||||
}
|
||||
} else {
|
||||
opts.js_name().map_or_else(
|
||||
|| ident.to_string().to_case(Case::Camel),
|
||||
|(js_name, _)| js_name.to_owned(),
|
||||
)
|
||||
};
|
||||
|
||||
NapiFn {
|
||||
name: ident,
|
||||
js_name,
|
||||
args,
|
||||
ret,
|
||||
is_async: asyncness.is_some(),
|
||||
vis,
|
||||
kind: fn_kind(opts),
|
||||
fn_self,
|
||||
parent: parent.cloned(),
|
||||
attrs,
|
||||
strict: opts.strict().is_some(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl ParseNapi for syn::Item {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
match self {
|
||||
syn::Item::Fn(f) => f.parse_napi(tokens, opts),
|
||||
syn::Item::Struct(s) => s.parse_napi(tokens, opts),
|
||||
syn::Item::Impl(i) => i.parse_napi(tokens, opts),
|
||||
syn::Item::Enum(e) => e.parse_napi(tokens, opts),
|
||||
_ => bail_span!(
|
||||
self,
|
||||
"#[napi] can only be applied to a function, struct, enum or impl."
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseNapi for syn::ItemFn {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
self.to_tokens(tokens);
|
||||
self.convert_to_ast(opts)
|
||||
}
|
||||
}
|
||||
impl ParseNapi for syn::ItemStruct {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
let napi = self.convert_to_ast(opts);
|
||||
self.to_tokens(tokens);
|
||||
|
||||
napi
|
||||
}
|
||||
}
|
||||
impl ParseNapi for syn::ItemImpl {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
// #[napi] macro will be remove from impl items after converted to ast
|
||||
let napi = self.convert_to_ast(opts);
|
||||
self.to_tokens(tokens);
|
||||
|
||||
napi
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseNapi for syn::ItemEnum {
|
||||
fn parse_napi(&mut self, tokens: &mut TokenStream, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
let napi = self.convert_to_ast(opts);
|
||||
self.to_tokens(tokens);
|
||||
|
||||
napi
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_kind(opts: &BindgenAttrs) -> FnKind {
|
||||
let mut kind = FnKind::Normal;
|
||||
|
||||
if opts.getter().is_some() {
|
||||
kind = FnKind::Getter;
|
||||
}
|
||||
|
||||
if opts.setter().is_some() {
|
||||
kind = FnKind::Setter;
|
||||
}
|
||||
|
||||
if opts.constructor().is_some() {
|
||||
kind = FnKind::Constructor;
|
||||
}
|
||||
|
||||
kind
|
||||
}
|
||||
|
||||
impl ConvertToAST for syn::ItemFn {
|
||||
fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
let func = napi_fn_from_decl(
|
||||
self.sig.clone(),
|
||||
&opts,
|
||||
self.attrs.clone(),
|
||||
self.vis.clone(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(Napi {
|
||||
comments: vec![],
|
||||
item: NapiItem::Fn(func),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConvertToAST for syn::ItemStruct {
|
||||
fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
let vis = self.vis.clone();
|
||||
let struct_name = self.ident.clone();
|
||||
let js_name = opts.js_name().map_or_else(
|
||||
|| self.ident.to_string().to_case(Case::Pascal),
|
||||
|(js_name, _)| js_name.to_owned(),
|
||||
);
|
||||
let mut fields = vec![];
|
||||
let mut is_tuple = false;
|
||||
|
||||
for (i, field) in self.fields.iter_mut().enumerate() {
|
||||
match field.vis {
|
||||
syn::Visibility::Public(..) => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let field_opts = BindgenAttrs::find(&mut field.attrs)?;
|
||||
|
||||
let (js_name, name) = match &field.ident {
|
||||
Some(ident) => (
|
||||
field_opts
|
||||
.js_name()
|
||||
.map_or_else(|| ident.to_string(), |(js_name, _)| js_name.to_owned()),
|
||||
syn::Member::Named(ident.clone()),
|
||||
),
|
||||
None => {
|
||||
is_tuple = true;
|
||||
(i.to_string(), syn::Member::Unnamed(i.into()))
|
||||
}
|
||||
};
|
||||
|
||||
let ignored = field_opts.skip().is_some();
|
||||
let readonly = field_opts.readonly().is_some();
|
||||
|
||||
fields.push(NapiStructField {
|
||||
name,
|
||||
js_name,
|
||||
ty: field.ty.clone(),
|
||||
getter: !ignored,
|
||||
setter: !(ignored || readonly),
|
||||
})
|
||||
}
|
||||
|
||||
record_struct(&struct_name, js_name.clone(), &opts);
|
||||
|
||||
Ok(Napi {
|
||||
comments: vec![],
|
||||
item: NapiItem::Struct(NapiStruct {
|
||||
js_name,
|
||||
name: struct_name,
|
||||
vis,
|
||||
fields,
|
||||
is_tuple,
|
||||
gen_default_ctor: opts.constructor().is_some(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConvertToAST for syn::ItemImpl {
|
||||
fn convert_to_ast(&mut self, _opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
let struct_name = match get_ty(&self.self_ty) {
|
||||
syn::Type::Path(syn::TypePath {
|
||||
ref path,
|
||||
qself: None,
|
||||
}) => path,
|
||||
_ => {
|
||||
bail_span!(self.self_ty, "unsupported self type in #[napi] impl")
|
||||
}
|
||||
};
|
||||
|
||||
let struct_name = extract_path_ident(struct_name)?;
|
||||
|
||||
let mut struct_js_name = struct_name.to_string();
|
||||
let mut items = vec![];
|
||||
|
||||
for item in self.items.iter_mut() {
|
||||
let method = match item {
|
||||
syn::ImplItem::Method(m) => m,
|
||||
_ => {
|
||||
bail_span!(item, "unsupported impl item in #[napi]")
|
||||
}
|
||||
};
|
||||
let opts = BindgenAttrs::find(&mut method.attrs)?;
|
||||
|
||||
// it'd better only care methods decorated with `#[napi]` attribute
|
||||
if !opts.exists {
|
||||
continue;
|
||||
}
|
||||
|
||||
if opts.constructor().is_some() {
|
||||
struct_js_name = check_recorded_struct_for_impl(&struct_name, &opts)?;
|
||||
}
|
||||
|
||||
let vis = method.vis.clone();
|
||||
|
||||
match &vis {
|
||||
Visibility::Public(_) => {}
|
||||
_ => {
|
||||
bail_span!(method.sig.ident, "only pub method supported by #[napi].",);
|
||||
}
|
||||
}
|
||||
|
||||
let func = napi_fn_from_decl(
|
||||
method.sig.clone(),
|
||||
&opts,
|
||||
method.attrs.clone(),
|
||||
vis,
|
||||
Some(&struct_name),
|
||||
)?;
|
||||
|
||||
items.push(func);
|
||||
}
|
||||
|
||||
Ok(Napi {
|
||||
comments: vec![],
|
||||
item: NapiItem::Impl(NapiImpl {
|
||||
name: struct_name,
|
||||
js_name: struct_js_name,
|
||||
items,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConvertToAST for syn::ItemEnum {
|
||||
fn convert_to_ast(&mut self, opts: BindgenAttrs) -> BindgenResult<Napi> {
|
||||
match self.vis {
|
||||
Visibility::Public(_) => {}
|
||||
_ => bail_span!(self, "only public enum allowed"),
|
||||
}
|
||||
if self.variants.is_empty() {
|
||||
bail_span!(self, "cannot export empty enum to JS");
|
||||
}
|
||||
|
||||
self.attrs.push(Attribute {
|
||||
pound_token: Default::default(),
|
||||
style: syn::AttrStyle::Outer,
|
||||
bracket_token: Default::default(),
|
||||
path: syn::parse_quote! { derive },
|
||||
tokens: quote! { (Copy, Clone) },
|
||||
});
|
||||
|
||||
let js_name = opts
|
||||
.js_name()
|
||||
.map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string());
|
||||
|
||||
let mut last_variant_val: i32 = -1;
|
||||
let variants = self
|
||||
.variants
|
||||
.iter()
|
||||
.map(|v| {
|
||||
match v.fields {
|
||||
syn::Fields::Unit => {}
|
||||
_ => bail_span!(v.fields, "Structured enum is not supported in #[napi]"),
|
||||
};
|
||||
|
||||
let val = match &v.discriminant {
|
||||
Some((_, expr)) => {
|
||||
let mut symbol = 1;
|
||||
let mut inner_expr = get_expr(expr);
|
||||
if let syn::Expr::Unary(syn::ExprUnary {
|
||||
attrs: _,
|
||||
op: syn::UnOp::Neg(_),
|
||||
expr,
|
||||
}) = inner_expr
|
||||
{
|
||||
symbol = -1;
|
||||
inner_expr = expr;
|
||||
}
|
||||
|
||||
match inner_expr {
|
||||
syn::Expr::Lit(syn::ExprLit {
|
||||
attrs: _,
|
||||
lit: syn::Lit::Int(int_lit),
|
||||
}) => match int_lit.base10_digits().parse::<i32>() {
|
||||
Ok(v) => symbol * v,
|
||||
Err(_) => {
|
||||
bail_span!(
|
||||
int_lit,
|
||||
"enums with #[wasm_bindgen] can only support \
|
||||
numbers that can be represented as i32",
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => bail_span!(
|
||||
expr,
|
||||
"enums with #[wasm_bindgen] may only have \
|
||||
number literal values",
|
||||
),
|
||||
}
|
||||
}
|
||||
None => last_variant_val + 1,
|
||||
};
|
||||
|
||||
last_variant_val = val;
|
||||
let comments = extract_doc_comments(&v.attrs);
|
||||
Ok(NapiEnumVariant {
|
||||
name: v.ident.clone(),
|
||||
val,
|
||||
comments,
|
||||
})
|
||||
})
|
||||
.collect::<BindgenResult<Vec<NapiEnumVariant>>>()?;
|
||||
|
||||
let comments = extract_doc_comments(&self.attrs);
|
||||
|
||||
Ok(Napi {
|
||||
comments,
|
||||
item: NapiItem::Enum(NapiEnum {
|
||||
name: self.ident.clone(),
|
||||
js_name,
|
||||
variants,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,10 +7,11 @@ license = "MIT"
|
|||
name = "napi"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/napi-rs/napi-rs"
|
||||
version = "1.7.7"
|
||||
version = "2.0.0-alpha.0"
|
||||
|
||||
[features]
|
||||
default = ["napi3"] # for most Node.js users
|
||||
compat-mode = []
|
||||
default = ["napi3", "compat-mode"] # for most Node.js users
|
||||
latin1 = ["encoding_rs"]
|
||||
napi1 = []
|
||||
napi2 = ["napi1"]
|
||||
|
@ -24,6 +25,7 @@ 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]
|
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
|
||||
|
||||
![Lint](https://github.com/napi-rs/napi-rs/workflows/Lint/badge.svg)
|
||||
![Linux N-API@3](https://github.com/napi-rs/napi-rs/workflows/Linux%20N-API@3/badge.svg)
|
||||
![Linux musl](https://github.com/napi-rs/napi-rs/workflows/Linux%20musl/badge.svg)
|
||||
![macOS/Windows/Linux x64](https://github.com/napi-rs/napi-rs/workflows/macOS/Windows/Linux%20x64/badge.svg)
|
||||
![Linux-aarch64](https://github.com/napi-rs/napi-rs/workflows/Linux-aarch64/badge.svg)
|
||||
![Linux-armv7](https://github.com/napi-rs/napi-rs/workflows/Linux-armv7/badge.svg)
|
||||
![macOS-Android](https://github.com/napi-rs/napi-rs/workflows/macOS-Android/badge.svg)
|
||||
![Windows i686](https://github.com/napi-rs/napi-rs/workflows/Windows%20i686/badge.svg)
|
||||
[![Windows arm64](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml/badge.svg)](https://github.com/napi-rs/napi-rs/actions/workflows/windows-arm.yml)
|
||||
[![FreeBSD](https://api.cirrus-ci.com/github/napi-rs/napi-rs.svg)](https://cirrus-ci.com/github/napi-rs/napi-rs?branch=main)
|
||||
|
||||
| | node12 | node14 | node16 |
|
||||
| --------------------- | ------ | ------ | ------ |
|
||||
| Windows x64 | ✓ | ✓ | ✓ |
|
||||
| Windows x86 | ✓ | ✓ | ✓ |
|
||||
| Windows arm64 | ✓ | ✓ | ✓ |
|
||||
| macOS x64 | ✓ | ✓ | ✓ |
|
||||
| macOS aarch64 | ✓ | ✓ | ✓ |
|
||||
| Linux x64 gnu | ✓ | ✓ | ✓ |
|
||||
| Linux x64 musl | ✓ | ✓ | ✓ |
|
||||
| Linux aarch64 gnu | ✓ | ✓ | ✓ |
|
||||
| Linux aarch64 musl | ✓ | ✓ | ✓ |
|
||||
| Linux arm gnueabihf | ✓ | ✓ | ✓ |
|
||||
| Linux aarch64 android | ✓ | ✓ | ✓ |
|
||||
| FreeBSD x64 | ✓ | ✓ | ✓ |
|
||||
|
||||
This library depends on Node-API and requires `Node@10.0.0` or later.
|
||||
|
||||
We already have some packages written by `napi-rs`: [node-rs](https://github.com/napi-rs/node-rs)
|
||||
|
||||
One nice feature is that this crate allows you to build add-ons purely with the `Rust/JavaScript` toolchain and without involving `node-gyp`.
|
||||
|
||||
## Taste
|
||||
|
||||
> You can start from [package-template](https://github.com/napi-rs/package-template) to play with `napi-rs`
|
||||
|
||||
### Define JavaScript functions
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate napi;
|
||||
|
||||
// import the preludes
|
||||
use napi::bindgen_prelude::*;
|
||||
|
||||
/// module registerion is done by the runtime, no need to explicitly do it now.
|
||||
#[napi]
|
||||
fn fibonacci(n: u32) -> u32 {
|
||||
match n {
|
||||
1 | 2 => 1,
|
||||
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
|
||||
}
|
||||
}
|
||||
|
||||
/// use `Fn`, `FnMut` or `FnOnce` traits to defined JavaScript callbacks
|
||||
/// the return type of callbacks can only be `Result`.
|
||||
#[napi]
|
||||
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
|
||||
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
|
||||
}
|
||||
|
||||
/// or, define the callback signature in where clause
|
||||
#[napi]
|
||||
fn test_callback<T>(callback: T)
|
||||
where T: Fn(String) -> Result<()>
|
||||
{}
|
||||
```
|
||||
|
||||
Checkout more examples in [examples](./examples) folder
|
||||
|
||||
## Building
|
||||
|
||||
This repository is a `Cargo` crate. Any napi-based add-on should contain `Cargo.toml` to make it a Cargo crate.
|
||||
|
||||
In your `Cargo.toml` you need to set the `crate-type` to `"cdylib"` so that cargo builds a C-style shared library that can be dynamically loaded by the Node executable. You'll also need to add this crate as a dependency.
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "awesome"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
napi = "2"
|
||||
napi-derive = "2"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "1"
|
||||
```
|
||||
|
||||
And create `build.rs` in your own project:
|
||||
|
||||
```rust
|
||||
// build.rs
|
||||
extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
```
|
||||
|
||||
So far, the `napi` build script has only been tested on `macOS` `Linux` `Windows x64 MSVC` and `FreeBSD`.
|
||||
|
||||
Install the `@napi-rs/cli` to help you build your `Rust` codes and copy `Dynamic lib` file to `.node` file in case you can `require` it in your program.
|
||||
|
||||
```js
|
||||
{
|
||||
"package": "awesome-package",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^1.0.0"
|
||||
},
|
||||
"napi": {
|
||||
"name": "jarvis" // <----------- Config the name of native addon, or the napi command will use the name of `Cargo.toml` for the binary file name.
|
||||
},
|
||||
"scripts": {
|
||||
"build": "napi build --release",
|
||||
"build:debug": "napi build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then you can require your native binding:
|
||||
|
||||
```js
|
||||
require('./jarvis.node')
|
||||
```
|
||||
|
||||
The `module_name` would be your `package` name in your `Cargo.toml`.
|
||||
|
||||
`xxx => ./xxx.node`
|
||||
|
||||
`xxx-yyy => ./xxx_yyy.node`
|
||||
|
||||
You can also copy `Dynamic lib` file to an appointed location:
|
||||
|
||||
```bash
|
||||
napi build [--release] ./dll
|
||||
napi build [--release] ./artifacts
|
||||
```
|
||||
|
||||
There are [documents](./cli) which contains more details about the `@napi-rs/cli` usage.
|
||||
|
||||
## Testing
|
||||
|
||||
Because libraries that depend on this crate must be loaded into a Node executable in order to resolve symbols, all tests are written in JavaScript in the `test_module` subdirectory.
|
||||
|
||||
To run tests:
|
||||
|
||||
```sh
|
||||
yarn build:test
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Related projects
|
||||
|
||||
- [neon](https://www.neon-bindings.com)
|
||||
- [node-bindgen](https://github.com/infinyon/node-bindgen)
|
||||
|
||||
## Features table
|
||||
|
||||
| Rust Type | Node Type | [NAPI Version](https://nodejs.org/api/n-api.html#n_api_node_api_version_matrix) | Minimal Node version |
|
||||
| ----------------------- | ---------------------- | ------------------------------------------------------------------------------- | -------------------- |
|
||||
| u32 | Number | 1 | v8.0.0 |
|
||||
| i32/i64 | Number | 1 | v8.0.0 |
|
||||
| f64 | Number | 1 | v8.0.0 |
|
||||
| bool | Boolean | 1 | v8.0.0 |
|
||||
| String/&'a str | String | 1 | v8.0.0 |
|
||||
| Latin1String | String | 1 | v8.0.0 |
|
||||
| UTF16String | String | 1 | v8.0.0 |
|
||||
| Object | Object | 1 | v8.0.0 |
|
||||
| Array | Array<any> | 1 | v8.0.0 |
|
||||
| Vec<T> | Array<T> | 1 | v8.0.0 |
|
||||
| Buffer | Buffer | 1 | v8.0.0 |
|
||||
| Null | null | 1 | v8.0.0 |
|
||||
| Undefined/() | undefined | 1 | v8.0.0 |
|
||||
| Result<()> | Error | 1 | v8.0.0 |
|
||||
| T: Fn(...) -> Result<T> | function | 1 | v8.0.0 |
|
||||
| (NOT YET) | global | 1 | v8.0.0 |
|
||||
| (NOT YET) | Symbol | 1 | v8.0.0 |
|
||||
| (NOT YET) | Promise<T> | 1 | b8.5.0 |
|
||||
| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 |
|
||||
| (NOT YET) | threadsafe function | 4 | v10.6.0 |
|
||||
| (NOT YET) | BigInt | 6 | v10.7.0 |
|
|
@ -22,18 +22,15 @@ pub struct AsyncWorkPromise<'env> {
|
|||
}
|
||||
|
||||
impl<'env> AsyncWorkPromise<'env> {
|
||||
#[inline]
|
||||
pub fn promise_object(&self) -> JsObject {
|
||||
unsafe { JsObject::from_raw_unchecked(self.env.0, self.raw_promise) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cancel(self) -> Result<()> {
|
||||
check_status!(unsafe { sys::napi_cancel_async_work(self.env.0, self.napi_async_work) })
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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) })?;
|
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
|
||||
}
|
|
@ -25,12 +25,10 @@ impl<'env> CallContext<'env> {
|
|||
///
|
||||
/// If `.length > .arg_len`, then truncation has happened and some args have
|
||||
/// been lost.
|
||||
#[inline]
|
||||
fn arg_len(&self) -> usize {
|
||||
self.args.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new(
|
||||
env: &'env mut Env,
|
||||
callback_info: sys::napi_callback_info,
|
||||
|
@ -47,7 +45,6 @@ impl<'env> CallContext<'env> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get<ArgType: NapiValue>(&self, index: usize) -> Result<ArgType> {
|
||||
if index >= self.arg_len() {
|
||||
Err(Error {
|
||||
|
@ -59,7 +56,6 @@ impl<'env> CallContext<'env> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_get<ArgType: NapiValue>(&self, index: usize) -> Result<Either<ArgType, JsUndefined>> {
|
||||
if index >= self.arg_len() {
|
||||
Err(Error {
|
||||
|
@ -73,7 +69,6 @@ impl<'env> CallContext<'env> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_all(&self) -> Vec<crate::JsUnknown> {
|
||||
/* (0 .. self.arg_len()).map(|i| self.get(i).unwrap()).collect() */
|
||||
self
|
||||
|
@ -83,7 +78,6 @@ impl<'env> CallContext<'env> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_new_target<V>(&self) -> Result<V>
|
||||
where
|
||||
V: NapiValue,
|
||||
|
@ -93,12 +87,10 @@ impl<'env> CallContext<'env> {
|
|||
unsafe { V::from_raw(self.env.0, value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn this<T: NapiValue>(&self) -> Result<T> {
|
||||
unsafe { T::from_raw(self.env.0, self.raw_this) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn this_unchecked<T: NapiValue>(&self) -> T {
|
||||
unsafe { T::from_raw_unchecked(self.env.0, self.raw_this) }
|
||||
}
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
js_values::*,
|
||||
sys,
|
||||
task::Task,
|
||||
Error, ExtendedErrorInfo, NodeVersion, Result, Status,
|
||||
Error, ExtendedErrorInfo, JsError, NodeVersion, Result, Status, ValueType,
|
||||
};
|
||||
|
||||
#[cfg(feature = "napi8")]
|
||||
|
@ -59,7 +59,7 @@ static RT: Lazy<(Handle, mpsc::Sender<()>)> = Lazy::new(|| {
|
|||
#[doc(hidden)]
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
#[inline(never)]
|
||||
pub fn shutdown_tokio_rt() {
|
||||
pub extern "C" fn shutdown_tokio_rt(_arg: *mut c_void) {
|
||||
let sender = &RT.1;
|
||||
sender
|
||||
.clone()
|
||||
|
@ -80,13 +80,11 @@ pub fn shutdown_tokio_rt() {
|
|||
pub struct Env(pub(crate) sys::napi_env);
|
||||
|
||||
impl Env {
|
||||
#[inline]
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn from_raw(env: sys::napi_env) -> Self {
|
||||
Env(env)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get [JsUndefined](./struct.JsUndefined.html) value
|
||||
pub fn get_undefined(&self) -> Result<JsUndefined> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
|
@ -94,21 +92,18 @@ impl Env {
|
|||
Ok(unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_null(&self) -> Result<JsNull> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?;
|
||||
Ok(unsafe { JsNull::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) })?;
|
||||
Ok(unsafe { JsBoolean::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_int32(&self, int: i32) -> Result<JsNumber> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -117,7 +112,6 @@ impl Env {
|
|||
Ok(unsafe { JsNumber::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_int64(&self, int: i64) -> Result<JsNumber> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -126,14 +120,12 @@ impl Env {
|
|||
Ok(unsafe { JsNumber::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_uint32(&self, number: u32) -> Result<JsNumber> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_uint32(self.0, number, &mut raw_value) })?;
|
||||
Ok(unsafe { JsNumber::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_double(&self, double: f64) -> Result<JsNumber> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -144,7 +136,6 @@ impl Env {
|
|||
|
||||
/// [n_api_napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64)
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
pub fn create_bigint_from_i64(&self, value: i64) -> Result<JsBigint> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_bigint_int64(self.0, value, &mut raw_value) })?;
|
||||
|
@ -152,7 +143,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
pub fn create_bigint_from_u64(&self, value: u64) -> Result<JsBigint> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_bigint_uint64(self.0, value, &mut raw_value) })?;
|
||||
|
@ -160,7 +150,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
pub fn create_bigint_from_i128(&self, value: i128) -> Result<JsBigint> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let sign_bit = if value > 0 { 0 } else { 1 };
|
||||
|
@ -172,7 +161,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
pub fn create_bigint_from_u128(&self, value: u128) -> Result<JsBigint> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let words = &value as *const u128 as *const u64;
|
||||
|
@ -184,7 +172,6 @@ impl Env {
|
|||
///
|
||||
/// The resulting BigInt will be negative when sign_bit is true.
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
pub fn create_bigint_from_words(&self, sign_bit: bool, words: Vec<u64>) -> Result<JsBigint> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let len = words.len();
|
||||
|
@ -203,17 +190,14 @@ impl Env {
|
|||
Ok(JsBigint::from_raw_unchecked(self.0, raw_value, len))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_string(&self, s: &str) -> Result<JsString> {
|
||||
unsafe { self.create_string_from_c_char(s.as_ptr() as *const c_char, s.len()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_string_from_std(&self, s: String) -> Result<JsString> {
|
||||
unsafe { self.create_string_from_c_char(s.as_ptr() as *const c_char, s.len()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API is used for C ffi scenario.
|
||||
/// Convert raw *const c_char into JsString
|
||||
///
|
||||
|
@ -235,7 +219,6 @@ impl Env {
|
|||
Ok(JsString::from_raw_unchecked(self.0, raw_value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_string_utf16(&self, chars: &[u16]) -> Result<JsString> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -244,7 +227,6 @@ impl Env {
|
|||
Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_string_latin1(&self, chars: &[u8]) -> Result<JsString> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -258,14 +240,12 @@ impl Env {
|
|||
Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_symbol_from_js_string(&self, description: JsString) -> Result<JsSymbol> {
|
||||
let mut result = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_symbol(self.0, description.0.value, &mut result) })?;
|
||||
Ok(unsafe { JsSymbol::from_raw_unchecked(self.0, result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_symbol(&self, description: Option<&str>) -> Result<JsSymbol> {
|
||||
let mut result = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -281,28 +261,24 @@ impl Env {
|
|||
Ok(unsafe { JsSymbol::from_raw_unchecked(self.0, result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_object(&self) -> Result<JsObject> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_object(self.0, &mut raw_value) })?;
|
||||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_array(&self) -> Result<JsObject> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_array(self.0, &mut raw_value) })?;
|
||||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_array_with_length(&self, length: usize) -> Result<JsObject> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_array_with_length(self.0, length, &mut raw_value) })?;
|
||||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API allocates a node::Buffer object. While this is still a fully-supported data structure, in most cases using a TypedArray will suffice.
|
||||
pub fn create_buffer(&self, length: usize) -> Result<JsBufferValue> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
|
@ -322,7 +298,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API allocates a node::Buffer object and initializes it with data backed by the passed in buffer.
|
||||
///
|
||||
/// While this is still a fully-supported data structure, in most cases using a TypedArray will suffice.
|
||||
|
@ -350,7 +325,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// # Safety
|
||||
/// Mostly the same with `create_buffer_with_data`
|
||||
///
|
||||
|
@ -393,7 +367,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This function gives V8 an indication of the amount of externally allocated memory that is kept alive by JavaScript objects (i.e. a JavaScript object that points to its own memory allocated by a native module).
|
||||
///
|
||||
/// Registering externally allocated memory will trigger global garbage collections more often than it would otherwise.
|
||||
|
@ -405,7 +378,6 @@ impl Env {
|
|||
Ok(changed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API allocates a node::Buffer object and initializes it with data copied from the passed-in buffer.
|
||||
///
|
||||
/// While this is still a fully-supported data structure, in most cases using a TypedArray will suffice.
|
||||
|
@ -436,7 +408,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_arraybuffer(&self, length: usize) -> Result<JsArrayBufferValue> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
let mut data: Vec<u8> = Vec::with_capacity(length as usize);
|
||||
|
@ -452,7 +423,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_arraybuffer_with_data(&self, data: Vec<u8>) -> Result<JsArrayBufferValue> {
|
||||
let length = data.len();
|
||||
let mut raw_value = ptr::null_mut();
|
||||
|
@ -480,7 +450,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// # Safety
|
||||
/// Mostly the same with `create_arraybuffer_with_data`
|
||||
///
|
||||
|
@ -524,7 +493,6 @@ impl Env {
|
|||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API allows an add-on author to create a function object in native code.
|
||||
///
|
||||
/// This is the primary mechanism to allow calling into the add-on's native code from JavaScript.
|
||||
|
@ -615,7 +583,7 @@ impl Env {
|
|||
.cast::<F>()
|
||||
.as_ref()
|
||||
.expect("`napi_get_cb_info` should have yielded non-`NULL` assoc data");
|
||||
let ref mut env = Env::from_raw(raw_env);
|
||||
let env = &mut Env::from_raw(raw_env);
|
||||
let ctx = CallContext::new(env, cb_info, raw_this, raw_args, raw_args.len());
|
||||
closure(ctx).map(|ret: R| ret.raw())
|
||||
}))
|
||||
|
@ -677,7 +645,6 @@ impl Env {
|
|||
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API retrieves a napi_extended_error_info structure with information about the last error that occurred.
|
||||
///
|
||||
/// The content of the napi_extended_error_info returned is only valid up until an n-api function is called on the same env.
|
||||
|
@ -691,7 +658,6 @@ impl Env {
|
|||
unsafe { ptr::read(raw_extended_error) }.try_into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API throws a JavaScript Error with the text provided.
|
||||
pub fn throw_error(&self, msg: &str, code: Option<&str>) -> Result<()> {
|
||||
check_status!(unsafe {
|
||||
|
@ -706,7 +672,6 @@ impl Env {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API throws a JavaScript RangeError with the text provided.
|
||||
pub fn throw_range_error(&self, msg: &str, code: Option<&str>) -> Result<()> {
|
||||
check_status!(unsafe {
|
||||
|
@ -721,7 +686,6 @@ impl Env {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API throws a JavaScript TypeError with the text provided.
|
||||
pub fn throw_type_error(&self, msg: &str, code: Option<&str>) -> Result<()> {
|
||||
check_status!(unsafe {
|
||||
|
@ -736,7 +700,6 @@ impl Env {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::expect_fun_call)]
|
||||
/// In the event of an unrecoverable error in a native module
|
||||
///
|
||||
|
@ -760,7 +723,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi3")]
|
||||
#[inline]
|
||||
/// Trigger an 'uncaughtException' in JavaScript.
|
||||
///
|
||||
/// Useful if an async callback throws an exception with no way to recover.
|
||||
|
@ -771,7 +733,6 @@ impl Env {
|
|||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create JavaScript class
|
||||
pub fn define_class(
|
||||
&self,
|
||||
|
@ -801,7 +762,6 @@ impl Env {
|
|||
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wrap<T: 'static>(&self, js_object: &mut JsObject, native_object: T) -> Result<()> {
|
||||
check_status!(unsafe {
|
||||
sys::napi_wrap(
|
||||
|
@ -815,7 +775,6 @@ impl Env {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unwrap<T: 'static>(&self, js_object: &JsObject) -> Result<&mut T> {
|
||||
unsafe {
|
||||
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
|
||||
|
@ -841,7 +800,6 @@ impl Env {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unwrap_from_ref<T: 'static>(&self, js_ref: &Ref<()>) -> Result<&'static mut T> {
|
||||
unsafe {
|
||||
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
|
||||
|
@ -867,7 +825,6 @@ impl Env {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drop_wrapped<T: 'static>(&self, js_object: &mut JsObject) -> Result<()> {
|
||||
unsafe {
|
||||
let mut unknown_tagged_object = ptr::null_mut();
|
||||
|
@ -889,7 +846,6 @@ impl Env {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API create a new reference with the specified reference count to the Object passed in.
|
||||
pub fn create_reference<T>(&self, value: T) -> Result<Ref<()>>
|
||||
where
|
||||
|
@ -909,7 +865,6 @@ impl Env {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get reference value from `Ref` with type check
|
||||
///
|
||||
/// Return error if the type of `reference` provided is mismatched with `T`
|
||||
|
@ -924,7 +879,6 @@ impl Env {
|
|||
unsafe { T::from_raw(self.0, js_value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get reference value from `Ref` without type check
|
||||
///
|
||||
/// Using this API if you are sure the type of `T` is matched with provided `Ref<()>`.
|
||||
|
@ -941,7 +895,6 @@ impl Env {
|
|||
Ok(unsafe { T::from_raw_unchecked(self.0, js_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// If `size_hint` provided, `Env::adjust_external_memory` will be called under the hood.
|
||||
///
|
||||
/// If no `size_hint` provided, global garbage collections will be triggered less times than expected.
|
||||
|
@ -971,7 +924,6 @@ impl Env {
|
|||
Ok(unsafe { JsExternal::from_raw_unchecked(self.0, object_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> {
|
||||
unsafe {
|
||||
let mut unknown_tagged_object = ptr::null_mut();
|
||||
|
@ -997,7 +949,6 @@ impl Env {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_error(&self, e: Error) -> Result<JsObject> {
|
||||
let reason = e.reason;
|
||||
let reason_string = self.create_string(reason.as_str())?;
|
||||
|
@ -1008,13 +959,11 @@ impl Env {
|
|||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Run [Task](./trait.Task.html) in libuv thread pool, return [AsyncWorkPromise](./struct.AsyncWorkPromise.html)
|
||||
pub fn spawn<T: 'static + Task>(&self, task: T) -> Result<AsyncWorkPromise> {
|
||||
async_work::run(self, task)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn run_in_scope<T, F>(&self, executor: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce() -> Result<T>,
|
||||
|
@ -1028,14 +977,12 @@ impl Env {
|
|||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_global(&self) -> Result<JsGlobal> {
|
||||
let mut raw_global = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?;
|
||||
Ok(unsafe { JsGlobal::from_raw_unchecked(self.0, raw_global) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_napi_version(&self) -> Result<u32> {
|
||||
let global = self.get_global()?;
|
||||
let process: JsObject = global.get_named_property("process")?;
|
||||
|
@ -1049,7 +996,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi2")]
|
||||
#[inline]
|
||||
pub fn get_uv_event_loop(&self) -> Result<*mut sys::uv_loop_s> {
|
||||
let mut uv_loop: *mut sys::uv_loop_s = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_uv_event_loop(self.0, &mut uv_loop) })?;
|
||||
|
@ -1057,7 +1003,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi3")]
|
||||
#[inline]
|
||||
pub fn add_env_cleanup_hook<T, F>(
|
||||
&mut self,
|
||||
cleanup_data: T,
|
||||
|
@ -1083,7 +1028,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi3")]
|
||||
#[inline]
|
||||
pub fn remove_env_cleanup_hook<T>(&mut self, hook: CleanupEnvHook<T>) -> Result<()>
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -1094,7 +1038,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi4")]
|
||||
#[inline]
|
||||
pub fn create_threadsafe_function<
|
||||
T: Send,
|
||||
V: NapiRaw,
|
||||
|
@ -1109,7 +1052,6 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
#[inline]
|
||||
pub fn execute_tokio_future<
|
||||
T: 'static + Send,
|
||||
V: 'static + NapiValue,
|
||||
|
@ -1135,13 +1077,12 @@ impl Env {
|
|||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_promise) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi5")]
|
||||
#[inline]
|
||||
/// This API does not observe leap seconds; they are ignored, as ECMAScript aligns with POSIX time specification.
|
||||
///
|
||||
/// This API allocates a JavaScript Date object.
|
||||
///
|
||||
/// JavaScript Date objects are described in [Section 20.3](https://tc39.github.io/ecma262/#sec-date-objects) of the ECMAScript Language Specification.
|
||||
#[cfg(feature = "napi5")]
|
||||
pub fn create_date(&self, time: f64) -> Result<JsDate> {
|
||||
let mut js_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_date(self.0, time, &mut js_value) })?;
|
||||
|
@ -1149,7 +1090,7 @@ impl Env {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
|
||||
/// This API associates data with the currently running Agent. data can later be retrieved using `Env::get_instance_data()`.
|
||||
///
|
||||
/// Any existing data associated with the currently running Agent which was set by means of a previous call to `Env::set_instance_data()` will be overwritten.
|
||||
|
@ -1179,11 +1120,10 @@ impl Env {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
/// This API retrieves data that was previously associated with the currently running Agent via `Env::set_instance_data()`.
|
||||
///
|
||||
/// If no data is set, the call will succeed and data will be set to NULL.
|
||||
#[cfg(feature = "napi6")]
|
||||
pub fn get_instance_data<T>(&self) -> Result<Option<&'static mut T>>
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -1213,12 +1153,12 @@ impl Env {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi8")]
|
||||
/// Registers hook, which is a function of type `FnOnce(Arg)`, as a function to be run with the `arg` parameter once the current Node.js environment exits.
|
||||
///
|
||||
/// Unlike [`add_env_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_env_cleanup_hook), the hook is allowed to be asynchronous.
|
||||
///
|
||||
/// Otherwise, behavior generally matches that of [`add_env_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_env_cleanup_hook).
|
||||
#[cfg(feature = "napi8")]
|
||||
pub fn add_removable_async_cleanup_hook<Arg, F>(
|
||||
&self,
|
||||
arg: Arg,
|
||||
|
@ -1243,10 +1183,10 @@ impl Env {
|
|||
Ok(AsyncCleanupHook(handle))
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi8")]
|
||||
/// This API is very similar to [`add_removable_async_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_removable_async_cleanup_hook)
|
||||
///
|
||||
/// Use this one if you don't want remove the cleanup hook anymore.
|
||||
#[cfg(feature = "napi8")]
|
||||
pub fn add_async_cleanup_hook<Arg, F>(&self, arg: Arg, cleanup_fn: F) -> Result<()>
|
||||
where
|
||||
F: FnOnce(Arg),
|
||||
|
@ -1283,7 +1223,6 @@ impl Env {
|
|||
/// ```
|
||||
#[cfg(feature = "serde-json")]
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[inline]
|
||||
pub fn to_js_value<T>(&self, node: &T) -> Result<JsUnknown>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -1309,7 +1248,6 @@ impl Env {
|
|||
/// }
|
||||
///
|
||||
#[cfg(feature = "serde-json")]
|
||||
#[inline]
|
||||
pub fn from_js_value<T, V>(&self, value: V) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned + ?Sized,
|
||||
|
@ -1324,7 +1262,6 @@ impl Env {
|
|||
T::deserialize(&mut de)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API represents the invocation of the Strict Equality algorithm as defined in [Section 7.2.14](https://tc39.es/ecma262/#sec-strict-equality-comparison) of the ECMAScript Language Specification.
|
||||
pub fn strict_equals<A: NapiRaw, B: NapiRaw>(&self, a: A, b: B) -> Result<bool> {
|
||||
let mut result = false;
|
||||
|
@ -1332,7 +1269,6 @@ impl Env {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_node_version(&self) -> Result<NodeVersion> {
|
||||
let mut result = ptr::null();
|
||||
check_status!(unsafe { sys::napi_get_node_version(self.0, &mut result) })?;
|
||||
|
@ -1341,7 +1277,6 @@ impl Env {
|
|||
}
|
||||
|
||||
/// get raw env ptr
|
||||
#[inline]
|
||||
pub fn raw(&self) -> sys::napi_env {
|
||||
self.0
|
||||
}
|
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)*))),
|
||||
}
|
||||
}};
|
||||
}
|
|
@ -83,13 +83,11 @@ impl From<TypedArrayType> for sys::napi_typedarray_type {
|
|||
|
||||
impl JsArrayBuffer {
|
||||
#[cfg(feature = "napi7")]
|
||||
#[inline]
|
||||
pub fn detach(self) -> Result<()> {
|
||||
check_status!(unsafe { sys::napi_detach_arraybuffer(self.0.env, self.0.value) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi7")]
|
||||
#[inline]
|
||||
pub fn is_detached(&self) -> Result<bool> {
|
||||
let mut is_detached = false;
|
||||
check_status!(unsafe {
|
||||
|
@ -98,7 +96,6 @@ impl JsArrayBuffer {
|
|||
Ok(is_detached)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_value(self) -> Result<JsArrayBufferValue> {
|
||||
let mut data = ptr::null_mut();
|
||||
let mut len: usize = 0;
|
||||
|
@ -112,7 +109,6 @@ impl JsArrayBuffer {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_typedarray(
|
||||
self,
|
||||
typedarray_type: TypedArrayType,
|
||||
|
@ -137,7 +133,6 @@ impl JsArrayBuffer {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_dataview(self, length: usize, byte_offset: usize) -> Result<JsDataView> {
|
||||
let mut dataview_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -156,24 +151,20 @@ impl JsArrayBuffer {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_ref(self) -> Result<Ref<JsArrayBufferValue>> {
|
||||
Ref::new(self.0, 1, self.into_value()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsArrayBufferValue {
|
||||
#[inline]
|
||||
pub fn new(value: JsArrayBuffer, data: *mut c_void, len: usize) -> Self {
|
||||
JsArrayBufferValue { value, len, data }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_raw(self) -> JsArrayBuffer {
|
||||
self.value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_unknown(self) -> JsUnknown {
|
||||
unsafe { JsUnknown::from_raw_unchecked(self.value.0.env, self.value.0.value) }
|
||||
}
|
||||
|
@ -210,7 +201,6 @@ impl JsTypedArray {
|
|||
/// 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.
|
||||
#[inline]
|
||||
pub fn into_value(self) -> Result<JsTypedArrayValue> {
|
||||
let mut typedarray_type = 0;
|
||||
let mut len = 0u64;
|
||||
|
@ -269,7 +259,6 @@ impl_as_ref!(i64);
|
|||
impl_as_ref!(u64);
|
||||
|
||||
impl JsDataView {
|
||||
#[inline]
|
||||
pub fn into_value(self) -> Result<JsDataViewValue> {
|
||||
let mut length = 0u64;
|
||||
let mut byte_offset = 0u64;
|
|
@ -11,7 +11,6 @@ pub struct JsBigint {
|
|||
}
|
||||
|
||||
impl JsBigint {
|
||||
#[inline]
|
||||
pub(crate) fn from_raw_unchecked(
|
||||
env: sys::napi_env,
|
||||
value: sys::napi_value,
|
||||
|
@ -27,12 +26,10 @@ impl JsBigint {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_unknown(self) -> Result<JsUnknown> {
|
||||
unsafe { JsUnknown::from_raw(self.raw.env, self.raw.value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coerce_to_number(self) -> Result<JsNumber> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -45,7 +42,6 @@ impl JsBigint {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coerce_to_string(self) -> Result<JsString> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -57,7 +53,7 @@ impl JsBigint {
|
|||
value_type: ValueType::String,
|
||||
}))
|
||||
}
|
||||
#[inline]
|
||||
|
||||
pub fn coerce_to_object(self) -> Result<JsObject> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -70,7 +66,6 @@ impl JsBigint {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "napi5")]
|
||||
pub fn is_date(&self) -> Result<bool> {
|
||||
let mut is_date = true;
|
||||
|
@ -78,42 +73,36 @@ impl JsBigint {
|
|||
Ok(is_date)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn instanceof<Constructor: NapiRaw>(&self, constructor: Constructor) -> Result<bool> {
|
||||
let mut result = false;
|
||||
check_status!(unsafe {
|
||||
|
@ -199,7 +188,6 @@ impl TryFrom<JsBigint> for u64 {
|
|||
|
||||
impl JsBigint {
|
||||
/// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words
|
||||
#[inline]
|
||||
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;
|
||||
|
@ -221,7 +209,6 @@ impl JsBigint {
|
|||
Ok((sign_bit == 1, words))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_u64(&self) -> Result<(u64, bool)> {
|
||||
let mut val: u64 = 0;
|
||||
let mut loss = false;
|
||||
|
@ -232,7 +219,6 @@ impl JsBigint {
|
|||
Ok((val, loss))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_i64(&self) -> Result<(i64, bool)> {
|
||||
let mut val: i64 = 0;
|
||||
let mut loss: bool = false;
|
||||
|
@ -242,7 +228,6 @@ impl JsBigint {
|
|||
Ok((val, loss))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_i128(&mut self) -> Result<(i128, bool)> {
|
||||
let (signed, words) = self.get_words()?;
|
||||
let len = words.len();
|
||||
|
@ -254,7 +239,6 @@ impl JsBigint {
|
|||
Ok((val, len > 2))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_u128(&mut self) -> Result<(bool, u128, bool)> {
|
||||
let (signed, words) = self.get_words()?;
|
||||
let len = words.len();
|
|
@ -8,7 +8,6 @@ use crate::{sys, Error, Result};
|
|||
pub struct JsBoolean(pub(crate) Value);
|
||||
|
||||
impl JsBoolean {
|
||||
#[inline]
|
||||
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) })?;
|
|
@ -16,7 +16,6 @@ pub struct JsBufferValue {
|
|||
}
|
||||
|
||||
impl JsBuffer {
|
||||
#[inline]
|
||||
pub fn into_value(self) -> Result<JsBufferValue> {
|
||||
let mut data = ptr::null_mut();
|
||||
let mut len: usize = 0;
|
||||
|
@ -29,7 +28,6 @@ impl JsBuffer {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_ref(self) -> Result<Ref<JsBufferValue>> {
|
||||
Ref::new(self.0, 1, self.into_value()?)
|
||||
}
|
||||
|
@ -37,7 +35,6 @@ impl JsBuffer {
|
|||
|
||||
impl JsBufferValue {
|
||||
#[cfg(feature = "serde-json")]
|
||||
#[inline]
|
||||
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;
|
||||
|
@ -54,17 +51,14 @@ impl JsBufferValue {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new(value: JsBuffer, data: mem::ManuallyDrop<Vec<u8>>) -> Self {
|
||||
JsBufferValue { value, data }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_raw(self) -> JsBuffer {
|
||||
self.value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_unknown(self) -> JsUnknown {
|
||||
unsafe { JsUnknown::from_raw_unchecked(self.value.0.env, self.value.0.value) }
|
||||
}
|
|
@ -4,7 +4,6 @@ use crate::{sys, Result, Value};
|
|||
pub struct JsDate(pub(crate) Value);
|
||||
|
||||
impl JsDate {
|
||||
#[inline]
|
||||
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) })?;
|
|
@ -10,7 +10,6 @@ pub struct EscapableHandleScope<T: NapiRaw> {
|
|||
}
|
||||
|
||||
impl<T: NapiRaw> EscapableHandleScope<T> {
|
||||
#[inline]
|
||||
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) })?;
|
|
@ -22,7 +22,6 @@ pub struct JsFunction(pub(crate) Value);
|
|||
/// ```
|
||||
impl JsFunction {
|
||||
/// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function)
|
||||
#[inline]
|
||||
pub fn call<V>(&self, this: Option<&JsObject>, args: &[V]) -> Result<JsUnknown>
|
||||
where
|
||||
V: NapiRaw,
|
||||
|
@ -57,7 +56,6 @@ impl JsFunction {
|
|||
|
||||
/// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function)
|
||||
/// The same with `call`, but without arguments
|
||||
#[inline]
|
||||
pub fn call_without_args(&self, this: Option<&JsObject>) -> Result<JsUnknown> {
|
||||
let raw_this = this
|
||||
.map(|v| unsafe { v.raw() })
|
||||
|
@ -87,7 +85,6 @@ impl JsFunction {
|
|||
///
|
||||
/// 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)]
|
||||
#[inline]
|
||||
pub fn new<V>(&self, args: &[V]) -> Result<JsObject>
|
||||
where
|
||||
V: NapiRaw,
|
|
@ -8,7 +8,6 @@ pub struct JsGlobal(pub(crate) Value);
|
|||
pub struct JsTimeout(pub(crate) Value);
|
||||
|
||||
impl JsGlobal {
|
||||
#[inline]
|
||||
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
|
||||
let func: JsFunction = self.get_named_property("setInterval")?;
|
||||
func
|
||||
|
@ -31,7 +30,6 @@ impl JsGlobal {
|
|||
.and_then(|ret| ret.try_into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
|
||||
let func: JsFunction = self.get_named_property("setTimeout")?;
|
||||
func
|
||||
|
@ -47,7 +45,6 @@ impl JsGlobal {
|
|||
.and_then(|ret| ret.try_into())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> {
|
||||
let func: JsFunction = self.get_named_property("clearTimeout")?;
|
||||
func
|
|
@ -2,7 +2,7 @@ use std::convert::TryFrom;
|
|||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use crate::{check_status, sys, Callback, Error, Result, Status};
|
||||
use crate::{check_status, sys, type_of, Callback, Error, Result, Status, ValueType};
|
||||
|
||||
#[cfg(feature = "serde-json")]
|
||||
mod de;
|
||||
|
@ -17,7 +17,6 @@ mod buffer;
|
|||
#[cfg(feature = "napi5")]
|
||||
mod date;
|
||||
mod either;
|
||||
mod error;
|
||||
mod escapable_handle_scope;
|
||||
mod function;
|
||||
mod global;
|
||||
|
@ -29,7 +28,6 @@ mod tagged_object;
|
|||
mod undefined;
|
||||
mod value;
|
||||
mod value_ref;
|
||||
mod value_type;
|
||||
|
||||
pub use arraybuffer::*;
|
||||
#[cfg(feature = "napi6")]
|
||||
|
@ -41,7 +39,6 @@ pub use date::*;
|
|||
#[cfg(feature = "serde-json")]
|
||||
pub(crate) use de::De;
|
||||
pub use either::Either;
|
||||
pub use error::*;
|
||||
pub use escapable_handle_scope::EscapableHandleScope;
|
||||
pub use function::JsFunction;
|
||||
pub use global::*;
|
||||
|
@ -55,7 +52,6 @@ pub(crate) use tagged_object::TaggedObject;
|
|||
pub use undefined::JsUndefined;
|
||||
pub(crate) use value::Value;
|
||||
pub use value_ref::*;
|
||||
pub use value_type::ValueType;
|
||||
|
||||
// Value types
|
||||
|
||||
|
@ -69,19 +65,6 @@ pub struct JsSymbol(pub(crate) Value);
|
|||
|
||||
pub struct JsExternal(pub(crate) Value);
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! type_of {
|
||||
($env:expr, $value:expr) => {{
|
||||
use std::convert::TryFrom;
|
||||
use $crate::sys;
|
||||
let mut value_type = 0;
|
||||
let status = sys::napi_typeof($env, $value, &mut value_type);
|
||||
check_status!(status)
|
||||
.and_then(|_| ValueType::try_from(value_type).or_else(|_| Ok(ValueType::Unknown)))
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! impl_napi_value_trait {
|
||||
($js_value:ident, $value_type:ident) => {
|
||||
impl NapiValue for $js_value {
|
||||
|
@ -134,12 +117,10 @@ macro_rules! impl_napi_value_trait {
|
|||
macro_rules! impl_js_value_methods {
|
||||
($js_value:ident) => {
|
||||
impl $js_value {
|
||||
#[inline]
|
||||
pub fn into_unknown(self) -> JsUnknown {
|
||||
unsafe { JsUnknown::from_raw_unchecked(self.0.env, self.0.value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coerce_to_number(self) -> Result<JsNumber> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -152,7 +133,6 @@ macro_rules! impl_js_value_methods {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coerce_to_string(self) -> Result<JsString> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -165,7 +145,6 @@ macro_rules! impl_js_value_methods {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coerce_to_object(self) -> Result<JsObject> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -179,56 +158,49 @@ macro_rules! impl_js_value_methods {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi5")]
|
||||
#[inline]
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn instanceof<Constructor>(&self, constructor: Constructor) -> Result<bool>
|
||||
where
|
||||
Constructor: NapiRaw,
|
||||
|
@ -241,13 +213,11 @@ macro_rules! impl_js_value_methods {
|
|||
}
|
||||
|
||||
#[cfg(feature = "napi8")]
|
||||
#[inline]
|
||||
pub fn freeze(&mut self) -> Result<()> {
|
||||
check_status!(unsafe { sys::napi_object_freeze(self.0.env, self.0.value) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi8")]
|
||||
#[inline]
|
||||
pub fn seal(&mut self) -> Result<()> {
|
||||
check_status!(unsafe { sys::napi_object_seal(self.0.env, self.0.value) })
|
||||
}
|
||||
|
@ -258,7 +228,6 @@ macro_rules! impl_js_value_methods {
|
|||
macro_rules! impl_object_methods {
|
||||
($js_value:ident) => {
|
||||
impl $js_value {
|
||||
#[inline]
|
||||
pub fn set_property<V>(&mut self, key: JsString, value: V) -> Result<()>
|
||||
where
|
||||
V: NapiRaw,
|
||||
|
@ -268,7 +237,6 @@ macro_rules! impl_object_methods {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_property<K, T>(&self, key: K) -> Result<T>
|
||||
where
|
||||
K: NapiRaw,
|
||||
|
@ -281,7 +249,6 @@ macro_rules! impl_object_methods {
|
|||
unsafe { T::from_raw(self.0.env, raw_value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_property_unchecked<K, T>(&self, key: K) -> Result<T>
|
||||
where
|
||||
K: NapiRaw,
|
||||
|
@ -294,7 +261,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_named_property<T>(&mut self, name: &str, value: T) -> Result<()>
|
||||
where
|
||||
T: NapiRaw,
|
||||
|
@ -305,7 +271,6 @@ macro_rules! impl_object_methods {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_named_method(&mut self, name: &str, function: Callback) -> Result<()> {
|
||||
let mut js_function = ptr::null_mut();
|
||||
let len = name.len();
|
||||
|
@ -325,7 +290,6 @@ macro_rules! impl_object_methods {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_named_property<T>(&self, name: &str) -> Result<T>
|
||||
where
|
||||
T: NapiValue,
|
||||
|
@ -338,7 +302,6 @@ macro_rules! impl_object_methods {
|
|||
unsafe { T::from_raw(self.0.env, raw_value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_named_property_unchecked<T>(&self, name: &str) -> Result<T>
|
||||
where
|
||||
T: NapiValue,
|
||||
|
@ -351,7 +314,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(unsafe { T::from_raw_unchecked(self.0.env, raw_value) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_named_property(&self, name: &str) -> Result<bool> {
|
||||
let mut result = false;
|
||||
let key = CString::new(name)?;
|
||||
|
@ -361,7 +323,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn delete_property<S>(&mut self, name: S) -> Result<bool>
|
||||
where
|
||||
S: NapiRaw,
|
||||
|
@ -373,7 +334,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn delete_named_property(&mut self, name: &str) -> Result<bool> {
|
||||
let mut result = false;
|
||||
let key_str = CString::new(name)?;
|
||||
|
@ -387,7 +347,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_own_property(&self, key: &str) -> Result<bool> {
|
||||
let mut result = false;
|
||||
let string = CString::new(key)?;
|
||||
|
@ -401,7 +360,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_own_property_js<K>(&self, key: K) -> Result<bool>
|
||||
where
|
||||
K: NapiRaw,
|
||||
|
@ -413,7 +371,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_property(&self, name: &str) -> Result<bool> {
|
||||
let string = CString::new(name)?;
|
||||
let mut js_key = ptr::null_mut();
|
||||
|
@ -427,7 +384,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_property_js<K>(&self, name: K) -> Result<bool>
|
||||
where
|
||||
K: NapiRaw,
|
||||
|
@ -439,7 +395,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_property_names(&self) -> Result<JsObject> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
|
@ -451,7 +406,6 @@ macro_rules! impl_object_methods {
|
|||
/// https://nodejs.org/api/n-api.html#n_api_napi_get_all_property_names
|
||||
/// return `Array` of property names
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
pub fn get_all_property_names(
|
||||
&self,
|
||||
mode: KeyCollectionMode,
|
||||
|
@ -473,7 +427,6 @@ macro_rules! impl_object_methods {
|
|||
}
|
||||
|
||||
/// This returns the equivalent of `Object.getPrototypeOf` (which is not the same as the function's prototype property).
|
||||
#[inline]
|
||||
pub fn get_prototype<T>(&self) -> Result<T>
|
||||
where
|
||||
T: NapiValue,
|
||||
|
@ -483,7 +436,6 @@ macro_rules! impl_object_methods {
|
|||
unsafe { T::from_raw(self.0.env, result) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_prototype_unchecked<T>(&self) -> Result<T>
|
||||
where
|
||||
T: NapiValue,
|
||||
|
@ -493,7 +445,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(unsafe { T::from_raw_unchecked(self.0.env, result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_element<T>(&mut self, index: u32, value: T) -> Result<()>
|
||||
where
|
||||
T: NapiRaw,
|
||||
|
@ -503,7 +454,6 @@ macro_rules! impl_object_methods {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_element(&self, index: u32) -> Result<bool> {
|
||||
let mut result = false;
|
||||
check_status!(unsafe {
|
||||
|
@ -512,7 +462,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn delete_element(&mut self, index: u32) -> Result<bool> {
|
||||
let mut result = false;
|
||||
check_status!(unsafe {
|
||||
|
@ -521,7 +470,6 @@ macro_rules! impl_object_methods {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_element<T>(&self, index: u32) -> Result<T>
|
||||
where
|
||||
T: NapiValue,
|
||||
|
@ -533,7 +481,6 @@ macro_rules! impl_object_methods {
|
|||
unsafe { T::from_raw(self.0.env, raw_value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_element_unchecked<T>(&self, index: u32) -> Result<T>
|
||||
where
|
||||
T: NapiValue,
|
||||
|
@ -546,7 +493,6 @@ macro_rules! impl_object_methods {
|
|||
}
|
||||
|
||||
/// This method allows the efficient definition of multiple properties on a given object.
|
||||
#[inline]
|
||||
pub fn define_properties(&mut self, properties: &[Property]) -> Result<()> {
|
||||
check_status!(unsafe {
|
||||
sys::napi_define_properties(
|
||||
|
@ -564,7 +510,6 @@ macro_rules! impl_object_methods {
|
|||
|
||||
/// Perform `is_array` check before get the length
|
||||
/// if `Object` is not array, `ArrayExpected` error returned
|
||||
#[inline]
|
||||
pub fn get_array_length(&self) -> Result<u32> {
|
||||
if self.is_array()? != true {
|
||||
return Err(Error::new(
|
||||
|
@ -576,7 +521,6 @@ macro_rules! impl_object_methods {
|
|||
}
|
||||
|
||||
/// use this API if you can ensure this `Object` is `Array`
|
||||
#[inline]
|
||||
pub fn get_array_length_unchecked(&self) -> Result<u32> {
|
||||
let mut length: u32 = 0;
|
||||
check_status!(unsafe {
|
||||
|
@ -648,7 +592,6 @@ impl_napi_value_trait!(JsExternal, External);
|
|||
impl_napi_value_trait!(JsSymbol, Symbol);
|
||||
|
||||
impl NapiValue for JsUnknown {
|
||||
#[inline]
|
||||
unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
|
||||
Ok(JsUnknown(Value {
|
||||
env,
|
||||
|
@ -657,7 +600,6 @@ impl NapiValue for JsUnknown {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
|
||||
JsUnknown(Value {
|
||||
env,
|
||||
|
@ -669,7 +611,6 @@ impl NapiValue for JsUnknown {
|
|||
|
||||
impl NapiRaw for JsUnknown {
|
||||
/// get raw js value ptr
|
||||
#[inline]
|
||||
unsafe fn raw(&self) -> sys::napi_value {
|
||||
self.0.value
|
||||
}
|
||||
|
@ -677,19 +618,16 @@ impl NapiRaw for JsUnknown {
|
|||
|
||||
impl<'env> NapiRaw for &'env JsUnknown {
|
||||
/// get raw js value ptr
|
||||
#[inline]
|
||||
unsafe fn raw(&self) -> sys::napi_value {
|
||||
self.0.value
|
||||
}
|
||||
}
|
||||
|
||||
impl JsUnknown {
|
||||
#[inline]
|
||||
pub fn get_type(&self) -> Result<ValueType> {
|
||||
unsafe { type_of!(self.0.env, self.0.value) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called after `JsUnknown::get_type`
|
|
@ -8,28 +8,24 @@ use crate::{sys, Error, Result};
|
|||
pub struct JsNumber(pub(crate) Value);
|
||||
|
||||
impl JsNumber {
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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) })?;
|
|
@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback<T, Hint, F>(
|
|||
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 { value, hint, env });
|
||||
callback(FinalizeContext { env, value, hint });
|
||||
if !raw_ref.is_null() {
|
||||
let status = sys::napi_delete_reference(raw_env, raw_ref);
|
||||
debug_assert!(
|
|
@ -2,12 +2,16 @@ use std::convert::From;
|
|||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use crate::{check_status, sys, Callback, Env, NapiRaw, Result};
|
||||
use crate::{sys, Callback, Result};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Property<'env> {
|
||||
pub name: &'env str,
|
||||
pub(crate) raw_descriptor: sys::napi_property_descriptor,
|
||||
#[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)]
|
||||
|
@ -20,6 +24,12 @@ pub enum PropertyAttributes {
|
|||
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 {
|
||||
|
@ -32,61 +42,55 @@ impl From<PropertyAttributes> for sys::napi_property_attributes {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'env> Property<'env> {
|
||||
#[inline]
|
||||
pub fn new(env: &'env Env, name: &'env str) -> Result<Self> {
|
||||
let string_value = CString::new(name)?;
|
||||
let mut result = ptr::null_mut();
|
||||
check_status!(unsafe {
|
||||
sys::napi_create_string_utf8(env.0, string_value.as_ptr(), name.len(), &mut result)
|
||||
})?;
|
||||
impl Property {
|
||||
pub fn new(name: &str) -> Result<Self> {
|
||||
Ok(Property {
|
||||
name,
|
||||
raw_descriptor: sys::napi_property_descriptor {
|
||||
utf8name: ptr::null_mut(),
|
||||
name: result,
|
||||
method: None,
|
||||
getter: None,
|
||||
setter: None,
|
||||
value: ptr::null_mut(),
|
||||
attributes: sys::napi_property_attributes::napi_default,
|
||||
data: ptr::null_mut(),
|
||||
},
|
||||
name: CString::new(name)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_value<T: NapiRaw>(mut self, value: T) -> Self {
|
||||
self.raw_descriptor.value = unsafe { T::raw(&value) };
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = CString::new(name).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_method(mut self, callback: Callback) -> Self {
|
||||
self.raw_descriptor.method = Some(callback);
|
||||
self.method = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_getter(mut self, callback: Callback) -> Self {
|
||||
self.raw_descriptor.getter = Some(callback);
|
||||
self.getter = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_setter(mut self, callback: Callback) -> Self {
|
||||
self.raw_descriptor.setter = Some(callback);
|
||||
self.setter = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_property_attributes(mut self, attributes: PropertyAttributes) -> Self {
|
||||
self.raw_descriptor.attributes = attributes.into();
|
||||
self.attrs = attributes;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn raw(&self) -> sys::napi_property_descriptor {
|
||||
self.raw_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
|
||||
}
|
||||
}
|
|
@ -27,12 +27,10 @@ impl<'env> Serializer for Ser<'env> {
|
|||
type SerializeStruct = StructSerializer;
|
||||
type SerializeStructVariant = StructSerializer;
|
||||
|
||||
#[inline]
|
||||
fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
|
||||
self.0.get_boolean(v).map(|js_value| js_value.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
||||
self
|
||||
.0
|
||||
|
@ -40,54 +38,44 @@ impl<'env> Serializer for Ser<'env> {
|
|||
.map(|js_value| js_value.value.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
|
||||
self.0.create_double(v as _).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
|
||||
self.0.create_double(v).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i16(self, v: i16) -> Result<Self::Ok> {
|
||||
self.0.create_int32(v as _).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i32(self, v: i32) -> Result<Self::Ok> {
|
||||
self.0.create_int32(v).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
|
||||
self.0.create_int64(v).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_i8(self, v: i8) -> Result<Self::Ok> {
|
||||
self.0.create_int32(v as _).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
|
||||
self.0.create_uint32(v as _).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u16(self, v: u16) -> Result<Self::Ok> {
|
||||
self.0.create_uint32(v as _).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_u32(self, v: u32) -> Result<Self::Ok> {
|
||||
self.0.create_uint32(v).map(|js_number| js_number.0)
|
||||
}
|
||||
|
@ -101,13 +89,11 @@ impl<'env> Serializer for Ser<'env> {
|
|||
),
|
||||
not(feature = "napi6")
|
||||
))]
|
||||
#[inline]
|
||||
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
|
||||
self.0.create_int64(v as _).map(|js_number| js_number.0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi6")]
|
||||
#[inline]
|
||||
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
|
||||
self
|
||||
.0
|
||||
|
@ -124,13 +110,11 @@ impl<'env> Serializer for Ser<'env> {
|
|||
),
|
||||
not(feature = "napi6")
|
||||
))]
|
||||
#[inline]
|
||||
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")]
|
||||
#[inline]
|
||||
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) };
|
||||
|
@ -149,13 +133,11 @@ impl<'env> Serializer for Ser<'env> {
|
|||
),
|
||||
not(feature = "napi6")
|
||||
))]
|
||||
#[inline]
|
||||
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")]
|
||||
#[inline]
|
||||
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) };
|
||||
|
@ -165,22 +147,18 @@ impl<'env> Serializer for Ser<'env> {
|
|||
.map(|v| v.raw)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_unit(self) -> Result<Self::Ok> {
|
||||
self.0.get_null().map(|null| null.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_none(self) -> Result<Self::Ok> {
|
||||
self.0.get_null().map(|null| null.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
||||
self.0.create_string(v).map(|string| string.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -188,7 +166,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
value.serialize(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||
let env = self.0;
|
||||
let key = env.create_string("")?;
|
||||
|
@ -196,7 +173,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
Ok(MapSerializer { key, obj })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
let array = self.0.create_array_with_length(len.unwrap_or(0))?;
|
||||
Ok(SeqSerializer {
|
||||
|
@ -205,7 +181,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
|
@ -230,12 +205,10 @@ impl<'env> Serializer for Ser<'env> {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
|
||||
self.0.get_null().map(|null| null.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
|
@ -245,7 +218,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
self.0.create_string(variant).map(|string| string.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_newtype_struct<T: ?Sized>(self, _name: &'static str, value: &T) -> Result<Self::Ok>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -253,7 +225,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
value.serialize(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_newtype_variant<T: ?Sized>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
|
@ -269,7 +240,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
Ok(obj.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
||||
Ok(SeqSerializer {
|
||||
array: self.0.create_array_with_length(len)?,
|
||||
|
@ -277,7 +247,6 @@ impl<'env> Serializer for Ser<'env> {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
|
@ -289,14 +258,12 @@ impl<'env> Serializer for Ser<'env> {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
|
||||
Ok(StructSerializer {
|
||||
obj: self.0.create_object()?,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
|
@ -352,7 +319,6 @@ impl ser::SerializeTuple for SeqSerializer {
|
|||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -366,7 +332,6 @@ impl ser::SerializeTuple for SeqSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end(self) -> StdResult<Self::Ok, Self::Error> {
|
||||
Ok(self.array.0)
|
||||
}
|
||||
|
@ -377,7 +342,6 @@ impl ser::SerializeTupleStruct for SeqSerializer {
|
|||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -391,7 +355,6 @@ impl ser::SerializeTupleStruct for SeqSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end(self) -> StdResult<Self::Ok, Self::Error> {
|
||||
Ok(self.array.0)
|
||||
}
|
||||
|
@ -402,7 +365,6 @@ impl ser::SerializeTupleVariant for SeqSerializer {
|
|||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -416,7 +378,6 @@ impl ser::SerializeTupleVariant for SeqSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(self.array.0)
|
||||
}
|
||||
|
@ -432,7 +393,6 @@ impl ser::SerializeMap for MapSerializer {
|
|||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> StdResult<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -442,7 +402,6 @@ impl ser::SerializeMap for MapSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> StdResult<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -459,7 +418,6 @@ impl ser::SerializeMap for MapSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize_entry<K: ?Sized, V: ?Sized>(
|
||||
&mut self,
|
||||
key: &K,
|
||||
|
@ -477,7 +435,6 @@ impl ser::SerializeMap for MapSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(self.obj.0)
|
||||
}
|
||||
|
@ -492,7 +449,6 @@ impl ser::SerializeStruct for StructSerializer {
|
|||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> StdResult<(), Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -504,7 +460,6 @@ impl ser::SerializeStruct for StructSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(self.obj.0)
|
||||
}
|
||||
|
@ -515,7 +470,6 @@ impl ser::SerializeStructVariant for StructSerializer {
|
|||
type Ok = Value;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> StdResult<(), Error>
|
||||
where
|
||||
T: Serialize,
|
||||
|
@ -527,7 +481,6 @@ impl ser::SerializeStructVariant for StructSerializer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(self.obj.0)
|
||||
}
|
|
@ -11,33 +11,27 @@ pub struct JsStringLatin1 {
|
|||
}
|
||||
|
||||
impl JsStringLatin1 {
|
||||
#[inline]
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take(self) -> Vec<u8> {
|
||||
self.as_slice().to_vec()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_value(self) -> JsString {
|
||||
self.inner
|
||||
}
|
||||
|
||||
#[cfg(feature = "latin1")]
|
||||
#[inline]
|
||||
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());
|
|
@ -15,7 +15,6 @@ mod utf8;
|
|||
pub struct JsString(pub(crate) Value);
|
||||
|
||||
impl JsString {
|
||||
#[inline]
|
||||
pub fn utf8_len(&self) -> Result<usize> {
|
||||
let mut length = 0;
|
||||
check_status!(unsafe {
|
||||
|
@ -24,7 +23,6 @@ impl JsString {
|
|||
Ok(length as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn utf16_len(&self) -> Result<usize> {
|
||||
let mut length = 0;
|
||||
check_status!(unsafe {
|
||||
|
@ -33,7 +31,6 @@ impl JsString {
|
|||
Ok(length as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn latin1_len(&self) -> Result<usize> {
|
||||
let mut length = 0;
|
||||
check_status!(unsafe {
|
||||
|
@ -42,7 +39,6 @@ impl JsString {
|
|||
Ok(length as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_utf8(self) -> Result<JsStringUtf8> {
|
||||
let mut written_char_count = 0;
|
||||
let len = self.utf8_len()? + 1;
|
||||
|
@ -64,7 +60,6 @@ impl JsString {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_utf16(self) -> Result<JsStringUtf16> {
|
||||
let mut written_char_count = 0usize;
|
||||
let len = self.utf16_len()? + 1;
|
||||
|
@ -86,7 +81,6 @@ impl JsString {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_latin1(self) -> Result<JsStringLatin1> {
|
||||
let mut written_char_count = 0usize;
|
||||
let len = self.latin1_len()? + 1;
|
|
@ -9,7 +9,6 @@ pub struct JsStringUtf16 {
|
|||
}
|
||||
|
||||
impl JsStringUtf16 {
|
||||
#[inline]
|
||||
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)))
|
||||
|
@ -18,22 +17,18 @@ impl JsStringUtf16 {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_slice(&self) -> &[u16] {
|
||||
self.buf.as_slice()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_value(self) -> JsString {
|
||||
self.inner
|
||||
}
|
|
@ -11,39 +11,32 @@ pub struct JsStringUtf8 {
|
|||
}
|
||||
|
||||
impl JsStringUtf8 {
|
||||
#[inline]
|
||||
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)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
unsafe { CStr::from_ptr(self.buf.as_ptr()) }.to_bytes()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_owned(self) -> Result<String> {
|
||||
Ok(self.as_str()?.to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take(self) -> Vec<u8> {
|
||||
self.as_slice().to_vec()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_value(self) -> JsString {
|
||||
self.inner
|
||||
}
|
|
@ -15,7 +15,6 @@ unsafe impl<T> Send for Ref<T> {}
|
|||
unsafe impl<T> Sync for Ref<T> {}
|
||||
|
||||
impl<T> Ref<T> {
|
||||
#[inline]
|
||||
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");
|
||||
|
@ -30,13 +29,11 @@ impl<T> Ref<T> {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unref(mut self, env: Env) -> Result<u32> {
|
||||
check_status!(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#![deny(clippy::all)]
|
||||
|
||||
//! High level NodeJS [N-API](https://nodejs.org/api/n-api.html) binding
|
||||
//! 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`.
|
||||
//!
|
||||
|
@ -8,7 +8,7 @@
|
|||
//!
|
||||
//! ### napi1 ~ napi8
|
||||
//!
|
||||
//! Because `NodeJS` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for.
|
||||
//! 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)
|
||||
|
@ -77,20 +77,23 @@
|
|||
|
||||
#[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;
|
||||
mod module;
|
||||
|
||||
#[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")]
|
||||
|
@ -104,88 +107,55 @@ pub use napi_sys as sys;
|
|||
|
||||
pub use async_work::AsyncWorkPromise;
|
||||
pub use call_context::CallContext;
|
||||
|
||||
pub use env::*;
|
||||
pub use error::{Error, ExtendedErrorInfo, Result};
|
||||
pub use error::*;
|
||||
pub use js_values::*;
|
||||
pub use module::Module;
|
||||
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>>;
|
||||
|
||||
/// Deprecated
|
||||
/// register nodejs module
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// register_module!(test_module, init);
|
||||
///
|
||||
/// fn init(module: &mut Module) -> Result<()> {
|
||||
/// module.create_named_method("nativeFunction", native_function)?;
|
||||
/// }
|
||||
/// ```
|
||||
#[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]
|
||||
#[deprecated(since = "1.0.0", note = "[module_exports] macro instead")]
|
||||
macro_rules! register_module {
|
||||
($module_name:ident, $init:ident) => {
|
||||
#[inline]
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
fn check_status(code: $crate::sys::napi_status) -> Result<()> {
|
||||
use $crate::{Error, Status};
|
||||
let status = Status::from(code);
|
||||
match status {
|
||||
Status::Ok => Ok(()),
|
||||
_ => Err(Error::from_status(status)),
|
||||
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
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn napi_register_module_v1(
|
||||
raw_env: $crate::sys::napi_env,
|
||||
raw_exports: $crate::sys::napi_value,
|
||||
) -> $crate::sys::napi_value {
|
||||
use std::ffi::CString;
|
||||
use std::io::Write;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use $crate::{Env, JsObject, NapiValue};
|
||||
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
use $crate::shutdown_tokio_rt;
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
println!("`register_module` macro will deprecate soon, please migrate to [module_exports]");
|
||||
}
|
||||
|
||||
let env = Env::from_raw(raw_env);
|
||||
let mut exports: JsObject = JsObject::from_raw_unchecked(raw_env, raw_exports);
|
||||
let mut cjs_module = Module { env, exports };
|
||||
let result = $init(&mut cjs_module);
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
let hook_result = check_status(unsafe {
|
||||
$crate::sys::napi_add_env_cleanup_hook(raw_env, Some(shutdown_tokio_rt), ptr::null_mut())
|
||||
});
|
||||
#[cfg(not(all(feature = "tokio_rt", feature = "napi4")))]
|
||||
let hook_result = Ok(());
|
||||
match hook_result.and_then(move |_| result) {
|
||||
Ok(_) => cjs_module.exports.raw(),
|
||||
Err(e) => {
|
||||
unsafe {
|
||||
$crate::sys::napi_throw_error(
|
||||
raw_env,
|
||||
ptr::null(),
|
||||
CString::from_vec_unchecked(format!("Error initializing module: {}", e).into())
|
||||
.as_ptr(),
|
||||
)
|
||||
};
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
|
@ -18,7 +18,6 @@ pub struct FuturePromise<T, V: NapiRaw, F: FnOnce(&mut Env, T) -> Result<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> {
|
||||
#[inline]
|
||||
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";
|
||||
|
@ -42,7 +41,6 @@ impl<T, V: NapiRaw, F: FnOnce(&mut Env, T) -> Result<V>> FuturePromise<T, V, F>
|
|||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start(self) -> Result<TSFNValue> {
|
||||
let mut tsfn_value = ptr::null_mut();
|
||||
let async_resource_name = self.async_resource_name;
|
||||
|
@ -72,7 +70,6 @@ pub(crate) struct TSFNValue(sys::napi_threadsafe_function);
|
|||
|
||||
unsafe impl Send for TSFNValue {}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) async fn resolve_from_future<T: Send, F: Future<Output = Result<T>>>(
|
||||
tsfn_value: TSFNValue,
|
||||
fut: F,
|
|
@ -181,7 +181,6 @@ 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.
|
||||
#[inline]
|
||||
pub fn create<
|
||||
V: NapiRaw,
|
||||
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
|
|
@ -9,4 +9,4 @@
|
|||
|
||||
Low-level N-API bindings for Node.js addons written in Rust.
|
||||
|
||||
See the [napi](https://github.com/napi-rs/napi-rs) for the high-level API.
|
||||
See the [napi](https://nodejs.org/api/n-api.html) for the high-level API.
|
|
@ -58,7 +58,7 @@ pub struct uv_loop_s {
|
|||
pub type napi_deferred = *mut napi_deferred__;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum napi_property_attributes {
|
||||
napi_default = 0,
|
||||
napi_writable = 1 << 0,
|
||||
|
@ -137,7 +137,7 @@ pub type napi_finalize = Option<
|
|||
unsafe extern "C" fn(env: napi_env, finalize_data: *mut c_void, finalize_hint: *mut c_void),
|
||||
>;
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct napi_property_descriptor {
|
||||
pub utf8name: *const c_char,
|
||||
pub name: napi_value,
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
authors = ["LongYinan <lynweklm@gmail.com>"]
|
||||
edition = "2018"
|
||||
name = "napi-test-module"
|
||||
name = "napi-compat-mode-examples"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
|
@ -13,8 +13,8 @@ napi3 = ["napi/napi3"]
|
|||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
napi = {path = "../napi", features = ["tokio_rt", "serde-json", "latin1"]}
|
||||
napi-derive = {path = "../napi-derive"}
|
||||
napi = {path = "../../crates/napi", features = ["tokio_rt", "serde-json", "latin1", "compat-mode"]}
|
||||
napi-derive = {path = "../../crates/macro", features = ["compat-mode"]}
|
||||
serde = "1"
|
||||
serde_bytes = "0.11"
|
||||
serde_derive = "1"
|
||||
|
@ -22,4 +22,4 @@ serde_json = "1"
|
|||
tokio = {version = "1", features = ["default", "fs"]}
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = {path = "../build"}
|
||||
napi-build = {path = "../../crates/build"}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue