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:
forehalo 2021-09-23 01:29:09 +08:00 committed by GitHub
parent b64677aaad
commit 2467b7139b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
203 changed files with 5308 additions and 1500 deletions

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View file

@ -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 |

View file

@ -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',
}

View file

@ -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"}

View file

@ -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),
],

View file

@ -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
View 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
View 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
View 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>,
}

View 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())
}

View 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);
}
}
}
}

View 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);
}
}
}
}
}

View 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
View 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
View 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),
}

View 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(),
}
}

View 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(", ")
}
}

View 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)
}
}
}
}
}

View 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
View 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
View 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();
}
```

View 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
View 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()
}

View 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,
)
}
})
}

View 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(&quoted).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,
}),
})
}
}

View file

@ -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
View file

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

View file

@ -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) })?;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) }
}

View file

@ -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
View file

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

View file

@ -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;

View file

@ -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();

View file

@ -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) })?;

View file

@ -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) }
}

View file

@ -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) })?;

View file

@ -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) })?;

View file

@ -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,

View file

@ -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

View file

@ -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`

View file

@ -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) })?;

View file

@ -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!(

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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());

View file

@ -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;

View file

@ -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
}

View file

@ -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
}

View file

@ -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) })?;

View file

@ -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)),
}
}
#[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()
}
}
macro_rules! assert_type_of {
($env: expr, $value:expr, $value_ty: expr) => {
$crate::type_of!($env, $value).and_then(|received_type| {
if received_type == $value_ty {
Ok(())
} else {
Err($crate::Error::new(
$crate::Status::InvalidArg,
format!(
"Expect value to be {}, but received {}",
$value_ty, received_type
),
))
}
})
};
}
pub mod bindgen_prelude {
#[cfg(feature = "compat-mode")]
pub use crate::bindgen_runtime::register_module_exports;
pub use crate::{
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
};
}

View file

@ -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,

View file

@ -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>>,

View file

@ -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.

View file

@ -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,

View file

@ -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