From 0216c55e54f316a5b3fe8e2bb34442f1a5d7a52c Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sun, 21 Jun 2020 19:10:06 +0800 Subject: [PATCH] refactor(napi): redesign the JavaScript values API --- README.md | 12 +- napi-derive-example/src/lib.rs | 16 +- napi-derive/README.md | 24 +- napi-derive/src/lib.rs | 18 +- napi/build.rs | 5 +- napi/src/async_work.rs | 6 +- napi/src/call_context.rs | 16 +- napi/src/env.rs | 445 ++++++++++ napi/src/error.rs | 78 ++ napi/src/js_values/arraybuffer.rs | 46 + napi/src/js_values/boolean.rs | 24 + napi/src/js_values/buffer.rs | 68 ++ napi/src/js_values/class_property.rs | 47 + napi/src/js_values/function.rs | 44 + napi/src/js_values/mod.rs | 202 +++++ napi/src/js_values/number.rs | 63 ++ napi/src/js_values/object.rs | 97 ++ napi/src/js_values/string.rs | 115 +++ napi/src/js_values/tagged_object.rs | 16 + napi/src/js_values/value.rs | 10 + napi/src/js_values/value_ref.rs | 35 + napi/src/js_values/value_type.rs | 57 ++ napi/src/lib.rs | 1215 +------------------------- napi/src/module.rs | 16 + napi/src/task.rs | 7 +- napi/src/threadsafe_function.rs | 35 +- napi/src/version.rs | 2 +- test_module/__test__/buffer.spec.js | 13 + test_module/__test__/symbol.spec.js | 22 + test_module/src/buffer.rs | 17 + test_module/src/external.rs | 21 + test_module/src/lib.rs | 248 +----- test_module/src/symbol.rs | 17 + test_module/src/task.rs | 41 + test_module/src/tsfn.rs | 104 +++ 35 files changed, 1731 insertions(+), 1471 deletions(-) create mode 100644 napi/src/env.rs create mode 100644 napi/src/error.rs create mode 100644 napi/src/js_values/arraybuffer.rs create mode 100644 napi/src/js_values/boolean.rs create mode 100644 napi/src/js_values/buffer.rs create mode 100644 napi/src/js_values/class_property.rs create mode 100644 napi/src/js_values/function.rs create mode 100644 napi/src/js_values/mod.rs create mode 100644 napi/src/js_values/number.rs create mode 100644 napi/src/js_values/object.rs create mode 100644 napi/src/js_values/string.rs create mode 100644 napi/src/js_values/tagged_object.rs create mode 100644 napi/src/js_values/value.rs create mode 100644 napi/src/js_values/value_ref.rs create mode 100644 napi/src/js_values/value_type.rs create mode 100644 napi/src/module.rs create mode 100644 test_module/__test__/buffer.spec.js create mode 100644 test_module/__test__/symbol.spec.js create mode 100644 test_module/src/buffer.rs create mode 100644 test_module/src/external.rs create mode 100644 test_module/src/symbol.rs create mode 100644 test_module/src/task.rs create mode 100644 test_module/src/tsfn.rs diff --git a/README.md b/README.md index 82427612..52ac88be 100644 --- a/README.md +++ b/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> { - let n = ctx.get::(0)?.try_into()?; +fn fibonacci(ctx: CallContext) -> Result { + let n = ctx.get::(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 | ⛔️ | diff --git a/napi-derive-example/src/lib.rs b/napi-derive-example/src/lib.rs index 699721a4..b283d0ae 100644 --- a/napi-derive-example/src/lib.rs +++ b/napi-derive-example/src/lib.rs @@ -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) -> 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> { +fn test_throw(_ctx: CallContext) -> Result { Err(Error::from_status(Status::GenericFailure)) } #[js_function(1)] -fn fibonacci(ctx: CallContext) -> Result> { - let n = ctx.get::(0)?.try_into()?; +fn fibonacci(ctx: CallContext) -> Result { + let n = ctx.get::(0)?.try_into()?; ctx.env.create_int64(fibonacci_native(n)) } diff --git a/napi-derive/README.md b/napi-derive/README.md index 061d57fb..6e663dfd 100644 --- a/napi-derive/README.md +++ b/napi-derive/README.md @@ -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 { + Err(Error::from_status(Status::GenericFailure)) +} + #[js_function(1)] -fn fibonacci(ctx: CallContext) -> Result> { - let n = ctx.get::(0)?.try_into()?; +fn fibonacci(ctx: CallContext) -> Result { + let n = ctx.get::(0)?.try_into()?; ctx.env.create_int64(fibonacci_native(n)) } @@ -19,7 +35,7 @@ fn fibonacci(ctx: CallContext) -> Result> { 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), } } ``` diff --git a/napi-derive/src/lib.rs b/napi-derive/src/lib.rs index 7ce7bfce..fd3cc20e 100644 --- a/napi-derive/src/lib.rs +++ b/napi-derive/src/lib.rs @@ -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, @@ -26,6 +26,7 @@ struct JsFunction { name: Option, signature: Option, block: Vec, + 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); } diff --git a/napi/build.rs b/napi/build.rs index 179683d4..9916efd9 100644 --- a/napi/build.rs +++ b/napi/build.rs @@ -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, diff --git a/napi/src/async_work.rs b/napi/src/async_work.rs index 27ceef5c..0ec502f0 100644 --- a/napi/src/async_work.rs +++ b/napi/src/async_work.rs @@ -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 { inner_task: T, @@ -88,7 +90,7 @@ unsafe extern "C" fn complete( 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) => { diff --git a/napi/src/call_context.rs b/napi/src/call_context.rs index 69518c96..9e3f475c 100644 --- a/napi/src/call_context.rs +++ b/napi/src/call_context.rs @@ -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, + 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 { Ok(Self { env, - this: Value::::from_raw(env.0, this)?, + this: T::from_raw(env.0, this)?, args, arg_len, }) } #[inline] - pub fn get(&self, index: usize) -> Result> { + pub fn get(&self, index: usize) -> Result { 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::::from_raw(self.env.0, self.args[index]) + ArgType::from_raw(self.env.0, self.args[index]) } } } diff --git a/napi/src/env.rs b/napi/src/env.rs new file mode 100644 index 00000000..4c5b1be1 --- /dev/null +++ b/napi/src/env.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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) -> Result { + 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 { + 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) -> Result { + 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 { + 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(&self, value: T) -> Result> { + 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(&self, reference: &Ref) -> Result { + 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, + ) -> Result { + let mut raw_result = ptr::null_mut(); + let raw_properties = properties + .into_iter() + .map(|prop| prop.into_raw(self)) + .collect::>>()?; + + 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(&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::), + ptr::null_mut(), + ptr::null_mut(), + ) + }; + + check_status(status).or(Ok(())) + } + + pub fn unwrap(&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::() { + let tagged_object: *mut TaggedObject = 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(&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::() { + let tagged_object: *mut TaggedObject = 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(&self, native_object: T) -> Result { + 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::), + ptr::null_mut(), + &mut object_value, + ) + }; + + check_status(status)?; + Ok(JsExternal::from_raw_unchecked(self.0, object_value)) + } + + pub fn get_value_external(&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::() { + let tagged_object: *mut TaggedObject = 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 { + 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(&self, task: T) -> Result { + 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 { + 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( + _raw_env: sys::napi_env, + finalize_data: *mut c_void, + _finalize_hint: *mut c_void, +) { + let tagged_object: *mut TaggedObject = mem::transmute(finalize_data); + Box::from_raw(tagged_object); +} diff --git a/napi/src/error.rs b/napi/src/error.rs new file mode 100644 index 00000000..c0171913 --- /dev/null +++ b/napi/src/error.rs @@ -0,0 +1,78 @@ +use std::fmt; +use std::os::raw::c_char; +use std::ptr; + +use crate::{sys, Status}; + +pub type Result = std::result::Result; + +#[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 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)), + } +} diff --git a/napi/src/js_values/arraybuffer.rs b/napi/src/js_values/arraybuffer.rs new file mode 100644 index 00000000..e131fe67 --- /dev/null +++ b/napi/src/js_values/arraybuffer.rs @@ -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 { + 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, + } + } +} diff --git a/napi/src/js_values/boolean.rs b/napi/src/js_values/boolean.rs new file mode 100644 index 00000000..b9ab7142 --- /dev/null +++ b/napi/src/js_values/boolean.rs @@ -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 { + let mut result = false; + check_status(unsafe { sys::napi_get_value_bool(self.0.env, self.0.value, &mut result) })?; + Ok(result) + } +} + +impl TryFrom for bool { + type Error = Error; + + fn try_from(value: JsBoolean) -> Result { + value.get_value() + } +} diff --git a/napi/src/js_values/buffer.rs b/napi/src/js_values/buffer.rs new file mode 100644 index 00000000..bda2fc44 --- /dev/null +++ b/napi/src/js_values/buffer.rs @@ -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 { + 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) } + } +} diff --git a/napi/src/js_values/class_property.rs b/napi/src/js_values/class_property.rs new file mode 100644 index 00000000..c44dbb9b --- /dev/null +++ b/napi/src/js_values/class_property.rs @@ -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(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 { + self.raw_descriptor.name = env.create_string(&self.name)?.into_raw(); + Ok(self.raw_descriptor) + } +} diff --git a/napi/src/js_values/function.rs b/napi/src/js_values/function.rs new file mode 100644 index 00000000..03c2297a --- /dev/null +++ b/napi/src/js_values/function.rs @@ -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 { + 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) + } +} diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs new file mode 100644 index 00000000..6deb5a13 --- /dev/null +++ b/napi/src/js_values/mod.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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; + + 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 { + 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 { + type_of(self.0.env, self.0.value) + } +} diff --git a/napi/src/js_values/number.rs b/napi/src/js_values/number.rs new file mode 100644 index 00000000..c0486aab --- /dev/null +++ b/napi/src/js_values/number.rs @@ -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 for usize { + type Error = Error; + + fn try_from(value: JsNumber) -> Result { + 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 for u32 { + type Error = Error; + + fn try_from(value: JsNumber) -> Result { + 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 for i32 { + type Error = Error; + + fn try_from(value: JsNumber) -> Result { + 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 for i64 { + type Error = Error; + + fn try_from(value: JsNumber) -> Result { + 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 for f64 { + type Error = Error; + + fn try_from(value: JsNumber) -> Result { + 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) + } +} diff --git a/napi/src/js_values/object.rs b/napi/src/js_values/object.rs new file mode 100644 index 00000000..c81b4ad6 --- /dev/null +++ b/napi/src/js_values/object.rs @@ -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(&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( + &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(&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(&self, name: &str) -> Result { + 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(&self) -> Result { + 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(&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(&self, index: u32) -> Result { + 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 { + 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 { + 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::from_raw(self.0.env, self.0.value) + } + + pub fn get_array_length(&self) -> Result { + 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) + } +} diff --git a/napi/src/js_values/string.rs b/napi/src/js_values/string.rs new file mode 100644 index 00000000..aa523584 --- /dev/null +++ b/napi/src/js_values/string.rs @@ -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 { + 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 for Vec { + type Error = Error; + + fn try_from(value: JsString) -> Result> { + 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 for String { + type Error = Error; + + fn try_from(value: JsString) -> Result { + Ok(value.as_str()?.to_owned()) + } +} diff --git a/napi/src/js_values/tagged_object.rs b/napi/src/js_values/tagged_object.rs new file mode 100644 index 00000000..e1a72e7f --- /dev/null +++ b/napi/src/js_values/tagged_object.rs @@ -0,0 +1,16 @@ +use std::any::TypeId; + +#[repr(C)] +pub struct TaggedObject { + type_id: TypeId, + pub(crate) object: Option, +} + +impl TaggedObject { + pub fn new(object: T) -> Self { + TaggedObject { + type_id: TypeId::of::(), + object: Some(object), + } + } +} diff --git a/napi/src/js_values/value.rs b/napi/src/js_values/value.rs new file mode 100644 index 00000000..d4d3645c --- /dev/null +++ b/napi/src/js_values/value.rs @@ -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, +} diff --git a/napi/src/js_values/value_ref.rs b/napi/src/js_values/value_ref.rs new file mode 100644 index 00000000..0e4dd2e2 --- /dev/null +++ b/napi/src/js_values/value_ref.rs @@ -0,0 +1,35 @@ +use std::marker::PhantomData; + +use super::NapiValue; +use crate::{sys, Status}; + +pub struct Ref { + pub(crate) raw_env: sys::napi_env, + pub(crate) ref_value: sys::napi_ref, + _phantom: PhantomData, +} + +impl Ref { + pub fn new(raw_env: sys::napi_env, ref_value: sys::napi_ref) -> Ref { + Ref { + raw_env, + ref_value, + _phantom: PhantomData, + } + } +} + +impl Drop for Ref { + 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); + } + } + } +} diff --git a/napi/src/js_values/value_type.rs b/napi/src/js_values/value_type.rs new file mode 100644 index 00000000..9163cd0e --- /dev/null +++ b/napi/src/js_values/value_type.rs @@ -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 for ValueType { + type Error = Error; + + fn try_into(self) -> Result { + 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 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, + } + } +} diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 1b9ed7c0..17893101 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -1,111 +1,25 @@ -use async_work::AsyncWork; -use core::fmt::Debug; -use std::any::TypeId; -use std::convert::{TryFrom, TryInto}; -use std::ffi::CString; -use std::marker::PhantomData; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::os::raw::{c_char, c_void}; -use std::ptr; -use std::slice; -use std::str; -use std::string::String as RustString; - mod async_work; mod call_context; +mod env; +mod error; +mod js_values; +mod module; pub mod sys; mod task; #[cfg(napi4)] pub mod threadsafe_function; mod version; +pub use async_work::AsyncWork; pub use call_context::CallContext; +pub use env::*; +pub use error::{Error, Result}; +pub use js_values::*; +pub use module::Module; pub use sys::{napi_valuetype, Status}; pub use task::Task; pub use version::NodeVersion; -pub type Result = std::result::Result; -pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value; - -#[derive(Debug, Clone)] -pub struct Error { - pub status: Status, - pub reason: Option, -} - -#[derive(Clone, Copy, Debug)] -pub struct Env(sys::napi_env); - -// Value types -#[derive(Clone, Copy, Debug)] -pub struct Any; - -#[derive(Clone, Copy, Debug)] -pub struct Undefined; - -#[derive(Clone, Copy, Debug)] -pub struct Null; - -#[derive(Clone, Copy, Debug)] -pub struct Boolean { - value: bool, -} - -#[derive(Clone, Copy, Debug)] -pub enum Number { - Int(i64), - Int32(i32), - U32(u32), - Double(f64), -} - -#[derive(Clone, Copy, Debug)] -pub struct JsString; - -#[derive(Clone, Copy, Debug)] -pub struct Object; - -#[derive(Clone, Copy, Debug)] -pub struct Function; - -#[derive(Clone, Copy, Debug)] -pub struct Buffer { - data: *const u8, - size: u64, -} - -#[derive(Clone, Copy, Debug)] -pub struct ArrayBuffer { - data: *const u8, - size: u64, -} - -#[derive(Clone, Copy, Debug)] -pub struct Value { - env: sys::napi_env, - raw_value: sys::napi_value, - value: T, -} - -pub struct Ref { - raw_env: sys::napi_env, - raw_ref: sys::napi_ref, - _marker: PhantomData, -} - -#[derive(Clone, Debug)] -pub struct Property { - name: RustString, - raw_descriptor: sys::napi_property_descriptor, -} - -#[repr(C)] -struct TaggedObject { - type_id: TypeId, - object: Option, -} - #[macro_export] macro_rules! register_module { ($module_name:ident, $init:ident) => { @@ -117,7 +31,7 @@ macro_rules! register_module { use std::io::Write; use std::os::raw::c_char; use std::ptr; - use $crate::sys; + use $crate::{sys, Env, JsObject, Module, NapiValue}; extern "C" fn register_module() { static mut MODULE_DESCRIPTOR: Option = None; @@ -140,9 +54,9 @@ macro_rules! register_module { raw_exports: sys::napi_value, ) -> sys::napi_value { let env = Env::from_raw(raw_env); - let mut exports: Value = Value::from_raw(raw_env, raw_exports).unwrap(); - - let result = $init(&env, &mut exports); + let mut exports: JsObject = JsObject::from_raw_unchecked(raw_env, raw_exports); + let mut cjs_module = Module { env, exports }; + let result = $init(&mut cjs_module); match result { Ok(_) => exports.into_raw(), @@ -164,1106 +78,3 @@ macro_rules! register_module { }; }; } - -impl Error { - pub fn from_status(status: Status) -> Self { - Error { - status: status, - reason: None, - } - } - - pub fn into_raw(self, env: sys::napi_env) -> sys::napi_value { - let mut err = ptr::null_mut(); - let s = self.reason.unwrap_or("NAPI error".to_owned()); - 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 for Error { - fn from(error: std::ffi::NulError) -> Self { - Error { - status: Status::StringExpected, - reason: Some(format!("{:?}", error)), - } - } -} - -impl Env { - pub fn from_raw(env: sys::napi_env) -> Self { - Env(env) - } - - pub fn get_undefined(&self) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = unsafe { sys::napi_get_undefined(self.0, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value(self, raw_value, Undefined)) - } - - pub fn get_null(&self) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = unsafe { sys::napi_get_null(self.0, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value(self, raw_value, Null)) - } - - pub fn get_boolean(&self, value: bool) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value(self, raw_value, Boolean { value })) - } - - pub fn create_int32(&self, int: i32) -> Result> { - 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(Value::from_raw_value(self, raw_value, Number::Int32(int))) - } - - pub fn create_int64(&self, int: i64) -> Result> { - 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(Value::from_raw_value(self, raw_value, Number::Int(int))) - } - - pub fn create_uint32(&self, number: u32) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = unsafe { sys::napi_create_uint32(self.0, number, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value(self, raw_value, Number::U32(number))) - } - - pub fn create_double(&self, double: f64) -> Result> { - 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(Value::from_raw_value( - self, - raw_value, - Number::Double(double), - )) - } - - pub fn create_string<'a, 'b>(&'a self, s: &'b str) -> Result> { - 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(Value::from_raw_value(self, raw_value, JsString)) - } - - pub fn create_string_utf16(&self, chars: &[u16]) -> Result> { - 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(Value::from_raw_value(self, raw_value, JsString)) - } - - pub fn create_object(&self) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = unsafe { sys::napi_create_object(self.0, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value(self, raw_value, Object)) - } - - pub fn create_array_with_length(&self, length: usize) -> Result> { - 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(Value::from_raw_value(self, raw_value, Object)) - } - - pub fn create_buffer(&self, length: u64) -> Result> { - let mut raw_value = ptr::null_mut(); - let mut data = ptr::null_mut(); - let status = unsafe { sys::napi_create_buffer(self.0, length, &mut data, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value( - self, - raw_value, - Buffer { - data: data as *const u8, - size: length, - }, - )) - } - - pub fn create_buffer_with_data(&self, data: Vec) -> Result> { - 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), - Box::into_raw(Box::from(length)) 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); - Ok(Value::from_raw_value( - self, - raw_value, - Buffer { - data: data_ptr, - size: length, - }, - )) - } - - pub fn create_arraybuffer(&self, length: u64) -> Result> { - let mut raw_value = ptr::null_mut(); - let mut data = ptr::null_mut(); - let status = unsafe { sys::napi_create_arraybuffer(self.0, length, &mut data, &mut raw_value) }; - check_status(status)?; - Ok(Value::from_raw_value( - self, - raw_value, - ArrayBuffer { - data: data as *const u8, - size: length, - }, - )) - } - - pub fn create_arraybuffer_with_data(&self, data: Vec) -> Result> { - 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), - Box::into_raw(Box::from(length)) 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); - Ok(Value::from_raw_value( - self, - raw_value, - ArrayBuffer { - data: data_ptr, - size: length, - }, - )) - } - - pub fn create_function(&self, name: &str, callback: Callback) -> Result> { - 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(Value::from_raw_value(self, raw_result, Function)) - } - - 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(&self, value: &Value) -> Result> { - let mut raw_ref = ptr::null_mut(); - unsafe { - let status = sys::napi_create_reference(self.0, value.raw_value, 1, &mut raw_ref); - check_status(status)?; - }; - - Ok(Ref { - raw_env: self.0, - raw_ref, - _marker: PhantomData, - }) - } - - pub fn get_reference_value(&self, reference: &Ref) -> Result> { - let mut raw_value = ptr::null_mut(); - unsafe { - let status = sys::napi_get_reference_value(self.0, reference.raw_ref, &mut raw_value); - check_status(status)?; - }; - - Value::from_raw(self.0, raw_value) - } - - pub fn define_class<'a, 'b>( - &'a self, - name: &'b str, - constructor_cb: Callback, - properties: Vec, - ) -> Result> { - let mut raw_result = ptr::null_mut(); - let raw_properties = properties - .into_iter() - .map(|prop| prop.into_raw(self)) - .collect::>>()?; - - 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(Value::from_raw_value(self, raw_result, Function)) - } - - pub fn wrap(&self, js_object: &mut Value, native_object: T) -> Result<()> { - let status = unsafe { - sys::napi_wrap( - self.0, - js_object.raw_value, - Box::into_raw(Box::new(TaggedObject::new(native_object))) as *mut c_void, - Some(raw_finalize::), - ptr::null_mut(), - ptr::null_mut(), - ) - }; - - check_status(status).or(Ok(())) - } - - pub fn unwrap(&self, js_object: &Value) -> Result<&mut T> { - unsafe { - let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); - let status = sys::napi_unwrap(self.0, js_object.raw_value, &mut unknown_tagged_object); - check_status(status)?; - - let type_id: *const TypeId = mem::transmute(unknown_tagged_object); - if *type_id == TypeId::of::() { - let tagged_object: *mut TaggedObject = mem::transmute(unknown_tagged_object); - (*tagged_object).object.as_mut().ok_or(Error { - status: Status::InvalidArg, - reason: Some("Invalid argument, nothing attach to js_object".to_owned()), - }) - } else { - Err(Error { - status: Status::InvalidArg, - reason: Some( - "Invalid argument, T on unrwap is not the type of wrapped object".to_owned(), - ), - }) - } - } - } - - pub fn drop_wrapped(&self, js_object: Value) -> Result<()> { - unsafe { - let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); - let status = sys::napi_unwrap(self.0, js_object.raw_value, &mut unknown_tagged_object); - check_status(status)?; - - let type_id: *const TypeId = mem::transmute(unknown_tagged_object); - if *type_id == TypeId::of::() { - let tagged_object: *mut TaggedObject = mem::transmute(unknown_tagged_object); - (*tagged_object).object = None; - Ok(()) - } else { - Err(Error { - status: Status::InvalidArg, - reason: Some( - "Invalid argument, T on drop_wrapped is not the type of wrapped object".to_owned(), - ), - }) - } - } - } - - pub fn create_external(&self, native_object: T) -> Result> { - 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::), - ptr::null_mut(), - &mut object_value, - ) - }; - - check_status(status)?; - Ok(Value::from_raw_value(self, object_value, Object)) - } - - pub fn get_value_external(&self, js_object: &Value) -> Result<&mut T> { - unsafe { - let mut unknown_tagged_object = ptr::null_mut(); - let status = - sys::napi_get_value_external(self.0, js_object.raw_value, &mut unknown_tagged_object); - check_status(status)?; - - let type_id: *const TypeId = mem::transmute(unknown_tagged_object); - if *type_id == TypeId::of::() { - let tagged_object: *mut TaggedObject = mem::transmute(unknown_tagged_object); - (*tagged_object).object.as_mut().ok_or(Error { - status: Status::InvalidArg, - reason: Some("Invalid argument, nothing attach to js_object".to_owned()), - }) - } else { - Err(Error { - status: Status::InvalidArg, - reason: Some( - "Invalid argument, T on get_value_external is not the type of wrapped object" - .to_owned(), - ), - }) - } - } - } - - pub fn create_error(&self, e: Error) -> Result> { - let reason = e.reason.unwrap_or("".to_owned()); - 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(Value::from_raw_value(self, result, Object)) - } - - pub fn spawn(&self, task: T) -> Result> { - 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(Value::from_raw_value(self, raw_promise, Object)) - } - - pub fn get_node_version(&self) -> Result { - let mut result = ptr::null(); - check_status(unsafe { sys::napi_get_node_version(self.0, &mut result) })?; - let version = unsafe { *result }; - version.try_into() - } -} - -pub trait ValueType: Copy + Debug { - fn from_raw(env: sys::napi_env, raw: sys::napi_value) -> Result; - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result; -} - -impl ValueType for Any { - fn from_raw(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(Any) - } - - fn matches_raw_type(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(true) - } -} - -impl ValueType for Undefined { - fn from_raw(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(Undefined) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_undefined) - } -} - -impl ValueType for Null { - fn from_raw(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(Null) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_null) - } -} - -impl ValueType for Boolean { - fn from_raw(env: sys::napi_env, raw: sys::napi_value) -> Result { - let mut value = true; - let status = unsafe { sys::napi_get_value_bool(env, raw, &mut value) }; - check_status(status)?; - Ok(Boolean { value }) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_boolean) - } -} - -impl ValueType for Number { - fn from_raw(env: sys::napi_env, raw: sys::napi_value) -> Result { - let mut double: f64 = 0.0; - let status = unsafe { sys::napi_get_value_double(env, raw, &mut double) }; - check_status(status)?; - Ok(Number::Double(double)) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_number) - } -} - -impl ValueType for JsString { - fn from_raw(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(JsString {}) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_string) - } -} - -impl ValueType for Object { - fn from_raw(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(Object {}) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_object) - } -} - -impl ValueType for Buffer { - fn from_raw(env: sys::napi_env, raw: sys::napi_value) -> Result { - let mut data = ptr::null_mut(); - let mut size: u64 = 0; - let status = unsafe { sys::napi_get_buffer_info(env, raw, &mut data, &mut size) }; - check_status(status)?; - Ok(Buffer { - data: data as *const u8, - size, - }) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - let mut result = false; - unsafe { - let status = sys::napi_is_buffer(env, raw, &mut result); - check_status(status)?; - } - Ok(result) - } -} - -impl ValueType for ArrayBuffer { - fn from_raw(env: sys::napi_env, raw: sys::napi_value) -> Result { - let mut data = ptr::null_mut(); - let mut size: u64 = 0; - let status = unsafe { sys::napi_get_arraybuffer_info(env, raw, &mut data, &mut size) }; - check_status(status)?; - Ok(ArrayBuffer { - data: data as *const u8, - size, - }) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - let mut result = false; - unsafe { - let status = sys::napi_is_arraybuffer(env, raw, &mut result); - check_status(status)?; - } - Ok(result) - } -} - -impl Value { - #[inline] - pub fn from_value(env: &Env, value: &Value) -> Result> { - Ok(Value { - env: env.0, - raw_value: value.raw_value, - value: Buffer::from_raw(env.0, value.into_raw())?, - }) - } -} - -impl ValueType for Function { - fn from_raw(_env: sys::napi_env, _raw: sys::napi_value) -> Result { - Ok(Function {}) - } - - fn matches_raw_type(env: sys::napi_env, raw: sys::napi_value) -> Result { - Ok(get_raw_type(env, raw)? == sys::napi_valuetype::napi_function) - } -} - -impl Value { - pub fn from_raw_value(env: &Env, raw_value: sys::napi_value, value: T) -> Self { - Self { - env: env.0, - raw_value, - value, - } - } - - pub fn from_raw(env: sys::napi_env, raw_value: sys::napi_value) -> Result { - Ok(Self { - env, - raw_value, - value: T::from_raw(env, raw_value)?, - }) - } - - pub fn into_raw(self) -> sys::napi_value { - self.raw_value - } - - pub fn coerce_to_number(self) -> Result> { - let mut new_raw_value = ptr::null_mut(); - let status = - unsafe { sys::napi_coerce_to_number(self.env, self.raw_value, &mut new_raw_value) }; - check_status(status)?; - Ok(Value { - env: self.env, - raw_value: self.raw_value, - value: Number::from_raw(self.env, self.raw_value)?, - }) - } - - pub fn coerce_to_string(self) -> Result> { - let mut new_raw_value = ptr::null_mut(); - let status = - unsafe { sys::napi_coerce_to_string(self.env, self.raw_value, &mut new_raw_value) }; - check_status(status)?; - Ok(Value { - env: self.env, - raw_value: self.raw_value, - value: JsString, - }) - } - - pub fn coerce_to_object(self) -> Result> { - let mut new_raw_value = ptr::null_mut(); - let status = unsafe { - sys::napi_coerce_to_object( - self.env, - self.raw_value, - (&mut new_raw_value) as *mut sys::napi_value, - ) - }; - check_status(status)?; - Ok(Value { - env: self.env, - raw_value: self.raw_value, - value: Object, - }) - } - - #[inline] - pub fn into_any(self) -> Value { - Value { - env: self.env, - raw_value: self.raw_value, - value: Any, - } - } -} - -#[inline] -fn get_raw_type(env: sys::napi_env, raw_value: sys::napi_value) -> Result { - unsafe { - let value_type = ptr::null_mut(); - check_status(sys::napi_typeof(env, raw_value, value_type))?; - Ok(*value_type) - } -} - -impl Value { - pub fn get_value(&self) -> bool { - self.value.value - } -} - -impl Value { - pub fn len(&self) -> Result { - let mut raw_length = ptr::null_mut(); - unsafe { - let status = sys::napi_get_named_property( - self.env, - self.raw_value, - "length\0".as_ptr() as *const c_char, - &mut raw_length, - ); - check_status(status)?; - } - let length: Value = Value::from_raw(self.env, raw_length)?; - length.try_into() - } -} - -impl Value { - #[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.env, - self.raw_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 { - status: Status::GenericFailure, - reason: Some(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.env, - self.raw_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> for Vec { - type Error = Error; - - fn try_from(value: Value) -> Result> { - 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.env, - value.raw_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> for usize { - type Error = Error; - - fn try_from(value: Value) -> Result { - let mut result = 0; - let status = unsafe { sys::napi_get_value_int64(value.env, value.raw_value, &mut result) }; - check_status(status)?; - Ok(result as usize) - } -} - -impl TryFrom> for u32 { - type Error = Error; - - fn try_from(value: Value) -> Result { - let mut result = 0; - let status = unsafe { sys::napi_get_value_uint32(value.env, value.raw_value, &mut result) }; - check_status(status)?; - Ok(result) - } -} - -impl TryFrom> for i32 { - type Error = Error; - - fn try_from(value: Value) -> Result { - let mut result = 0; - let status = unsafe { sys::napi_get_value_int32(value.env, value.raw_value, &mut result) }; - check_status(status)?; - Ok(result) - } -} - -impl TryFrom> for i64 { - type Error = Error; - - fn try_from(value: Value) -> Result { - let mut result = 0; - let status = unsafe { sys::napi_get_value_int64(value.env, value.raw_value, &mut result) }; - check_status(status)?; - Ok(result) - } -} - -impl TryFrom> for f64 { - type Error = Error; - - fn try_from(value: Value) -> Result { - let mut result = 0_f64; - let status = unsafe { sys::napi_get_value_double(value.env, value.raw_value, &mut result) }; - check_status(status)?; - Ok(result) - } -} - -impl Value { - pub fn set_property(&mut self, key: Value, value: Value) -> Result<()> { - let status = unsafe { - sys::napi_set_property( - self.raw_env(), - self.raw_value(), - key.raw_value, - value.raw_value, - ) - }; - check_status(status)?; - Ok(()) - } - - pub fn set_named_property>>(&mut self, name: &str, value: V) -> Result<()> { - let key = CString::new(name)?; - let status = unsafe { - sys::napi_set_named_property( - self.raw_env(), - self.raw_value(), - key.as_ptr(), - value.into().raw_value, - ) - }; - check_status(status)?; - Ok(()) - } - - pub fn get_named_property(&self, name: &str) -> Result> { - let key = CString::new(name)?; - let mut raw_value = ptr::null_mut(); - let status = unsafe { - sys::napi_get_named_property( - self.raw_env(), - self.raw_value(), - key.as_ptr(), - &mut raw_value, - ) - }; - check_status(status)?; - Value::::from_raw(self.env, raw_value) - } - - pub fn get_property_names(&self) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = - unsafe { sys::napi_get_property_names(self.raw_env(), self.raw_value(), &mut raw_value) }; - check_status(status)?; - Value::::from_raw(self.env, raw_value) - } - - pub fn set_index<'a, T>(&mut self, index: usize, value: Value) -> Result<()> { - self.set_property(Env::from_raw(self.env).create_int64(index as i64)?, value) - } - - pub fn get_index(&self, index: u32) -> Result> { - let mut raw_value = ptr::null_mut(); - let status = - unsafe { sys::napi_get_element(self.raw_env(), self.raw_value(), index, &mut raw_value) }; - check_status(status)?; - Value::::from_raw(self.env, raw_value) - } - - pub fn is_array(&self) -> Result { - let mut is_array = false; - let status = unsafe { sys::napi_is_array(self.raw_env(), self.raw_value(), &mut is_array) }; - check_status(status)?; - Ok(is_array) - } - - pub fn is_buffer(&self) -> Result { - let mut is_buffer = false; - let status = unsafe { sys::napi_is_buffer(self.raw_env(), self.raw_value(), &mut is_buffer) }; - check_status(status)?; - Ok(is_buffer) - } - - pub fn to_buffer(&self) -> Result> { - Value::from_raw(self.env, self.raw_value) - } - - pub fn get_array_length(&self) -> Result { - if self.is_array()? != true { - return Err(Error { - status: Status::ArrayExpected, - reason: Some("Object is not array".to_owned()), - }); - } - let mut length: u32 = 0; - let status = - unsafe { sys::napi_get_array_length(self.raw_env(), self.raw_value(), &mut length) }; - check_status(status)?; - Ok(length) - } - - pub fn is_date(&self) -> Result { - let mut is_date = true; - let status = unsafe { sys::napi_is_date(self.env, self.raw_value(), &mut is_date) }; - check_status(status)?; - Ok(is_date) - } - - fn raw_value(&self) -> sys::napi_value { - self.raw_value - } - - fn raw_env(&self) -> sys::napi_env { - self.env - } -} - -impl AsRef<[u8]> for Value { - fn as_ref(&self) -> &[u8] { - self.deref() - } -} - -impl Deref for Value { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.value.data, self.value.size as usize) } - } -} - -impl DerefMut for Value { - fn deref_mut(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.value.data as *mut _, self.value.size as usize) } - } -} - -impl Deref for Value { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.value.data, self.value.size as usize) } - } -} - -impl DerefMut for Value { - fn deref_mut(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.value.data as *mut _, self.value.size as usize) } - } -} - -impl Value { - pub fn call(&self, this: Option<&Value>, args: &[Value]) -> Result> { - let raw_this = this - .map(|v| v.into_raw()) - .or_else(|| { - Env::from_raw(self.env) - .get_undefined() - .ok() - .map(|u| u.into_raw()) - }) - .ok_or(Error { - status: Status::Unknown, - reason: Some("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.raw_value; - } - let mut return_value = ptr::null_mut(); - let status = unsafe { - sys::napi_call_function( - self.env, - raw_this, - self.raw_value, - args.len() as u64, - &raw_args[0], - &mut return_value, - ) - }; - check_status(status)?; - - Value::from_raw(self.env, return_value) - } -} - -impl Value { - pub fn get_type(&self) -> Result { - get_raw_type(self.env, self.raw_value) - } -} - -impl Drop for Ref { - fn drop(&mut self) { - unsafe { - let mut ref_count = 0; - let status = sys::napi_reference_unref(self.raw_env, self.raw_ref, &mut ref_count); - debug_assert!(Status::from(status) == Status::Ok); - - if ref_count == 0 { - let status = sys::napi_delete_reference(self.raw_env, self.raw_ref); - debug_assert!(Status::from(status) == Status::Ok); - } - } - } -} - -impl Property { - pub fn new(name: &str) -> Self { - Property { - name: RustString::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(mut self, value: Value) -> Self { - self.raw_descriptor.value = value.raw_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 - } - - fn into_raw(mut self, env: &Env) -> Result { - self.raw_descriptor.name = env.create_string(&self.name)?.into_raw(); - Ok(self.raw_descriptor) - } -} - -impl TaggedObject { - fn new(object: T) -> Self { - TaggedObject { - type_id: TypeId::of::(), - object: Some(object), - } - } -} - -#[inline] -fn check_status(code: sys::napi_status) -> Result<()> { - let status = Status::from(code); - match status { - Status::Ok => Ok(()), - _ => Err(Error::from_status(status)), - } -} - -unsafe extern "C" fn raw_finalize( - _raw_env: sys::napi_env, - finalize_data: *mut c_void, - _finalize_hint: *mut c_void, -) { - let tagged_object: *mut TaggedObject = mem::transmute(finalize_data); - Box::from_raw(tagged_object); -} - -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); -} diff --git a/napi/src/module.rs b/napi/src/module.rs new file mode 100644 index 00000000..96aad4e0 --- /dev/null +++ b/napi/src/module.rs @@ -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(()) + } +} diff --git a/napi/src/task.rs b/napi/src/task.rs index 7ff0ffb8..9747cddf 100644 --- a/napi/src/task.rs +++ b/napi/src/task.rs @@ -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; - fn resolve(&self, env: &mut Env, output: Self::Output) -> Result>; + fn resolve(&self, env: &mut Env, output: Self::Output) -> Result; } diff --git a/napi/src/threadsafe_function.rs b/napi/src/threadsafe_function.rs index 2d14829b..f505de30 100644 --- a/napi/src/threadsafe_function.rs +++ b/napi/src/threadsafe_function.rs @@ -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)>; + 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)> { +/// 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> { +/// fn test_threadsafe_function(ctx: CallContext) -> Result { /// // The callback function from js which will be called in `ThreadsafeFunction::call`. -/// let func: Value = ctx.get::(0)?; +/// let func = ctx.get::(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 Sync for ThreadsafeFunction {} impl ThreadsafeFunction { /// 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, to_js: T, max_queue_size: u64) -> Result { + pub fn create(env: &Env, func: JsFunction, to_js: T, max_queue_size: u64) -> Result { let mut async_resource_name = ptr::null_mut(); let s = "napi_rs_threadsafe_function"; let status = unsafe { @@ -115,7 +114,7 @@ impl ThreadsafeFunction { 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( 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( recv, js_callback, 1, - &mut err.raw_value, + &mut err.0.value, ptr::null_mut(), ); } diff --git a/napi/src/version.rs b/napi/src/version.rs index 4e4356fc..72ecc51f 100644 --- a/napi/src/version.rs +++ b/napi/src/version.rs @@ -21,7 +21,7 @@ impl TryFrom 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(), })? }, }) diff --git a/test_module/__test__/buffer.spec.js b/test_module/__test__/buffer.spec.js new file mode 100644 index 00000000..c44305df --- /dev/null +++ b/test_module/__test__/buffer.spec.js @@ -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) +}) diff --git a/test_module/__test__/symbol.spec.js b/test_module/__test__/symbol.spec.js new file mode 100644 index 00000000..b0d97f47 --- /dev/null +++ b/test_module/__test__/symbol.spec.js @@ -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})`) +}) diff --git a/test_module/src/buffer.rs b/test_module/src/buffer.rs new file mode 100644 index 00000000..a8884054 --- /dev/null +++ b/test_module/src/buffer.rs @@ -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 { + let buffer = ctx.get::(0)?; + ctx.env.create_uint32((&buffer).len() as u32) +} + +#[js_function(1)] +pub fn buffer_to_string(ctx: CallContext) -> Result { + let buffer = ctx.get::(0)?; + ctx.env.create_string( + str::from_utf8(&buffer).map_err(|e| Error::new(Status::StringExpected, format!("{}", e)))?, + ) +} diff --git a/test_module/src/external.rs b/test_module/src/external.rs new file mode 100644 index 00000000..b69edfb5 --- /dev/null +++ b/test_module/src/external.rs @@ -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 { + let count = ctx.get::(0)?.try_into()?; + let native = NativeObject { count }; + ctx.env.create_external(native) +} + +#[js_function(1)] +pub fn get_external_count(ctx: CallContext) -> Result { + let attached_obj = ctx.get::(0)?; + let native_object = ctx.env.get_value_external::(&attached_obj)?; + ctx.env.create_int32(native_object.count) +} diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs index 17304501..26d52c89 100644 --- a/test_module/src/lib.rs +++ b/test_module/src/lib.rs @@ -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) -> 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 { - Ok(fibonacci_native(self.n)) - } - - fn resolve(&self, env: &mut Env, output: Self::Output) -> Result> { - 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> { - let n = ctx.get::(0)?; - let task = ComputeFib::new(n.try_into()?); - ctx.env.spawn(task) -} - #[js_function] -fn test_throw(_ctx: CallContext) -> Result> { +fn test_throw(_ctx: CallContext) -> Result { Err(Error::from_status(Status::GenericFailure)) } #[js_function(1)] -fn test_throw_with_reason(ctx: CallContext) -> Result> { +fn test_throw_with_reason(ctx: CallContext) -> Result { let reason = ctx.get::(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> { - let obj: Value = ctx.get::(0)?; - Ok(Env::get_boolean(ctx.env, obj.is_date()?)?) -} - -struct NativeObject { - count: i32, -} - -#[js_function(1)] -fn create_external(ctx: CallContext) -> Result> { - let count = ctx.get::(0)?.try_into()?; - let native = NativeObject { count }; - ctx.env.create_external(native) -} - -#[js_function(1)] -fn get_external_count(ctx: CallContext) -> Result> { - let attached_obj = ctx.get::(0)?; - let native_object = ctx.env.get_value_external::(&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)> { - 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> { - let func: Value = ctx.get::(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> { - let func = ctx.get::(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; - type JsValue = Buffer; - - fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Value)> { - let value = env.create_buffer_with_data(output.to_vec())?; - Ok((1u64, value)) - } -} - -async fn read_file_content(filepath: &Path) -> Result> { - 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> { - let js_filepath: Value = ctx.get::(0)?; - let js_func: Value = ctx.get::(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 { + let obj = ctx.get::(0)?; + Env::get_boolean(ctx.env, obj.is_date()?) } diff --git a/test_module/src/symbol.rs b/test_module/src/symbol.rs new file mode 100644 index 00000000..ef912ac1 --- /dev/null +++ b/test_module/src/symbol.rs @@ -0,0 +1,17 @@ +use napi::{CallContext, JsString, JsSymbol, Result}; + +#[js_function] +pub fn create_named_symbol(ctx: CallContext) -> Result { + ctx.env.create_symbol(Some("native")) +} + +#[js_function] +pub fn create_unnamed_symbol(ctx: CallContext) -> Result { + ctx.env.create_symbol(None) +} + +#[js_function(1)] +pub fn create_symbol_from_js_string(ctx: CallContext) -> Result { + let name = ctx.get::(0)?; + ctx.env.create_symbol_from_js_string(name) +} diff --git a/test_module/src/task.rs b/test_module/src/task.rs new file mode 100644 index 00000000..d570f545 --- /dev/null +++ b/test_module/src/task.rs @@ -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 { + Ok(fibonacci_native(self.n)) + } + + fn resolve(&self, env: &mut Env, output: Self::Output) -> Result { + 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 { + let n = ctx.get::(0)?; + let task = ComputeFib::new(n.try_into()?); + ctx.env.spawn(task) +} diff --git a/test_module/src/tsfn.rs b/test_module/src/tsfn.rs new file mode 100644 index 00000000..87251759 --- /dev/null +++ b/test_module/src/tsfn.rs @@ -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 { + let func = ctx.get::(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 { + let func = ctx.get::(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; + 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> { + 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 { + let js_filepath = ctx.get::(0)?; + let js_func = ctx.get::(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() +}