refactor(napi): redesign the JavaScript values API

This commit is contained in:
LongYinan 2020-06-21 19:10:06 +08:00 committed by LongYinan
parent a16582629c
commit 0216c55e54
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
35 changed files with 1731 additions and 1471 deletions

View file

@ -22,7 +22,9 @@
A minimal library for building compiled Node add-ons in Rust. A minimal library for building compiled Node add-ons in Rust.
This library depends on N-API and requires Node 8.9 or later. It is still pretty raw and has not been tested in a production setting. This library depends on N-API and requires Node 8.9 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 toolchain and without involving `node-gyp`. One nice feature is that this crate allows you to build add-ons purely with the Rust toolchain and without involving `node-gyp`.
@ -30,8 +32,8 @@ One nice feature is that this crate allows you to build add-ons purely with the
```rust ```rust
#[js_function(1)] // ------> arguments length, omit for zero #[js_function(1)] // ------> arguments length, omit for zero
fn fibonacci(ctx: CallContext) -> Result<Value<Number>> { fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
let n = ctx.get::<Number>(0)?.try_into()?; let n = ctx.get::<JsNumber>(0)?.try_into()?;
ctx.env.create_int64(fibonacci_native(n)) ctx.env.create_int64(fibonacci_native(n))
} }
@ -139,7 +141,7 @@ yarn test
| [napi_create_external_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_create_external_arraybuffer) | 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_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_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_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_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_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_int32](https://nodejs.org/api/n-api.html#n_api_napi_create_int32) | 1 | v8.4.0 | ✅ |
@ -169,7 +171,7 @@ yarn test
| [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_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_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_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_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_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_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_latin1](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_latin1) | 1 | v8.0.0 | ⛔️ |

View file

@ -3,26 +3,26 @@ extern crate napi_rs as napi;
#[macro_use] #[macro_use]
extern crate napi_rs_derive; extern crate napi_rs_derive;
use napi::{Any, CallContext, Env, Error, Number, Object, Result, Status, Value}; use napi::{CallContext, Error, JsNumber, JsUnknown, Module, Result, Status};
use std::convert::TryInto; use std::convert::TryInto;
register_module!(test_module, init); register_module!(napi_derive_example, init);
fn init(env: &Env, exports: &mut Value<Object>) -> Result<()> { fn init(module: &mut Module) -> Result<()> {
exports.set_named_property("testThrow", env.create_function("testThrow", test_throw)?)?; module.create_named_method("testThrow", test_throw)?;
exports.set_named_property("fibonacci", env.create_function("fibonacci", fibonacci)?)?; module.create_named_method("fibonacci", fibonacci)?;
Ok(()) Ok(())
} }
#[js_function] #[js_function]
fn test_throw(_ctx: CallContext) -> Result<Value<Any>> { fn test_throw(_ctx: CallContext) -> Result<JsUnknown> {
Err(Error::from_status(Status::GenericFailure)) Err(Error::from_status(Status::GenericFailure))
} }
#[js_function(1)] #[js_function(1)]
fn fibonacci(ctx: CallContext) -> Result<Value<Number>> { fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
let n = ctx.get::<Number>(0)?.try_into()?; let n = ctx.get::<JsNumber>(0)?.try_into()?;
ctx.env.create_int64(fibonacci_native(n)) ctx.env.create_int64(fibonacci_native(n))
} }

View file

@ -4,14 +4,30 @@
```rust ```rust
#[macro_use] #[macro_use]
extern crate napi_rs as napi;
#[macro_use]
extern crate napi_rs_derive; extern crate napi_rs_derive;
use napi_rs::{Result, Value, CallContext, Number}; use napi::{CallContext, Error, JsNumber, JsUnknown, Module, Result, Status};
use std::convert::TryInto; use std::convert::TryInto;
register_module!(napi_derive_example, init);
fn init(module: &mut Module) -> Result<()> {
module.create_named_method("testThrow", test_throw)?;
module.create_named_method("fibonacci", fibonacci)?;
Ok(())
}
#[js_function]
fn test_throw(_ctx: CallContext) -> Result<JsUnknown> {
Err(Error::from_status(Status::GenericFailure))
}
#[js_function(1)] #[js_function(1)]
fn fibonacci(ctx: CallContext) -> Result<Value<Number>> { fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
let n = ctx.get::<Number>(0)?.try_into()?; let n = ctx.get::<JsNumber>(0)?.try_into()?;
ctx.env.create_int64(fibonacci_native(n)) ctx.env.create_int64(fibonacci_native(n))
} }
@ -19,7 +35,7 @@ fn fibonacci(ctx: CallContext) -> Result<Value<Number>> {
fn fibonacci_native(n: i64) -> i64 { fn fibonacci_native(n: i64) -> i64 {
match n { match n {
1 | 2 => 1, 1 | 2 => 1,
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2) _ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
} }
} }
``` ```

View file

@ -6,7 +6,7 @@ use quote::{format_ident, quote};
use syn::fold::{fold_fn_arg, fold_signature, Fold}; use syn::fold::{fold_fn_arg, fold_signature, Fold};
use syn::parse::{Parse, ParseStream, Result}; use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token}; use syn::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token, Visibility};
struct ArgLength { struct ArgLength {
length: Option<Literal>, length: Option<Literal>,
@ -26,6 +26,7 @@ struct JsFunction {
name: Option<Ident>, name: Option<Ident>,
signature: Option<Signature>, signature: Option<Signature>,
block: Vec<Block>, block: Vec<Block>,
visibility: Visibility,
} }
impl JsFunction { impl JsFunction {
@ -34,6 +35,7 @@ impl JsFunction {
args: vec![], args: vec![],
name: None, name: None,
signature: None, signature: None,
visibility: Visibility::Inherited,
block: vec![], block: vec![],
} }
} }
@ -53,6 +55,11 @@ impl Fold for JsFunction {
fold_signature(self, signature) 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 { fn fold_block(&mut self, node: Block) -> Block {
self.block.push(node.clone()); self.block.push(node.clone());
node node
@ -69,11 +76,12 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
let fn_name = js_fn.name.unwrap(); let fn_name = js_fn.name.unwrap();
let fn_block = js_fn.block; let fn_block = js_fn.block;
let signature = js_fn.signature.unwrap(); let signature = js_fn.signature.unwrap();
let visibility = js_fn.visibility;
let new_fn_name = signature.ident.clone(); let new_fn_name = signature.ident.clone();
let expanded = quote! { let expanded = quote! {
#signature #(#fn_block)* #signature #(#fn_block)*
extern "C" fn #fn_name( #visibility extern "C" fn #fn_name(
raw_env: napi_rs::sys::napi_env, raw_env: napi_rs::sys::napi_env,
cb_info: napi_rs::sys::napi_callback_info, cb_info: napi_rs::sys::napi_callback_info,
) -> napi_rs::sys::napi_value { ) -> napi_rs::sys::napi_value {
@ -81,7 +89,7 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
use std::mem; use std::mem;
use std::os::raw::c_char; use std::os::raw::c_char;
use std::ptr; use std::ptr;
use napi_rs::{Any, Env, Status, Value, CallContext}; use napi_rs::{JsUnknown, Env, Status, NapiValue, CallContext};
let mut argc = #arg_len_span as usize; let mut argc = #arg_len_span as usize;
let mut raw_args = let mut raw_args =
unsafe { mem::MaybeUninit::<[napi_rs::sys::napi_value; #arg_len_span as usize]>::uninit().assume_init() }; unsafe { mem::MaybeUninit::<[napi_rs::sys::napi_value; #arg_len_span as usize]>::uninit().assume_init() };
@ -107,9 +115,9 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
has_error = has_error && result.is_err(); has_error = has_error && result.is_err();
match result { match result {
Ok(result) => result.into_raw(), Ok(result) => result.raw_value(),
Err(e) => { Err(e) => {
let message = format!("{:?}", e); let message = format!("{}", e);
unsafe { unsafe {
napi_rs::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char); napi_rs::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
} }

View file

@ -60,10 +60,7 @@ fn main() {
let napi_version = String::from_utf8( let napi_version = String::from_utf8(
Command::new("node") Command::new("node")
.args(&[ .args(&["-e", "console.log(process.versions.napi)"])
"-e",
"console.log(process.versions.napi)",
])
.output() .output()
.unwrap() .unwrap()
.stdout, .stdout,

View file

@ -2,7 +2,9 @@ use std::mem;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::ptr; use std::ptr;
use crate::{check_status, sys, Env, Result, Task}; use crate::error::check_status;
use crate::js_values::NapiValue;
use crate::{sys, Env, Result, Task};
pub struct AsyncWork<T: Task> { pub struct AsyncWork<T: Task> {
inner_task: T, inner_task: T,
@ -88,7 +90,7 @@ unsafe extern "C" fn complete<T: Task>(
open_handle_status == sys::napi_status::napi_ok, open_handle_status == sys::napi_status::napi_ok,
"OpenHandleScope failed" "OpenHandleScope failed"
); );
let status = sys::napi_resolve_deferred(env, deferred, v.raw_value); let status = sys::napi_resolve_deferred(env, deferred, v.raw_value());
debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed"); debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed");
} }
Err(e) => { Err(e) => {

View file

@ -1,13 +1,13 @@
use crate::{sys, Any, Env, Error, Result, Status, Value, ValueType}; use crate::{sys, Env, Error, JsUnknown, NapiValue, Result, Status};
pub struct CallContext<'env, T: ValueType = Any> { pub struct CallContext<'env, T: NapiValue = JsUnknown> {
pub env: &'env Env, pub env: &'env Env,
pub this: Value<T>, pub this: T,
args: &'env [sys::napi_value], args: &'env [sys::napi_value],
arg_len: usize, arg_len: usize,
} }
impl<'env, T: ValueType> CallContext<'env, T> { impl<'env, T: NapiValue> CallContext<'env, T> {
#[inline] #[inline]
pub fn new( pub fn new(
env: &'env Env, env: &'env Env,
@ -17,21 +17,21 @@ impl<'env, T: ValueType> CallContext<'env, T> {
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
env, env,
this: Value::<T>::from_raw(env.0, this)?, this: T::from_raw(env.0, this)?,
args, args,
arg_len, arg_len,
}) })
} }
#[inline] #[inline]
pub fn get<ArgType: ValueType>(&self, index: usize) -> Result<Value<ArgType>> { pub fn get<ArgType: NapiValue>(&self, index: usize) -> Result<ArgType> {
if index + 1 > self.arg_len { if index + 1 > self.arg_len {
Err(Error { Err(Error {
status: Status::GenericFailure, status: Status::GenericFailure,
reason: Some("Arguments index out of range".to_owned()), reason: "Arguments index out of range".to_owned(),
}) })
} else { } else {
Value::<ArgType>::from_raw(self.env.0, self.args[index]) ArgType::from_raw(self.env.0, self.args[index])
} }
} }
} }

445
napi/src/env.rs Normal file
View file

@ -0,0 +1,445 @@
use crate::task::Task;
use std::any::TypeId;
use std::convert::TryInto;
use std::mem;
use std::os::raw::c_char;
use std::os::raw::c_void;
use std::ptr;
use crate::error::check_status;
use crate::js_values::*;
use crate::{sys, AsyncWork, Error, NodeVersion, Result, Status};
pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
#[derive(Clone, Copy, Debug)]
pub struct Env(pub(crate) sys::napi_env);
impl Env {
pub fn from_raw(env: sys::napi_env) -> Self {
Env(env)
}
pub fn get_undefined(&self) -> Result<JsUndefined> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_undefined(self.0, &mut raw_value) };
check_status(status)?;
Ok(JsUndefined::from_raw_unchecked(self.0, raw_value))
}
pub fn get_null(&self) -> Result<JsNull> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_null(self.0, &mut raw_value) };
check_status(status)?;
Ok(JsNull::from_raw_unchecked(self.0, raw_value))
}
pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) };
check_status(status)?;
Ok(JsBoolean::from_raw_unchecked(self.0, raw_value))
}
pub fn create_int32(&self, int: i32) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_create_int32(self.0, int, (&mut raw_value) as *mut sys::napi_value) };
check_status(status)?;
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
pub fn create_int64(&self, int: i64) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_create_int64(self.0, int, (&mut raw_value) as *mut sys::napi_value) };
check_status(status)?;
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
pub fn create_uint32(&self, number: u32) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_uint32(self.0, number, &mut raw_value) };
check_status(status)?;
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
pub fn create_double(&self, double: f64) -> Result<JsNumber> {
let mut raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_create_double(self.0, double, (&mut raw_value) as *mut sys::napi_value) };
check_status(status)?;
Ok(JsNumber::from_raw_unchecked(self.0, raw_value))
}
pub fn create_string(&self, s: &str) -> Result<JsString> {
let mut raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_create_string_utf8(
self.0,
s.as_ptr() as *const c_char,
s.len() as u64,
&mut raw_value,
)
};
check_status(status)?;
Ok(JsString::from_raw_unchecked(self.0, raw_value))
}
pub fn create_string_utf16(&self, chars: &[u16]) -> Result<JsString> {
let mut raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_create_string_utf16(self.0, chars.as_ptr(), chars.len() as u64, &mut raw_value)
};
check_status(status)?;
Ok(JsString::from_raw_unchecked(self.0, raw_value))
}
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(JsSymbol::from_raw_unchecked(self.0, result))
}
pub fn create_symbol(&self, description: Option<&str>) -> Result<JsSymbol> {
let mut result = ptr::null_mut();
check_status(unsafe {
sys::napi_create_symbol(
self.0,
description
.and_then(|desc| self.create_string(desc).ok())
.map(|string| string.0.value)
.unwrap_or(ptr::null_mut()),
&mut result,
)
})?;
Ok(JsSymbol::from_raw_unchecked(self.0, result))
}
pub fn create_object(&self) -> Result<JsObject> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_create_object(self.0, &mut raw_value) };
check_status(status)?;
Ok(JsObject::from_raw_unchecked(self.0, raw_value))
}
pub fn create_array_with_length(&self, length: usize) -> Result<JsObject> {
let mut raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_create_array_with_length(self.0, length as u64, &mut raw_value) };
check_status(status)?;
Ok(JsObject::from_raw_unchecked(self.0, raw_value))
}
pub fn create_buffer(&self, length: u64) -> Result<JsBuffer> {
let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize);
let mut data_ptr = data.as_mut_ptr();
let status = unsafe { sys::napi_create_buffer(self.0, length, &mut data_ptr, &mut raw_value) };
check_status(status)?;
mem::forget(data);
let mut buffer = JsBuffer::from_raw_unchecked(self.0, raw_value);
buffer.data = data_ptr as *const u8;
buffer.len = length;
Ok(buffer)
}
pub fn create_buffer_with_data(&self, data: Vec<u8>) -> Result<JsBuffer> {
let length = data.len() as u64;
let mut raw_value = ptr::null_mut();
let data_ptr = data.as_ptr();
let status = unsafe {
sys::napi_create_external_buffer(
self.0,
length,
data_ptr as *mut c_void,
Some(drop_buffer),
&length as *const _ as *mut c_void,
&mut raw_value,
)
};
check_status(status)?;
let mut changed = 0;
let ajust_external_memory_status =
unsafe { sys::napi_adjust_external_memory(self.0, length as i64, &mut changed) };
check_status(ajust_external_memory_status)?;
mem::forget(data);
let mut buffer = JsBuffer::from_raw_unchecked(self.0, raw_value);
buffer.data = data_ptr as *const u8;
buffer.len = length;
Ok(buffer)
}
pub fn create_arraybuffer(&self, length: u64) -> Result<JsArrayBuffer> {
let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize);
let mut data_ptr = data.as_mut_ptr();
let status =
unsafe { sys::napi_create_arraybuffer(self.0, length, &mut data_ptr, &mut raw_value) };
check_status(status)?;
mem::forget(data);
let mut array_buffer = JsArrayBuffer::from_raw_unchecked(self.0, raw_value);
array_buffer.data = data_ptr as *const u8;
array_buffer.len = length;
Ok(array_buffer)
}
pub fn create_arraybuffer_with_data(&self, data: Vec<u8>) -> Result<JsArrayBuffer> {
let length = data.len() as u64;
let mut raw_value = ptr::null_mut();
let data_ptr = data.as_ptr();
let status = unsafe {
sys::napi_create_external_arraybuffer(
self.0,
data_ptr as *mut c_void,
length,
Some(drop_buffer),
&length as *const _ as *mut c_void,
&mut raw_value,
)
};
check_status(status)?;
let mut changed = 0;
let ajust_external_memory_status =
unsafe { sys::napi_adjust_external_memory(self.0, length as i64, &mut changed) };
check_status(ajust_external_memory_status)?;
mem::forget(data);
let mut array_buffer = JsArrayBuffer::from_raw_unchecked(self.0, raw_value);
array_buffer.data = data_ptr as *const u8;
array_buffer.len = length;
Ok(array_buffer)
}
pub fn create_function(&self, name: &str, callback: Callback) -> Result<JsFunction> {
let mut raw_result = ptr::null_mut();
let status = unsafe {
sys::napi_create_function(
self.0,
name.as_ptr() as *const c_char,
name.len() as u64,
Some(callback),
callback as *mut c_void,
&mut raw_result,
)
};
check_status(status)?;
Ok(JsFunction::from_raw_unchecked(self.0, raw_result))
}
pub fn throw(&self, error: Error) -> Result<()> {
let err_value = self.create_error(error)?.0.value;
check_status(unsafe { sys::napi_throw(self.0, err_value) })?;
Ok(())
}
pub fn throw_error(&self, msg: &str) -> Result<()> {
let status = unsafe { sys::napi_throw_error(self.0, ptr::null(), msg.as_ptr() as *const _) };
check_status(status)?;
Ok(())
}
pub fn create_reference<T: NapiValue>(&self, value: T) -> Result<Ref<T>> {
let mut raw_ref = ptr::null_mut();
let initial_ref_count = 1;
unsafe {
let status =
sys::napi_create_reference(self.0, value.raw_value(), initial_ref_count, &mut raw_ref);
check_status(status)?;
};
Ok(Ref::new(self.0, raw_ref))
}
pub fn get_reference_value<T: NapiValue>(&self, reference: &Ref<T>) -> Result<T> {
let mut raw_value = ptr::null_mut();
unsafe {
let status = sys::napi_get_reference_value(self.0, reference.ref_value, &mut raw_value);
check_status(status)?;
};
Ok(T::from_raw_unchecked(self.0, raw_value))
}
pub fn define_class(
&self,
name: &str,
constructor_cb: Callback,
properties: Vec<Property>,
) -> Result<JsFunction> {
let mut raw_result = ptr::null_mut();
let raw_properties = properties
.into_iter()
.map(|prop| prop.into_raw(self))
.collect::<Result<Vec<sys::napi_property_descriptor>>>()?;
let status = unsafe {
sys::napi_define_class(
self.0,
name.as_ptr() as *const c_char,
name.len() as u64,
Some(constructor_cb),
ptr::null_mut(),
raw_properties.len() as u64,
raw_properties.as_ptr(),
&mut raw_result,
)
};
check_status(status)?;
Ok(JsFunction::from_raw_unchecked(self.0, raw_result))
}
pub fn wrap<T: 'static>(&self, js_object: &mut JsObject, native_object: T) -> Result<()> {
let status = unsafe {
sys::napi_wrap(
self.0,
js_object.0.value,
Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void,
Some(raw_finalize::<T>),
ptr::null_mut(),
ptr::null_mut(),
)
};
check_status(status).or(Ok(()))
}
pub fn unwrap<T: 'static>(&self, js_object: &JsObject) -> Result<&mut T> {
unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
let status = sys::napi_unwrap(self.0, js_object.0.value, &mut unknown_tagged_object);
check_status(status)?;
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
if *type_id == TypeId::of::<T>() {
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
(*tagged_object).object.as_mut().ok_or(Error {
status: Status::InvalidArg,
reason: "Invalid argument, nothing attach to js_object".to_owned(),
})
} else {
Err(Error {
status: Status::InvalidArg,
reason: "Invalid argument, T on unrwap is not the type of wrapped object".to_owned(),
})
}
}
}
pub fn drop_wrapped<T: 'static>(&self, js_object: JsObject) -> Result<()> {
unsafe {
let mut unknown_tagged_object: *mut c_void = ptr::null_mut();
let status = sys::napi_unwrap(self.0, js_object.0.value, &mut unknown_tagged_object);
check_status(status)?;
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
if *type_id == TypeId::of::<T>() {
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
(*tagged_object).object = None;
Ok(())
} else {
Err(Error {
status: Status::InvalidArg,
reason: "Invalid argument, T on drop_wrapped is not the type of wrapped object"
.to_owned(),
})
}
}
}
pub fn create_external<T: 'static>(&self, native_object: T) -> Result<JsExternal> {
let mut object_value = ptr::null_mut();
let status = unsafe {
sys::napi_create_external(
self.0,
Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void,
Some(raw_finalize::<T>),
ptr::null_mut(),
&mut object_value,
)
};
check_status(status)?;
Ok(JsExternal::from_raw_unchecked(self.0, object_value))
}
pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> {
unsafe {
let mut unknown_tagged_object = ptr::null_mut();
let status =
sys::napi_get_value_external(self.0, js_external.0.value, &mut unknown_tagged_object);
check_status(status)?;
let type_id: *const TypeId = mem::transmute(unknown_tagged_object);
if *type_id == TypeId::of::<T>() {
let tagged_object: *mut TaggedObject<T> = mem::transmute(unknown_tagged_object);
(*tagged_object).object.as_mut().ok_or(Error {
status: Status::InvalidArg,
reason: "Invalid argument, nothing attach to js_external".to_owned(),
})
} else {
Err(Error {
status: Status::InvalidArg,
reason: "Invalid argument, T on get_value_external is not the type of wrapped object"
.to_owned(),
})
}
}
}
pub fn create_error(&self, e: Error) -> Result<JsObject> {
let reason = e.reason;
let reason_string = self.create_string(reason.as_str())?;
let mut result = ptr::null_mut();
let status = unsafe {
sys::napi_create_error(
self.0,
ptr::null_mut(),
reason_string.into_raw(),
&mut result,
)
};
check_status(status)?;
Ok(JsObject::from_raw_unchecked(self.0, result))
}
pub fn spawn<T: 'static + Task>(&self, task: T) -> Result<JsObject> {
let mut raw_promise = ptr::null_mut();
let mut raw_deferred = ptr::null_mut();
check_status(unsafe { sys::napi_create_promise(self.0, &mut raw_deferred, &mut raw_promise) })?;
AsyncWork::run(self.0, task, raw_deferred)?;
Ok(JsObject::from_raw_unchecked(self.0, raw_promise))
}
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) })?;
let version = unsafe { *result };
version.try_into()
}
}
unsafe extern "C" fn drop_buffer(env: sys::napi_env, finalize_data: *mut c_void, len: *mut c_void) {
let length = Box::from_raw(len as *mut u64);
let length = length.as_ref();
let length = *length as usize;
let _ = Vec::from_raw_parts(finalize_data as *mut u8, length, length);
let mut changed = 0;
let ajust_external_memory_status =
sys::napi_adjust_external_memory(env, -(length as i64), &mut changed);
debug_assert!(Status::from(ajust_external_memory_status) == Status::Ok);
}
unsafe extern "C" fn raw_finalize<T>(
_raw_env: sys::napi_env,
finalize_data: *mut c_void,
_finalize_hint: *mut c_void,
) {
let tagged_object: *mut TaggedObject<T> = mem::transmute(finalize_data);
Box::from_raw(tagged_object);
}

78
napi/src/error.rs Normal file
View file

@ -0,0 +1,78 @@
use std::fmt;
use std::os::raw::c_char;
use std::ptr;
use crate::{sys, Status};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone)]
pub struct Error {
pub status: Status,
pub reason: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}: {}", self.status, self.reason)
}
}
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,
}
}
pub fn into_raw(self, env: sys::napi_env) -> sys::napi_value {
let mut err = ptr::null_mut();
let s = self.reason;
unsafe {
let mut err_reason = ptr::null_mut();
let status = sys::napi_create_string_utf8(
env,
s.as_ptr() as *const c_char,
s.len() as u64,
&mut err_reason,
);
debug_assert!(
status == sys::napi_status::napi_ok,
"Create error reason failed"
);
let status = sys::napi_create_error(env, ptr::null_mut(), err_reason, &mut err);
debug_assert!(status == sys::napi_status::napi_ok, "Create error failed");
};
err
}
}
impl From<std::ffi::NulError> for Error {
fn from(error: std::ffi::NulError) -> Self {
Error {
status: Status::StringExpected,
reason: format!("{:?}", error),
}
}
}
#[inline]
pub fn check_status(code: sys::napi_status) -> Result<()> {
let status = Status::from(code);
match status {
Status::Ok => Ok(()),
_ => Err(Error::from_status(status)),
}
}

View file

@ -0,0 +1,46 @@
use std::ptr;
use super::{JsObject, NapiValue, Value, ValueType};
use crate::error::check_status;
use crate::{sys, Result};
#[derive(Clone, Copy, Debug)]
pub struct JsArrayBuffer {
pub value: JsObject,
pub data: *const u8,
pub len: u64,
}
impl NapiValue for JsArrayBuffer {
fn raw_value(&self) -> sys::napi_value {
self.value.0.value
}
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut data = ptr::null_mut();
let mut len: u64 = 0;
let status = unsafe { sys::napi_get_arraybuffer_info(env, value, &mut data, &mut len) };
check_status(status)?;
Ok(JsArrayBuffer {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: data as *const u8,
len,
})
}
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: ptr::null(),
len: 0,
}
}
}

View file

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

View file

@ -0,0 +1,68 @@
use std::ops::{Deref, DerefMut};
use std::ptr;
use std::slice;
use super::{JsObject, NapiValue, Value, ValueType};
use crate::error::check_status;
use crate::{sys, Result};
#[derive(Clone, Copy, Debug)]
pub struct JsBuffer {
pub value: JsObject,
pub data: *const u8,
pub len: u64,
}
impl NapiValue for JsBuffer {
fn raw_value(&self) -> sys::napi_value {
self.value.0.value
}
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut data = ptr::null_mut();
let mut len: u64 = 0;
let status = unsafe { sys::napi_get_buffer_info(env, value, &mut data, &mut len) };
check_status(status)?;
Ok(JsBuffer {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: data as *const u8,
len,
})
}
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: ptr::null(),
len: 0,
}
}
}
impl AsRef<[u8]> for JsBuffer {
fn as_ref(&self) -> &[u8] {
self.deref()
}
}
impl Deref for JsBuffer {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.data, self.len as usize) }
}
}
impl DerefMut for JsBuffer {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.data as *mut _, self.len as usize) }
}
}

View file

@ -0,0 +1,47 @@
use std::ptr;
use crate::{sys, Callback, Env, NapiValue, Result};
#[derive(Clone, Debug)]
pub struct Property {
name: String,
raw_descriptor: sys::napi_property_descriptor,
}
impl Property {
pub fn new(name: &str) -> Self {
Property {
name: String::from(name),
raw_descriptor: sys::napi_property_descriptor {
utf8name: ptr::null_mut(),
name: ptr::null_mut(),
method: None,
getter: None,
setter: None,
value: ptr::null_mut(),
attributes: sys::napi_property_attributes::napi_default,
data: ptr::null_mut(),
},
}
}
pub fn with_value<T: NapiValue>(mut self, value: T) -> Self {
self.raw_descriptor.value = T::raw_value(&value);
self
}
pub fn with_method(mut self, callback: Callback) -> Self {
self.raw_descriptor.method = Some(callback);
self
}
pub fn with_getter(mut self, callback: Callback) -> Self {
self.raw_descriptor.getter = Some(callback);
self
}
pub(crate) fn into_raw(mut self, env: &Env) -> Result<sys::napi_property_descriptor> {
self.raw_descriptor.name = env.create_string(&self.name)?.into_raw();
Ok(self.raw_descriptor)
}
}

View file

@ -0,0 +1,44 @@
use std::mem;
use std::ptr;
use super::Value;
use crate::error::check_status;
use crate::{sys, Env, Error, JsObject, JsUnknown, NapiValue, Result, Status};
#[derive(Clone, Copy, Debug)]
pub struct JsFunction(pub(crate) Value);
impl JsFunction {
pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result<JsUnknown> {
let raw_this = this
.map(|v| v.into_raw())
.or_else(|| {
Env::from_raw(self.0.env)
.get_undefined()
.ok()
.map(|u| u.into_raw())
})
.ok_or(Error::new(
Status::Unknown,
"Get raw this failed".to_owned(),
))?;
let mut raw_args = unsafe { mem::MaybeUninit::<[sys::napi_value; 8]>::uninit().assume_init() };
for (i, arg) in args.into_iter().enumerate() {
raw_args[i] = arg.0.value;
}
let mut return_value = ptr::null_mut();
let status = unsafe {
sys::napi_call_function(
self.0.env,
raw_this,
self.0.value,
args.len() as u64,
&raw_args[0],
&mut return_value,
)
};
check_status(status)?;
JsUnknown::from_raw(self.0.env, return_value)
}
}

202
napi/src/js_values/mod.rs Normal file
View file

@ -0,0 +1,202 @@
use std::convert::From;
use std::ptr;
use crate::error::check_status;
use crate::{sys, Error, Result, Status};
mod arraybuffer;
mod boolean;
mod buffer;
mod class_property;
mod function;
mod number;
mod object;
mod string;
mod tagged_object;
mod value;
mod value_ref;
mod value_type;
pub use arraybuffer::JsArrayBuffer;
pub use boolean::JsBoolean;
pub use buffer::JsBuffer;
pub use class_property::Property;
pub use function::JsFunction;
pub use number::JsNumber;
pub use object::JsObject;
pub use string::JsString;
pub use tagged_object::TaggedObject;
pub use value::Value;
pub use value_ref::Ref;
pub use value_type::ValueType;
// Value types
#[derive(Clone, Copy, Debug)]
pub struct JsUnknown(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsUndefined(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsNull(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsBigint(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsSymbol(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsExternal(pub(crate) Value);
#[inline]
pub fn type_of(env: sys::napi_env, raw_value: sys::napi_value) -> Result<ValueType> {
unsafe {
let mut value_type = sys::napi_valuetype::napi_undefined;
check_status(sys::napi_typeof(env, raw_value, &mut value_type))?;
Ok(ValueType::from(value_type))
}
}
macro_rules! impl_napi_value_trait {
($js_value:ident, $value_type:ident) => {
impl NapiValue for $js_value {
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<$js_value> {
let value_type = type_of(env, value)?;
if value_type != $value_type {
Err(Error::new(
Status::InvalidArg,
format!("expect {:?}, got: {:?}", $value_type, value_type),
))
} else {
Ok($js_value(Value {
env,
value,
value_type: $value_type,
}))
}
}
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self(Value {
env,
value,
value_type: $value_type,
})
}
fn raw_value(&self) -> sys::napi_value {
self.0.value
}
}
};
}
macro_rules! impl_js_value_methods {
($js_value:ident) => {
impl $js_value {
pub fn into_raw(self) -> sys::napi_value {
self.0.value
}
pub fn coerce_to_number(self) -> Result<JsNumber> {
let mut new_raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_coerce_to_number(self.0.env, self.0.value, &mut new_raw_value) };
check_status(status)?;
Ok(JsNumber(Value {
env: self.0.env,
value: new_raw_value,
value_type: ValueType::Number,
}))
}
pub fn coerce_to_string(self) -> Result<JsString> {
let mut new_raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_coerce_to_string(self.0.env, self.0.value, &mut new_raw_value) };
check_status(status)?;
Ok(JsString(Value {
env: self.0.env,
value: new_raw_value,
value_type: ValueType::String,
}))
}
#[inline]
pub fn coerce_to_object(self) -> Result<JsObject> {
let mut new_raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_coerce_to_object(self.0.env, self.0.value, &mut new_raw_value) };
check_status(status)?;
Ok(JsObject(Value {
env: self.0.env,
value: new_raw_value,
value_type: ValueType::Object,
}))
}
#[inline]
pub fn is_date(&self) -> Result<bool> {
let mut is_date = true;
let status = unsafe { sys::napi_is_date(self.0.env, self.0.value, &mut is_date) };
check_status(status)?;
Ok(is_date)
}
}
};
}
pub trait NapiValue: Sized {
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self>;
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self;
fn raw_value(&self) -> sys::napi_value;
}
impl_js_value_methods!(JsUnknown);
impl_js_value_methods!(JsUndefined);
impl_js_value_methods!(JsNull);
impl_js_value_methods!(JsBoolean);
impl_js_value_methods!(JsNumber);
impl_js_value_methods!(JsString);
impl_js_value_methods!(JsObject);
impl_js_value_methods!(JsFunction);
impl_js_value_methods!(JsExternal);
impl_js_value_methods!(JsBigint);
impl_js_value_methods!(JsSymbol);
use ValueType::*;
impl_napi_value_trait!(JsUndefined, Undefined);
impl_napi_value_trait!(JsNull, Null);
impl_napi_value_trait!(JsBoolean, Boolean);
impl_napi_value_trait!(JsNumber, Number);
impl_napi_value_trait!(JsString, String);
impl_napi_value_trait!(JsObject, Object);
impl_napi_value_trait!(JsFunction, Function);
impl_napi_value_trait!(JsExternal, External);
impl_napi_value_trait!(JsBigint, Bigint);
impl_napi_value_trait!(JsSymbol, Symbol);
impl NapiValue for JsUnknown {
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
Ok(Self::from_raw_unchecked(env, value))
}
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> JsUnknown {
JsUnknown(Value {
env,
value,
value_type: Unknown,
})
}
fn raw_value(&self) -> sys::napi_value {
self.0.value
}
}
impl JsUnknown {
#[inline]
pub fn get_type(&self) -> Result<ValueType> {
type_of(self.0.env, self.0.value)
}
}

View file

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

View file

@ -0,0 +1,97 @@
use std::ffi::CString;
use std::ptr;
use super::Value;
use crate::error::check_status;
use crate::{sys, Env, Error, JsBuffer, JsNumber, JsString, NapiValue, Result, Status};
#[derive(Clone, Copy, Debug)]
pub struct JsObject(pub(crate) Value);
impl JsObject {
pub fn set_property<V: NapiValue>(&mut self, key: JsString, value: V) -> Result<()> {
let status =
unsafe { sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw_value()) };
check_status(status)?;
Ok(())
}
pub fn set_number_indexed_property<V: NapiValue>(
&mut self,
key: JsNumber,
value: V,
) -> Result<()> {
let status =
unsafe { sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw_value()) };
check_status(status)?;
Ok(())
}
pub fn set_named_property<T: NapiValue>(&mut self, name: &str, value: T) -> Result<()> {
let key = CString::new(name)?;
let status = unsafe {
sys::napi_set_named_property(self.0.env, self.0.value, key.as_ptr(), value.raw_value())
};
check_status(status)?;
Ok(())
}
pub fn get_named_property<T: NapiValue>(&self, name: &str) -> Result<T> {
let key = CString::new(name)?;
let mut raw_value = ptr::null_mut();
let status = unsafe {
sys::napi_get_named_property(self.0.env, self.0.value, key.as_ptr(), &mut raw_value)
};
check_status(status)?;
T::from_raw(self.0.env, raw_value)
}
pub fn get_property_names<T: NapiValue>(&self) -> Result<T> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_property_names(self.0.env, self.0.value, &mut raw_value) };
check_status(status)?;
T::from_raw(self.0.env, raw_value)
}
pub fn set_index<T: NapiValue>(&mut self, index: usize, value: T) -> Result<()> {
self.set_number_indexed_property(Env::from_raw(self.0.env).create_int64(index as i64)?, value)
}
pub fn get_index<T: NapiValue>(&self, index: u32) -> Result<T> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_element(self.0.env, self.0.value, index, &mut raw_value) };
check_status(status)?;
T::from_raw(self.0.env, raw_value)
}
pub fn is_array(&self) -> Result<bool> {
let mut is_array = false;
let status = unsafe { sys::napi_is_array(self.0.env, self.0.value, &mut is_array) };
check_status(status)?;
Ok(is_array)
}
pub fn is_buffer(&self) -> Result<bool> {
let mut is_buffer = false;
let status = unsafe { sys::napi_is_buffer(self.0.env, self.0.value, &mut is_buffer) };
check_status(status)?;
Ok(is_buffer)
}
pub fn to_buffer(&self) -> Result<JsBuffer> {
JsBuffer::from_raw(self.0.env, self.0.value)
}
pub fn get_array_length(&self) -> Result<u32> {
if self.is_array()? != true {
return Err(Error::new(
Status::ArrayExpected,
"Object is not array".to_owned(),
));
}
let mut length: u32 = 0;
let status = unsafe { sys::napi_get_array_length(self.0.env, self.raw_value(), &mut length) };
check_status(status)?;
Ok(length)
}
}

View file

@ -0,0 +1,115 @@
use std::convert::{TryFrom, TryInto};
use std::mem;
use std::os::raw::c_char;
use std::ptr;
use std::slice;
use std::str;
use super::Value;
use crate::error::check_status;
use crate::{sys, Error, JsNumber, NapiValue, Result, Status};
#[derive(Clone, Copy, Debug)]
pub struct JsString(pub(crate) Value);
impl JsString {
pub fn len(&self) -> Result<usize> {
let mut raw_length = ptr::null_mut();
unsafe {
let status = sys::napi_get_named_property(
self.0.env,
self.0.value,
"length\0".as_ptr() as *const c_char,
&mut raw_length,
);
check_status(status)?;
}
let length: JsNumber = JsNumber::from_raw_unchecked(self.0.env, raw_length);
length.try_into()
}
}
impl JsString {
#[inline]
pub fn get_ref(&self) -> Result<&[u8]> {
let mut written_char_count: u64 = 0;
let len = self.len()? + 1;
let mut result = Vec::with_capacity(len);
unsafe {
let status = sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
result.as_mut_ptr(),
len as u64,
&mut written_char_count,
);
check_status(status)?;
let ptr = result.as_ptr();
mem::forget(result);
Ok(slice::from_raw_parts(
ptr as *const u8,
written_char_count as usize,
))
}
}
pub fn as_str(&self) -> Result<&str> {
str::from_utf8(self.get_ref()?)
.map_err(|e| Error::new(Status::GenericFailure, format!("{:?}", e)))
}
pub fn get_ref_mut(&mut self) -> Result<&mut [u8]> {
let mut written_char_count: u64 = 0;
let len = self.len()? + 1;
let mut result = Vec::with_capacity(len);
unsafe {
let status = sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
result.as_mut_ptr(),
len as u64,
&mut written_char_count,
);
check_status(status)?;
let ptr = result.as_ptr();
mem::forget(result);
Ok(slice::from_raw_parts_mut(
ptr as *mut _,
written_char_count as usize,
))
}
}
}
impl TryFrom<JsString> for Vec<u16> {
type Error = Error;
fn try_from(value: JsString) -> Result<Vec<u16>> {
let mut result = Vec::with_capacity(value.len()? + 1); // Leave room for trailing null byte
unsafe {
let mut written_char_count = 0;
let status = sys::napi_get_value_string_utf16(
value.0.env,
value.0.value,
result.as_mut_ptr(),
result.capacity() as u64,
&mut written_char_count,
);
check_status(status)?;
result.set_len(written_char_count as usize);
}
Ok(result)
}
}
impl TryFrom<JsString> for String {
type Error = Error;
fn try_from(value: JsString) -> Result<String> {
Ok(value.as_str()?.to_owned())
}
}

View file

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

View file

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

View file

@ -0,0 +1,35 @@
use std::marker::PhantomData;
use super::NapiValue;
use crate::{sys, Status};
pub struct Ref<T: NapiValue> {
pub(crate) raw_env: sys::napi_env,
pub(crate) ref_value: sys::napi_ref,
_phantom: PhantomData<T>,
}
impl<T: NapiValue> Ref<T> {
pub fn new(raw_env: sys::napi_env, ref_value: sys::napi_ref) -> Ref<T> {
Ref {
raw_env,
ref_value,
_phantom: PhantomData,
}
}
}
impl<T: NapiValue> Drop for Ref<T> {
fn drop(&mut self) {
unsafe {
let mut ref_count = 0;
let status = sys::napi_reference_unref(self.raw_env, self.ref_value, &mut ref_count);
debug_assert!(Status::from(status) == Status::Ok);
if ref_count == 0 {
let status = sys::napi_delete_reference(self.raw_env, self.ref_value);
debug_assert!(Status::from(status) == Status::Ok);
}
}
}
}

View file

@ -0,0 +1,57 @@
use std::convert::TryInto;
use crate::{sys, Error, Result, Status};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum ValueType {
Unknown = 100,
Undefined = 0,
Null = 1,
Boolean = 2,
Number = 3,
String = 4,
Symbol = 5,
Object = 6,
Function = 7,
External = 8,
Bigint = 9,
}
impl TryInto<sys::napi_valuetype> for ValueType {
type Error = Error;
fn try_into(self) -> Result<sys::napi_valuetype> {
use sys::napi_valuetype::*;
match self {
ValueType::Unknown => Err(Error::from_status(Status::Unknown)),
ValueType::Bigint => Ok(napi_bigint),
ValueType::Boolean => Ok(napi_boolean),
ValueType::External => Ok(napi_external),
ValueType::Function => Ok(napi_function),
ValueType::Null => Ok(napi_null),
ValueType::Number => Ok(napi_number),
ValueType::Object => Ok(napi_object),
ValueType::String => Ok(napi_string),
ValueType::Symbol => Ok(napi_symbol),
ValueType::Undefined => Ok(napi_undefined),
}
}
}
impl From<sys::napi_valuetype> for ValueType {
fn from(value: sys::napi_valuetype) -> Self {
use sys::napi_valuetype::*;
match value {
napi_bigint => ValueType::Bigint,
napi_boolean => ValueType::Boolean,
napi_external => ValueType::External,
napi_function => ValueType::Function,
napi_null => ValueType::Null,
napi_number => ValueType::Number,
napi_object => ValueType::Object,
napi_string => ValueType::String,
napi_symbol => ValueType::Symbol,
napi_undefined => ValueType::Undefined,
}
}
}

File diff suppressed because it is too large Load diff

16
napi/src/module.rs Normal file
View file

@ -0,0 +1,16 @@
use crate::{Callback, Env, JsObject, Result};
pub struct Module {
pub env: Env,
pub exports: JsObject,
}
impl Module {
pub fn create_named_method(&mut self, name: &str, function: Callback) -> Result<()> {
self
.exports
.set_named_property(name, self.env.create_function(name, function)?)?;
Ok(())
}
}

View file

@ -1,10 +1,11 @@
use crate::{Env, Result, Value, ValueType}; use crate::js_values::NapiValue;
use crate::{Env, Result};
pub trait Task { pub trait Task {
type Output: Send + Sized + 'static; type Output: Send + Sized + 'static;
type JsValue: ValueType; type JsValue: NapiValue;
fn compute(&mut self) -> Result<Self::Output>; fn compute(&mut self) -> Result<Self::Output>;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Value<Self::JsValue>>; fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Self::JsValue>;
} }

View file

@ -1,18 +1,17 @@
use crate::{check_status, ptr, sys, Env, Function, Result, Value};
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::ptr;
use crate::error::check_status;
use crate::{sys, Env, JsFunction, NapiValue, Result};
use sys::napi_threadsafe_function_call_mode; use sys::napi_threadsafe_function_call_mode;
use sys::napi_threadsafe_function_release_mode; use sys::napi_threadsafe_function_release_mode;
pub trait ToJs: Copy + Clone { pub trait ToJs: Copy + Clone {
type Output; type Output;
type JsValue; type JsValue: NapiValue;
fn resolve( fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Self::JsValue)>;
&self,
env: &mut Env,
output: Self::Output,
) -> Result<(u64, Value<Self::JsValue>)>;
} }
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads. /// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
@ -26,7 +25,7 @@ pub trait ToJs: Copy + Clone {
/// ///
/// use std::thread; /// use std::thread;
/// use napi_rs::{ /// use napi_rs::{
/// Number, Result, Value, Env, CallContext, Undefined, Function, /// Number, Result, Env, CallContext, JsUndefined, JsFunction,
/// sys::{ /// sys::{
/// napi_threadsafe_function_call_mode::{ /// napi_threadsafe_function_call_mode::{
/// napi_tsfn_blocking, /// napi_tsfn_blocking,
@ -47,9 +46,9 @@ pub trait ToJs: Copy + Clone {
/// ///
/// impl ToJs for HandleNumber { /// impl ToJs for HandleNumber {
/// type Output = u8; /// type Output = u8;
/// type JsValue = Number; /// type JsValue = JsNumber;
/// ///
/// fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Value<Self::JsValue>)> { /// fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Self::JsValue)> {
/// let argv: u64 = 1; /// let argv: u64 = 1;
/// let value = env.create_uint32(output as u32)?; /// let value = env.create_uint32(output as u32)?;
/// Ok((argv, value)) /// Ok((argv, value))
@ -57,9 +56,9 @@ pub trait ToJs: Copy + Clone {
/// } /// }
/// ///
/// #[js_function(1)] /// #[js_function(1)]
/// fn test_threadsafe_function(ctx: CallContext) -> Result<Value<Undefined>> { /// fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
/// // The callback function from js which will be called in `ThreadsafeFunction::call`. /// // The callback function from js which will be called in `ThreadsafeFunction::call`.
/// let func: Value<Function> = ctx.get::<Function>(0)?; /// let func = ctx.get::<JsFunction>(0)?;
/// ///
/// let to_js = HandleNumber; /// let to_js = HandleNumber;
/// let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?; /// let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
@ -71,11 +70,11 @@ pub trait ToJs: Copy + Clone {
/// tsfn.call(Ok(output), napi_tsfn_blocking).unwrap(); /// tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
/// // We should call `ThreadsafeFunction::release` manually when we don't /// // We should call `ThreadsafeFunction::release` manually when we don't
/// // need the instance anymore, or it will prevent Node.js from exiting /// // need the instance anymore, or it will prevent Node.js from exiting
/// // automatically and possiblely cause memory leaks. /// // automatically and possibly cause memory leaks.
/// tsfn.release(napi_tsfn_release).unwrap(); /// tsfn.release(napi_tsfn_release).unwrap();
/// }); /// });
/// ///
/// Ok(Env::get_undefined(ctx.env)?) /// ctx.env.get_undefined()
/// } /// }
/// ``` /// ```
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -90,7 +89,7 @@ unsafe impl<T: ToJs> Sync for ThreadsafeFunction<T> {}
impl<T: ToJs> ThreadsafeFunction<T> { impl<T: ToJs> ThreadsafeFunction<T> {
/// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function) /// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
/// for more information. /// for more information.
pub fn create(env: &Env, func: Value<Function>, to_js: T, max_queue_size: u64) -> Result<Self> { pub fn create(env: &Env, func: JsFunction, to_js: T, max_queue_size: u64) -> Result<Self> {
let mut async_resource_name = ptr::null_mut(); let mut async_resource_name = ptr::null_mut();
let s = "napi_rs_threadsafe_function"; let s = "napi_rs_threadsafe_function";
let status = unsafe { let status = unsafe {
@ -115,7 +114,7 @@ impl<T: ToJs> ThreadsafeFunction<T> {
let status = unsafe { let status = unsafe {
sys::napi_create_threadsafe_function( sys::napi_create_threadsafe_function(
env.0, env.0,
func.raw_value, func.0.value,
ptr::null_mut(), ptr::null_mut(),
async_resource_name, async_resource_name,
max_queue_size, max_queue_size,
@ -208,7 +207,7 @@ unsafe extern "C" fn call_js_cb<T: ToJs>(
if ret.is_ok() { if ret.is_ok() {
let (argv, js_value) = ret.unwrap(); let (argv, js_value) = ret.unwrap();
let js_null = env.get_null().unwrap(); let js_null = env.get_null().unwrap();
let values = [js_null.raw_value, js_value.raw_value]; let values = [js_null.0.value, js_value.raw_value()];
status = sys::napi_call_function( status = sys::napi_call_function(
raw_env, raw_env,
recv, recv,
@ -224,7 +223,7 @@ unsafe extern "C" fn call_js_cb<T: ToJs>(
recv, recv,
js_callback, js_callback,
1, 1,
&mut err.raw_value, &mut err.0.value,
ptr::null_mut(), ptr::null_mut(),
); );
} }

View file

@ -21,7 +21,7 @@ impl TryFrom<sys::napi_node_version> for NodeVersion {
release: unsafe { release: unsafe {
CStr::from_ptr(value.release).to_str().map_err(|_| Error { CStr::from_ptr(value.release).to_str().map_err(|_| Error {
status: Status::StringExpected, status: Status::StringExpected,
reason: Some("Invalid release name".to_owned()), reason: "Invalid release name".to_owned(),
})? })?
}, },
}) })

View file

@ -0,0 +1,13 @@
const test = require('ava')
const bindings = require('../index.node')
test('should get buffer length', (t) => {
const fixture = Buffer.from('wow, hello')
t.is(bindings.getBufferLength(fixture), fixture.length)
})
test('should stringify buffer', (t) => {
const fixture = 'wow, hello'
t.is(bindings.bufferToString(Buffer.from(fixture)), fixture)
})

View file

@ -0,0 +1,22 @@
const test = require('ava')
const bindings = require('../index.node')
test('should create named symbol', (t) => {
const symbol = bindings.createNamedSymbol()
t.true(typeof symbol === 'symbol')
t.is(symbol.toString(), 'Symbol(native)')
})
test('should create unnamed symbol', (t) => {
const symbol = bindings.createUnnamedSymbol()
t.true(typeof symbol === 'symbol')
t.is(symbol.toString(), 'Symbol()')
})
test('should create symbol from JsString', (t) => {
const fixture = 'N-API Symbol'
const symbol = bindings.createSymbolFromJsString(fixture)
t.true(typeof symbol === 'symbol')
t.is(symbol.toString(), `Symbol(${fixture})`)
})

17
test_module/src/buffer.rs Normal file
View file

@ -0,0 +1,17 @@
use std::str;
use napi::{CallContext, Error, JsBuffer, JsNumber, JsString, Result, Status};
#[js_function(1)]
pub fn get_buffer_length(ctx: CallContext) -> Result<JsNumber> {
let buffer = ctx.get::<JsBuffer>(0)?;
ctx.env.create_uint32((&buffer).len() as u32)
}
#[js_function(1)]
pub fn buffer_to_string(ctx: CallContext) -> Result<JsString> {
let buffer = ctx.get::<JsBuffer>(0)?;
ctx.env.create_string(
str::from_utf8(&buffer).map_err(|e| Error::new(Status::StringExpected, format!("{}", e)))?,
)
}

View file

@ -0,0 +1,21 @@
use std::convert::TryInto;
use napi::{CallContext, JsExternal, JsNumber, Result};
struct NativeObject {
count: i32,
}
#[js_function(1)]
pub fn create_external(ctx: CallContext) -> Result<JsExternal> {
let count = ctx.get::<JsNumber>(0)?.try_into()?;
let native = NativeObject { count };
ctx.env.create_external(native)
}
#[js_function(1)]
pub fn get_external_count(ctx: CallContext) -> Result<JsNumber> {
let attached_obj = ctx.get::<JsExternal>(0)?;
let native_object = ctx.env.get_value_external::<NativeObject>(&attached_obj)?;
ctx.env.create_int32(native_object.count)
}

View file

@ -3,230 +3,56 @@ extern crate napi_rs as napi;
#[macro_use] #[macro_use]
extern crate napi_rs_derive; extern crate napi_rs_derive;
use napi::{ use napi::{CallContext, Env, Error, JsBoolean, JsString, JsUnknown, Module, Result, Status};
Any, Boolean, CallContext, Env, Error, JsString, Number, Object, Result, Status, Task, Value,
Undefined, Function, Buffer, mod buffer;
threadsafe_function::{ mod external;
ToJs, mod symbol;
ThreadsafeFunction, mod task;
} mod tsfn;
};
use napi::sys::{ use buffer::{buffer_to_string, get_buffer_length};
napi_threadsafe_function_call_mode:: { use external::{create_external, get_external_count};
napi_tsfn_blocking, use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_symbol};
}, use task::test_spawn_thread;
napi_threadsafe_function_release_mode:: { use tsfn::{test_threadsafe_function, test_tokio_readfile, test_tsfn_error};
napi_tsfn_release,
}
};
use std::convert::TryInto;
use std::thread;
use std::path::Path;
use std::ops::Deref;
use tokio;
register_module!(test_module, init); register_module!(test_module, init);
fn init(env: &Env, exports: &mut Value<Object>) -> Result<()> { fn init(module: &mut Module) -> Result<()> {
exports.set_named_property("testThrow", env.create_function("testThrow", test_throw)?)?; module.create_named_method("testThrow", test_throw)?;
exports.set_named_property( module.create_named_method("testThrowWithReason", test_throw_with_reason)?;
"testThrowWithReason", module.create_named_method("testSpawnThread", test_spawn_thread)?;
env.create_function("testThrowWithReason", test_throw_with_reason)?, module.create_named_method("testObjectIsDate", test_object_is_date)?;
)?; module.create_named_method("createExternal", create_external)?;
exports.set_named_property( module.create_named_method("getExternalCount", get_external_count)?;
"testSpawnThread", module.create_named_method("getBufferLength", get_buffer_length)?;
env.create_function("testSpawnThread", test_spawn_thread)?, module.create_named_method("bufferToString", buffer_to_string)?;
)?; module.create_named_method("createNamedSymbol", create_named_symbol)?;
exports.set_named_property( module.create_named_method("createUnnamedSymbol", create_unnamed_symbol)?;
"testObjectIsDate", module.create_named_method("createSymbolFromJsString", create_symbol_from_js_string)?;
env.create_function("testObjectIsDate", test_object_is_date)?, module.create_named_method("testTsfnError", test_tsfn_error)?;
)?; module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
exports.set_named_property( module.create_named_method("testTokioReadfile", test_tokio_readfile)?;
"createExternal",
env.create_function("createExternal", create_external)?,
)?;
exports.set_named_property(
"getExternalCount",
env.create_function("getExternalCount", get_external_count)?,
)?;
exports.set_named_property(
"testTsfnError",
env.create_function("testTsfnError", test_tsfn_error)?,
)?;
exports.set_named_property(
"testThreadsafeFunction",
env.create_function("testThreadsafeFunction", test_threadsafe_function)?
)?;
exports.set_named_property(
"testTokioReadfile",
env.create_function("testTokioReadfile", test_tokio_readfile)?
)?;
Ok(()) Ok(())
} }
struct ComputeFib {
n: u32,
}
impl ComputeFib {
pub fn new(n: u32) -> ComputeFib {
ComputeFib { n }
}
}
impl Task for ComputeFib {
type Output = u32;
type JsValue = Number;
fn compute(&mut self) -> Result<Self::Output> {
Ok(fibonacci_native(self.n))
}
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Value<Self::JsValue>> {
env.create_uint32(output)
}
}
#[inline]
fn fibonacci_native(n: u32) -> u32 {
match n {
1 | 2 => 1,
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
}
}
#[js_function(1)]
fn test_spawn_thread(ctx: CallContext) -> Result<Value<Object>> {
let n = ctx.get::<Number>(0)?;
let task = ComputeFib::new(n.try_into()?);
ctx.env.spawn(task)
}
#[js_function] #[js_function]
fn test_throw(_ctx: CallContext) -> Result<Value<Any>> { fn test_throw(_ctx: CallContext) -> Result<JsUnknown> {
Err(Error::from_status(Status::GenericFailure)) Err(Error::from_status(Status::GenericFailure))
} }
#[js_function(1)] #[js_function(1)]
fn test_throw_with_reason(ctx: CallContext) -> Result<Value<Any>> { fn test_throw_with_reason(ctx: CallContext) -> Result<JsUnknown> {
let reason = ctx.get::<JsString>(0)?; let reason = ctx.get::<JsString>(0)?;
Err(Error { Err(Error::new(
status: Status::GenericFailure, Status::GenericFailure,
reason: Some(reason.as_str()?.to_owned()), reason.as_str()?.to_owned(),
}) ))
} }
#[js_function(1)] #[js_function(1)]
fn test_object_is_date(ctx: CallContext) -> Result<Value<Boolean>> { fn test_object_is_date(ctx: CallContext) -> Result<JsBoolean> {
let obj: Value<Object> = ctx.get::<Object>(0)?; let obj = ctx.get::<JsUnknown>(0)?;
Ok(Env::get_boolean(ctx.env, obj.is_date()?)?) Env::get_boolean(ctx.env, obj.is_date()?)
}
struct NativeObject {
count: i32,
}
#[js_function(1)]
fn create_external(ctx: CallContext) -> Result<Value<Object>> {
let count = ctx.get::<Number>(0)?.try_into()?;
let native = NativeObject { count };
ctx.env.create_external(native)
}
#[js_function(1)]
fn get_external_count(ctx: CallContext) -> Result<Value<Number>> {
let attached_obj = ctx.get::<Object>(0)?;
let native_object = ctx.env.get_value_external::<NativeObject>(&attached_obj)?;
ctx.env.create_int32(native_object.count)
}
#[derive(Clone, Copy)]
struct HandleNumber;
impl ToJs for HandleNumber {
type Output = u8;
type JsValue = Number;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Value<Self::JsValue>)> {
let argv: u64 = 1;
let value = env.create_uint32(output as u32)?;
Ok((argv, value))
}
}
#[js_function(1)]
fn test_threadsafe_function(ctx: CallContext) -> Result<Value<Undefined>> {
let func: Value<Function> = ctx.get::<Function>(0)?;
let to_js = HandleNumber;
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
thread::spawn(move || {
let output: u8 = 42;
// It's okay to call a threadsafe function multiple times.
tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
tsfn.release(napi_tsfn_release).unwrap();
});
Ok(Env::get_undefined(ctx.env)?)
}
#[js_function(1)]
fn test_tsfn_error(ctx: CallContext) -> Result<Value<Undefined>> {
let func = ctx.get::<Function>(0)?;
let to_js = HandleNumber;
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
thread::spawn(move || {
tsfn.call(Err(Error {
status: napi::sys::Status::Unknown,
reason: Some(String::from("invalid")),
}), napi_tsfn_blocking).unwrap();
tsfn.release(napi_tsfn_release).unwrap();
});
Ok(Env::get_undefined(ctx.env)?)
}
#[derive(Copy, Clone)]
struct HandleBuffer;
impl ToJs for HandleBuffer {
type Output = Vec<u8>;
type JsValue = Buffer;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Value<Self::JsValue>)> {
let value = env.create_buffer_with_data(output.to_vec())?;
Ok((1u64, value))
}
}
async fn read_file_content(filepath: &Path) -> Result<Vec<u8>> {
tokio::fs::read(filepath).await.map_err(|_| Error {
status: Status::Unknown,
reason: Some(String::from("failed to read file")),
})
}
#[js_function(2)]
fn test_tokio_readfile(ctx: CallContext) -> Result<Value<Undefined>> {
let js_filepath: Value<JsString> = ctx.get::<JsString>(0)?;
let js_func: Value<Function> = ctx.get::<Function>(1)?;
let path_str = String::from(js_filepath.as_str()?);
let to_js = HandleBuffer;
let tsfn = ThreadsafeFunction::create(ctx.env, js_func, to_js, 0)?;
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async move {
let mut filepath = Path::new(path_str.deref());
let ret = read_file_content(&mut filepath).await;
let _ = tsfn.call(ret, napi_tsfn_blocking);
tsfn.release(napi_tsfn_release).unwrap();
});
Ok(Env::get_undefined(ctx.env)?)
} }

17
test_module/src/symbol.rs Normal file
View file

@ -0,0 +1,17 @@
use napi::{CallContext, JsString, JsSymbol, Result};
#[js_function]
pub fn create_named_symbol(ctx: CallContext) -> Result<JsSymbol> {
ctx.env.create_symbol(Some("native"))
}
#[js_function]
pub fn create_unnamed_symbol(ctx: CallContext) -> Result<JsSymbol> {
ctx.env.create_symbol(None)
}
#[js_function(1)]
pub fn create_symbol_from_js_string(ctx: CallContext) -> Result<JsSymbol> {
let name = ctx.get::<JsString>(0)?;
ctx.env.create_symbol_from_js_string(name)
}

41
test_module/src/task.rs Normal file
View file

@ -0,0 +1,41 @@
use std::convert::TryInto;
use napi::{CallContext, Env, JsNumber, JsObject, Result, Task};
struct ComputeFib {
n: u32,
}
impl ComputeFib {
pub fn new(n: u32) -> ComputeFib {
ComputeFib { n }
}
}
impl Task for ComputeFib {
type Output = u32;
type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> {
Ok(fibonacci_native(self.n))
}
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Self::JsValue> {
env.create_uint32(output)
}
}
#[inline]
fn fibonacci_native(n: u32) -> u32 {
match n {
1 | 2 => 1,
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
}
}
#[js_function(1)]
pub fn test_spawn_thread(ctx: CallContext) -> Result<JsObject> {
let n = ctx.get::<JsNumber>(0)?;
let task = ComputeFib::new(n.try_into()?);
ctx.env.spawn(task)
}

104
test_module/src/tsfn.rs Normal file
View file

@ -0,0 +1,104 @@
use std::path::Path;
use std::thread;
use napi::sys::{
napi_threadsafe_function_call_mode::napi_tsfn_blocking,
napi_threadsafe_function_release_mode::napi_tsfn_release,
};
use napi::threadsafe_function::{ThreadsafeFunction, ToJs};
use napi::{
CallContext, Env, Error, JsBuffer, JsFunction, JsNumber, JsString, JsUndefined, Result, Status,
};
use tokio;
#[derive(Clone, Copy)]
struct HandleNumber;
impl ToJs for HandleNumber {
type Output = u8;
type JsValue = JsNumber;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Self::JsValue)> {
let argv: u64 = 1;
let value = env.create_uint32(output as u32)?;
Ok((argv, value))
}
}
#[js_function(1)]
pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
let func = ctx.get::<JsFunction>(0)?;
let to_js = HandleNumber;
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
thread::spawn(move || {
let output: u8 = 42;
// It's okay to call a threadsafe function multiple times.
tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
tsfn.release(napi_tsfn_release).unwrap();
});
ctx.env.get_undefined()
}
#[js_function(1)]
pub fn test_tsfn_error(ctx: CallContext) -> Result<JsUndefined> {
let func = ctx.get::<JsFunction>(0)?;
let to_js = HandleNumber;
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
thread::spawn(move || {
tsfn
.call(
Err(Error::new(Status::Unknown, "invalid".to_owned())),
napi_tsfn_blocking,
)
.unwrap();
tsfn.release(napi_tsfn_release).unwrap();
});
ctx.env.get_undefined()
}
#[derive(Copy, Clone)]
struct HandleBuffer;
impl ToJs for HandleBuffer {
type Output = Vec<u8>;
type JsValue = JsBuffer;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, JsBuffer)> {
let value = env.create_buffer_with_data(output.to_vec())?;
Ok((1u64, value))
}
}
async fn read_file_content(filepath: &Path) -> Result<Vec<u8>> {
tokio::fs::read(filepath)
.await
.map_err(|_| Error::new(Status::Unknown, "failed to read file".to_owned()))
}
#[js_function(2)]
pub fn test_tokio_readfile(ctx: CallContext) -> Result<JsUndefined> {
let js_filepath = ctx.get::<JsString>(0)?;
let js_func = ctx.get::<JsFunction>(1)?;
let path_str = js_filepath.as_str()?;
let to_js = HandleBuffer;
let tsfn = ThreadsafeFunction::create(ctx.env, js_func, to_js, 0)?;
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async move {
let mut filepath = Path::new(path_str);
let ret = read_file_content(&mut filepath).await;
let _ = tsfn.call(ret, napi_tsfn_blocking);
tsfn.release(napi_tsfn_release).unwrap();
});
ctx.env.get_undefined()
}