refactor(napi): redesign the JavaScript values API
This commit is contained in:
parent
a16582629c
commit
0216c55e54
35 changed files with 1731 additions and 1471 deletions
12
README.md
12
README.md
|
@ -22,7 +22,9 @@
|
|||
|
||||
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`.
|
||||
|
||||
|
@ -30,8 +32,8 @@ One nice feature is that this crate allows you to build add-ons purely with the
|
|||
|
||||
```rust
|
||||
#[js_function(1)] // ------> arguments length, omit for zero
|
||||
fn fibonacci(ctx: CallContext) -> Result<Value<Number>> {
|
||||
let n = ctx.get::<Number>(0)?.try_into()?;
|
||||
fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
|
||||
let n = ctx.get::<JsNumber>(0)?.try_into()?;
|
||||
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_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_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 | ✅ |
|
||||
|
@ -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_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_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 | ⛔️ |
|
||||
|
|
|
@ -3,26 +3,26 @@ extern crate napi_rs as napi;
|
|||
#[macro_use]
|
||||
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;
|
||||
|
||||
register_module!(test_module, init);
|
||||
register_module!(napi_derive_example, init);
|
||||
|
||||
fn init(env: &Env, exports: &mut Value<Object>) -> Result<()> {
|
||||
exports.set_named_property("testThrow", env.create_function("testThrow", test_throw)?)?;
|
||||
fn init(module: &mut Module) -> Result<()> {
|
||||
module.create_named_method("testThrow", test_throw)?;
|
||||
|
||||
exports.set_named_property("fibonacci", env.create_function("fibonacci", fibonacci)?)?;
|
||||
module.create_named_method("fibonacci", fibonacci)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[js_function]
|
||||
fn test_throw(_ctx: CallContext) -> Result<Value<Any>> {
|
||||
fn test_throw(_ctx: CallContext) -> Result<JsUnknown> {
|
||||
Err(Error::from_status(Status::GenericFailure))
|
||||
}
|
||||
|
||||
#[js_function(1)]
|
||||
fn fibonacci(ctx: CallContext) -> Result<Value<Number>> {
|
||||
let n = ctx.get::<Number>(0)?.try_into()?;
|
||||
fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
|
||||
let n = ctx.get::<JsNumber>(0)?.try_into()?;
|
||||
ctx.env.create_int64(fibonacci_native(n))
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,30 @@
|
|||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate napi_rs as napi;
|
||||
#[macro_use]
|
||||
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;
|
||||
|
||||
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)]
|
||||
fn fibonacci(ctx: CallContext) -> Result<Value<Number>> {
|
||||
let n = ctx.get::<Number>(0)?.try_into()?;
|
||||
fn fibonacci(ctx: CallContext) -> Result<JsNumber> {
|
||||
let n = ctx.get::<JsNumber>(0)?.try_into()?;
|
||||
ctx.env.create_int64(fibonacci_native(n))
|
||||
}
|
||||
|
||||
|
@ -19,7 +35,7 @@ fn fibonacci(ctx: CallContext) -> Result<Value<Number>> {
|
|||
fn fibonacci_native(n: i64) -> i64 {
|
||||
match n {
|
||||
1 | 2 => 1,
|
||||
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2)
|
||||
_ => fibonacci_native(n - 1) + fibonacci_native(n - 2),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -6,7 +6,7 @@ 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::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token};
|
||||
use syn::{parse_macro_input, Block, FnArg, ItemFn, Signature, Token, Visibility};
|
||||
|
||||
struct ArgLength {
|
||||
length: Option<Literal>,
|
||||
|
@ -26,6 +26,7 @@ struct JsFunction {
|
|||
name: Option<Ident>,
|
||||
signature: Option<Signature>,
|
||||
block: Vec<Block>,
|
||||
visibility: Visibility,
|
||||
}
|
||||
|
||||
impl JsFunction {
|
||||
|
@ -34,6 +35,7 @@ impl JsFunction {
|
|||
args: vec![],
|
||||
name: None,
|
||||
signature: None,
|
||||
visibility: Visibility::Inherited,
|
||||
block: vec![],
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +55,11 @@ impl Fold for JsFunction {
|
|||
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
|
||||
|
@ -69,11 +76,12 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
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 expanded = quote! {
|
||||
#signature #(#fn_block)*
|
||||
|
||||
extern "C" fn #fn_name(
|
||||
#visibility extern "C" fn #fn_name(
|
||||
raw_env: napi_rs::sys::napi_env,
|
||||
cb_info: napi_rs::sys::napi_callback_info,
|
||||
) -> napi_rs::sys::napi_value {
|
||||
|
@ -81,7 +89,7 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
use std::mem;
|
||||
use std::os::raw::c_char;
|
||||
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 raw_args =
|
||||
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();
|
||||
|
||||
match result {
|
||||
Ok(result) => result.into_raw(),
|
||||
Ok(result) => result.raw_value(),
|
||||
Err(e) => {
|
||||
let message = format!("{:?}", e);
|
||||
let message = format!("{}", e);
|
||||
unsafe {
|
||||
napi_rs::sys::napi_throw_error(raw_env, ptr::null(), message.as_ptr() as *const c_char);
|
||||
}
|
||||
|
|
|
@ -60,10 +60,7 @@ fn main() {
|
|||
|
||||
let napi_version = String::from_utf8(
|
||||
Command::new("node")
|
||||
.args(&[
|
||||
"-e",
|
||||
"console.log(process.versions.napi)",
|
||||
])
|
||||
.args(&["-e", "console.log(process.versions.napi)"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
|
|
|
@ -2,7 +2,9 @@ use std::mem;
|
|||
use std::os::raw::{c_char, c_void};
|
||||
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> {
|
||||
inner_task: T,
|
||||
|
@ -88,7 +90,7 @@ unsafe extern "C" fn complete<T: Task>(
|
|||
open_handle_status == sys::napi_status::napi_ok,
|
||||
"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");
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
|
@ -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 this: Value<T>,
|
||||
pub this: T,
|
||||
args: &'env [sys::napi_value],
|
||||
arg_len: usize,
|
||||
}
|
||||
|
||||
impl<'env, T: ValueType> CallContext<'env, T> {
|
||||
impl<'env, T: NapiValue> CallContext<'env, T> {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
env: &'env Env,
|
||||
|
@ -17,21 +17,21 @@ impl<'env, T: ValueType> CallContext<'env, T> {
|
|||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
env,
|
||||
this: Value::<T>::from_raw(env.0, this)?,
|
||||
this: T::from_raw(env.0, this)?,
|
||||
args,
|
||||
arg_len,
|
||||
})
|
||||
}
|
||||
|
||||
#[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 {
|
||||
Err(Error {
|
||||
status: Status::GenericFailure,
|
||||
reason: Some("Arguments index out of range".to_owned()),
|
||||
reason: "Arguments index out of range".to_owned(),
|
||||
})
|
||||
} 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
445
napi/src/env.rs
Normal 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
78
napi/src/error.rs
Normal 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)),
|
||||
}
|
||||
}
|
46
napi/src/js_values/arraybuffer.rs
Normal file
46
napi/src/js_values/arraybuffer.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
24
napi/src/js_values/boolean.rs
Normal file
24
napi/src/js_values/boolean.rs
Normal 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()
|
||||
}
|
||||
}
|
68
napi/src/js_values/buffer.rs
Normal file
68
napi/src/js_values/buffer.rs
Normal 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) }
|
||||
}
|
||||
}
|
47
napi/src/js_values/class_property.rs
Normal file
47
napi/src/js_values/class_property.rs
Normal 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)
|
||||
}
|
||||
}
|
44
napi/src/js_values/function.rs
Normal file
44
napi/src/js_values/function.rs
Normal 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
202
napi/src/js_values/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
63
napi/src/js_values/number.rs
Normal file
63
napi/src/js_values/number.rs
Normal 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)
|
||||
}
|
||||
}
|
97
napi/src/js_values/object.rs
Normal file
97
napi/src/js_values/object.rs
Normal 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)
|
||||
}
|
||||
}
|
115
napi/src/js_values/string.rs
Normal file
115
napi/src/js_values/string.rs
Normal 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())
|
||||
}
|
||||
}
|
16
napi/src/js_values/tagged_object.rs
Normal file
16
napi/src/js_values/tagged_object.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use std::any::TypeId;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct TaggedObject<T> {
|
||||
type_id: TypeId,
|
||||
pub(crate) object: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> TaggedObject<T> {
|
||||
pub fn new(object: T) -> Self {
|
||||
TaggedObject {
|
||||
type_id: TypeId::of::<T>(),
|
||||
object: Some(object),
|
||||
}
|
||||
}
|
||||
}
|
10
napi/src/js_values/value.rs
Normal file
10
napi/src/js_values/value.rs
Normal 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,
|
||||
}
|
35
napi/src/js_values/value_ref.rs
Normal file
35
napi/src/js_values/value_ref.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
napi/src/js_values/value_type.rs
Normal file
57
napi/src/js_values/value_type.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
1215
napi/src/lib.rs
1215
napi/src/lib.rs
File diff suppressed because it is too large
Load diff
16
napi/src/module.rs
Normal file
16
napi/src/module.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
use crate::{Env, Result, Value, ValueType};
|
||||
use crate::js_values::NapiValue;
|
||||
use crate::{Env, Result};
|
||||
|
||||
pub trait Task {
|
||||
type Output: Send + Sized + 'static;
|
||||
type JsValue: ValueType;
|
||||
type JsValue: NapiValue;
|
||||
|
||||
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>;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
use crate::{check_status, ptr, sys, Env, Function, Result, Value};
|
||||
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_release_mode;
|
||||
|
||||
pub trait ToJs: Copy + Clone {
|
||||
type Output;
|
||||
type JsValue;
|
||||
type JsValue: NapiValue;
|
||||
|
||||
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)>;
|
||||
}
|
||||
|
||||
/// 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 napi_rs::{
|
||||
/// Number, Result, Value, Env, CallContext, Undefined, Function,
|
||||
/// Number, Result, Env, CallContext, JsUndefined, JsFunction,
|
||||
/// sys::{
|
||||
/// napi_threadsafe_function_call_mode::{
|
||||
/// napi_tsfn_blocking,
|
||||
|
@ -47,9 +46,9 @@ pub trait ToJs: Copy + Clone {
|
|||
///
|
||||
/// impl ToJs for HandleNumber {
|
||||
/// 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 value = env.create_uint32(output as u32)?;
|
||||
/// Ok((argv, value))
|
||||
|
@ -57,9 +56,9 @@ pub trait ToJs: Copy + Clone {
|
|||
/// }
|
||||
///
|
||||
/// #[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`.
|
||||
/// let func: Value<Function> = ctx.get::<Function>(0)?;
|
||||
/// let func = ctx.get::<JsFunction>(0)?;
|
||||
///
|
||||
/// let to_js = HandleNumber;
|
||||
/// 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();
|
||||
/// // We should call `ThreadsafeFunction::release` manually when we don't
|
||||
/// // 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();
|
||||
/// });
|
||||
///
|
||||
/// Ok(Env::get_undefined(ctx.env)?)
|
||||
/// ctx.env.get_undefined()
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -90,7 +89,7 @@ unsafe impl<T: ToJs> Sync for 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)
|
||||
/// 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 s = "napi_rs_threadsafe_function";
|
||||
let status = unsafe {
|
||||
|
@ -115,7 +114,7 @@ impl<T: ToJs> ThreadsafeFunction<T> {
|
|||
let status = unsafe {
|
||||
sys::napi_create_threadsafe_function(
|
||||
env.0,
|
||||
func.raw_value,
|
||||
func.0.value,
|
||||
ptr::null_mut(),
|
||||
async_resource_name,
|
||||
max_queue_size,
|
||||
|
@ -208,7 +207,7 @@ unsafe extern "C" fn call_js_cb<T: ToJs>(
|
|||
if ret.is_ok() {
|
||||
let (argv, js_value) = ret.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(
|
||||
raw_env,
|
||||
recv,
|
||||
|
@ -224,7 +223,7 @@ unsafe extern "C" fn call_js_cb<T: ToJs>(
|
|||
recv,
|
||||
js_callback,
|
||||
1,
|
||||
&mut err.raw_value,
|
||||
&mut err.0.value,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ impl TryFrom<sys::napi_node_version> for NodeVersion {
|
|||
release: unsafe {
|
||||
CStr::from_ptr(value.release).to_str().map_err(|_| Error {
|
||||
status: Status::StringExpected,
|
||||
reason: Some("Invalid release name".to_owned()),
|
||||
reason: "Invalid release name".to_owned(),
|
||||
})?
|
||||
},
|
||||
})
|
||||
|
|
13
test_module/__test__/buffer.spec.js
Normal file
13
test_module/__test__/buffer.spec.js
Normal 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)
|
||||
})
|
22
test_module/__test__/symbol.spec.js
Normal file
22
test_module/__test__/symbol.spec.js
Normal 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
17
test_module/src/buffer.rs
Normal 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)))?,
|
||||
)
|
||||
}
|
21
test_module/src/external.rs
Normal file
21
test_module/src/external.rs
Normal 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)
|
||||
}
|
|
@ -3,230 +3,56 @@ extern crate napi_rs as napi;
|
|||
#[macro_use]
|
||||
extern crate napi_rs_derive;
|
||||
|
||||
use napi::{
|
||||
Any, Boolean, CallContext, Env, Error, JsString, Number, Object, Result, Status, Task, Value,
|
||||
Undefined, Function, Buffer,
|
||||
threadsafe_function::{
|
||||
ToJs,
|
||||
ThreadsafeFunction,
|
||||
}
|
||||
};
|
||||
use napi::sys::{
|
||||
napi_threadsafe_function_call_mode:: {
|
||||
napi_tsfn_blocking,
|
||||
},
|
||||
napi_threadsafe_function_release_mode:: {
|
||||
napi_tsfn_release,
|
||||
}
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
use std::thread;
|
||||
use std::path::Path;
|
||||
use std::ops::Deref;
|
||||
use tokio;
|
||||
use napi::{CallContext, Env, Error, JsBoolean, JsString, JsUnknown, Module, Result, Status};
|
||||
|
||||
mod buffer;
|
||||
mod external;
|
||||
mod symbol;
|
||||
mod task;
|
||||
mod tsfn;
|
||||
|
||||
use buffer::{buffer_to_string, get_buffer_length};
|
||||
use external::{create_external, get_external_count};
|
||||
use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_symbol};
|
||||
use task::test_spawn_thread;
|
||||
use tsfn::{test_threadsafe_function, test_tokio_readfile, test_tsfn_error};
|
||||
|
||||
register_module!(test_module, init);
|
||||
|
||||
fn init(env: &Env, exports: &mut Value<Object>) -> Result<()> {
|
||||
exports.set_named_property("testThrow", env.create_function("testThrow", test_throw)?)?;
|
||||
exports.set_named_property(
|
||||
"testThrowWithReason",
|
||||
env.create_function("testThrowWithReason", test_throw_with_reason)?,
|
||||
)?;
|
||||
exports.set_named_property(
|
||||
"testSpawnThread",
|
||||
env.create_function("testSpawnThread", test_spawn_thread)?,
|
||||
)?;
|
||||
exports.set_named_property(
|
||||
"testObjectIsDate",
|
||||
env.create_function("testObjectIsDate", test_object_is_date)?,
|
||||
)?;
|
||||
exports.set_named_property(
|
||||
"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)?
|
||||
)?;
|
||||
fn init(module: &mut Module) -> Result<()> {
|
||||
module.create_named_method("testThrow", test_throw)?;
|
||||
module.create_named_method("testThrowWithReason", test_throw_with_reason)?;
|
||||
module.create_named_method("testSpawnThread", test_spawn_thread)?;
|
||||
module.create_named_method("testObjectIsDate", test_object_is_date)?;
|
||||
module.create_named_method("createExternal", create_external)?;
|
||||
module.create_named_method("getExternalCount", get_external_count)?;
|
||||
module.create_named_method("getBufferLength", get_buffer_length)?;
|
||||
module.create_named_method("bufferToString", buffer_to_string)?;
|
||||
module.create_named_method("createNamedSymbol", create_named_symbol)?;
|
||||
module.create_named_method("createUnnamedSymbol", create_unnamed_symbol)?;
|
||||
module.create_named_method("createSymbolFromJsString", create_symbol_from_js_string)?;
|
||||
module.create_named_method("testTsfnError", test_tsfn_error)?;
|
||||
module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
|
||||
module.create_named_method("testTokioReadfile", test_tokio_readfile)?;
|
||||
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]
|
||||
fn test_throw(_ctx: CallContext) -> Result<Value<Any>> {
|
||||
fn test_throw(_ctx: CallContext) -> Result<JsUnknown> {
|
||||
Err(Error::from_status(Status::GenericFailure))
|
||||
}
|
||||
|
||||
#[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)?;
|
||||
Err(Error {
|
||||
status: Status::GenericFailure,
|
||||
reason: Some(reason.as_str()?.to_owned()),
|
||||
})
|
||||
Err(Error::new(
|
||||
Status::GenericFailure,
|
||||
reason.as_str()?.to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
#[js_function(1)]
|
||||
fn test_object_is_date(ctx: CallContext) -> Result<Value<Boolean>> {
|
||||
let obj: Value<Object> = ctx.get::<Object>(0)?;
|
||||
Ok(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)?)
|
||||
fn test_object_is_date(ctx: CallContext) -> Result<JsBoolean> {
|
||||
let obj = ctx.get::<JsUnknown>(0)?;
|
||||
Env::get_boolean(ctx.env, obj.is_date()?)
|
||||
}
|
||||
|
|
17
test_module/src/symbol.rs
Normal file
17
test_module/src/symbol.rs
Normal 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
41
test_module/src/task.rs
Normal 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
104
test_module/src/tsfn.rs
Normal 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()
|
||||
}
|
Loading…
Reference in a new issue