refactor(napi): scope and Ref

This commit is contained in:
LongYinan 2020-09-28 00:27:37 +08:00 committed by LongYinan
parent 4460f43be8
commit 9c921ffaa3
No known key found for this signature in database
GPG key ID: A3FFE134A3E20881
36 changed files with 619 additions and 474 deletions

View file

@ -1,17 +1,20 @@
use napi::{CallContext, Env, JsBuffer, JsNumber, JsObject, Module, Result, Task}; use napi::{
CallContext, Env, JsBuffer, JsBufferValue, JsNumber, JsObject, Module, Ref, Result, Task,
};
#[repr(transparent)] #[repr(transparent)]
struct BufferLength(&'static [u8]); struct BufferLength(Ref<JsBufferValue>);
impl Task for BufferLength { impl Task for BufferLength {
type Output = usize; type Output = usize;
type JsValue = JsNumber; type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> { fn compute(&mut self) -> Result<Self::Output> {
Ok(self.0.len()) Ok((&self.0).len())
} }
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Self::JsValue> { fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
self.0.unref(env)?;
env.create_uint32(output as u32) env.create_uint32(output as u32)
} }
} }
@ -19,7 +22,7 @@ impl Task for BufferLength {
#[js_function(1)] #[js_function(1)]
fn bench_async_task(ctx: CallContext) -> Result<JsObject> { fn bench_async_task(ctx: CallContext) -> Result<JsObject> {
let n = ctx.get::<JsBuffer>(0)?; let n = ctx.get::<JsBuffer>(0)?;
let task = BufferLength(n.data); let task = BufferLength(n.into_ref()?);
let async_promise = ctx.env.spawn(task)?; let async_promise = ctx.env.spawn(task)?;
Ok(async_promise.promise_object()) Ok(async_promise.promise_object())
} }

View file

@ -176,13 +176,13 @@ fn get_execute_js_code(
let return_token_stream = match function_kind { let return_token_stream = match function_kind {
FunctionKind::Contextless => { FunctionKind::Contextless => {
quote! { quote! {
Ok(Some(v)) => v.raw_value(), Ok(Some(v)) => v.raw(),
Ok(None) => ptr::null_mut(), Ok(None) => ptr::null_mut(),
} }
} }
FunctionKind::JsFunction => { FunctionKind::JsFunction => {
quote! { quote! {
Ok(v) => v.raw_value(), Ok(v) => v.raw(),
} }
} }
}; };

View file

@ -80,6 +80,8 @@ unsafe impl<T: Task> Send for AsyncWork<T> {}
unsafe impl<T: Task> Sync for AsyncWork<T> {} unsafe impl<T: Task> Sync for AsyncWork<T> {}
/// env here is the same with the one in `CallContext`.
/// So it actually could do nothing here, because `execute` function is called in the other thread mostly.
unsafe extern "C" fn execute<T: Task>(_env: sys::napi_env, data: *mut c_void) { unsafe extern "C" fn execute<T: Task>(_env: sys::napi_env, data: *mut c_void) {
let mut work = Box::from_raw(data as *mut AsyncWork<T>); let mut work = Box::from_raw(data as *mut AsyncWork<T>);
let _ = mem::replace( let _ = mem::replace(
@ -100,11 +102,11 @@ unsafe extern "C" fn complete<T: Task>(
let napi_async_work = mem::replace(&mut work.napi_async_work, ptr::null_mut()); let napi_async_work = mem::replace(&mut work.napi_async_work, ptr::null_mut());
let value = value_ptr.and_then(move |v| { let value = value_ptr.and_then(move |v| {
let output = v.assume_init(); let output = v.assume_init();
work.inner_task.resolve(&mut Env::from_raw(env), output) work.inner_task.resolve(Env::from_raw(env), output)
}); });
match check_status(status).and_then(move |_| value) { match check_status(status).and_then(move |_| value) {
Ok(v) => { Ok(v) => {
let status = sys::napi_resolve_deferred(env, deferred, v.raw_value()); let status = sys::napi_resolve_deferred(env, deferred, v.raw());
debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed"); debug_assert!(status == sys::napi_status::napi_ok, "Reject promise failed");
} }
Err(e) => { Err(e) => {

View file

@ -30,6 +30,7 @@ use tokio::sync::mpsc::error::TrySendError;
pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value; pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
#[repr(transparent)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Env(pub(crate) sys::napi_env); pub struct Env(pub(crate) sys::napi_env);
@ -219,28 +220,26 @@ impl Env {
Ok(JsObject::from_raw_unchecked(self.0, raw_value)) Ok(JsObject::from_raw_unchecked(self.0, raw_value))
} }
pub fn create_buffer<'env, 'buffer>(&'env self, length: u64) -> Result<JsBuffer<'buffer>> { pub fn create_buffer(&self, length: u64) -> Result<JsBufferValue> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let mut data = Vec::with_capacity(length as usize); let mut data: Vec<u8> = Vec::with_capacity(length as usize);
let mut data_ptr = data.as_mut_ptr(); let mut data_ptr = data.as_mut_ptr() as *mut c_void;
check_status(unsafe { check_status(unsafe {
sys::napi_create_buffer(self.0, length, &mut data_ptr, &mut raw_value) sys::napi_create_buffer(self.0, length, &mut data_ptr, &mut raw_value)
})?; })?;
mem::forget(data);
Ok(JsBuffer::new( Ok(JsBufferValue::new(
self.0, JsBuffer(Value {
raw_value, env: self.0,
data_ptr as *mut u8, value: raw_value,
length as usize, value_type: ValueType::Object,
}),
data,
)) ))
} }
pub fn create_buffer_with_data<'env, 'buffer>( pub fn create_buffer_with_data(&self, mut data: Vec<u8>) -> Result<JsBufferValue> {
&'env self, let mut length = data.len() as u64;
mut data: Vec<u8>,
) -> Result<JsBuffer<'buffer>> {
let length = data.len() as u64;
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
let data_ptr = data.as_mut_ptr(); let data_ptr = data.as_mut_ptr();
check_status(unsafe { check_status(unsafe {
@ -249,14 +248,20 @@ impl Env {
length, length,
data_ptr as *mut c_void, data_ptr as *mut c_void,
Some(drop_buffer), Some(drop_buffer),
Box::leak(Box::new(length)) as *mut u64 as *mut _, &mut length as *mut u64 as *mut _,
&mut raw_value, &mut raw_value,
) )
})?; })?;
let mut changed = 0; let mut changed = 0;
check_status(unsafe { sys::napi_adjust_external_memory(self.0, length as i64, &mut changed) })?; check_status(unsafe { sys::napi_adjust_external_memory(self.0, length as i64, &mut changed) })?;
mem::forget(data); Ok(JsBufferValue::new(
Ok(JsBuffer::new(self.0, raw_value, data_ptr, length as usize)) JsBuffer(Value {
env: self.0,
value: raw_value,
value_type: ValueType::Object,
}),
data,
))
} }
pub fn create_arraybuffer(&self, length: u64) -> Result<JsArrayBuffer> { pub fn create_arraybuffer(&self, length: u64) -> Result<JsArrayBuffer> {
@ -328,25 +333,6 @@ impl Env {
}) })
} }
pub fn create_reference<T: NapiValue>(&self, value: T) -> Result<Ref<T>> {
let mut raw_ref = ptr::null_mut();
let initial_ref_count = 1;
check_status(unsafe {
sys::napi_create_reference(self.0, value.raw_value(), initial_ref_count, &mut raw_ref)
})?;
Ok(Ref::new(self.0, raw_ref))
}
pub fn get_reference_value<T: NapiValue>(&self, reference: &Ref<T>) -> Result<T> {
let mut raw_value = ptr::null_mut();
check_status(unsafe {
sys::napi_get_reference_value(self.0, reference.ref_value, &mut raw_value)
})?;
T::from_raw(self.0, raw_value)
}
pub fn define_class( pub fn define_class(
&self, &self,
name: &str, name: &str,
@ -491,6 +477,19 @@ impl Env {
async_work::run(self, task) async_work::run(self, task)
} }
pub fn run_in_scope<T, F>(&self, executor: F) -> Result<T>
where
F: FnOnce() -> Result<T>,
{
let mut handle_scope = ptr::null_mut();
check_status(unsafe { sys::napi_open_handle_scope(self.0, &mut handle_scope) })?;
let result = executor();
check_status(unsafe { sys::napi_close_handle_scope(self.0, handle_scope) })?;
result
}
pub fn get_global(&self) -> Result<JsObject> { pub fn get_global(&self) -> Result<JsObject> {
let mut raw_global = ptr::null_mut(); let mut raw_global = ptr::null_mut();
check_status(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?; check_status(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?;
@ -503,6 +502,7 @@ impl Env {
let versions: JsObject = process.get_named_property("versions")?; let versions: JsObject = process.get_named_property("versions")?;
let napi_version: JsString = versions.get_named_property("napi")?; let napi_version: JsString = versions.get_named_property("napi")?;
napi_version napi_version
.into_utf8()?
.as_str()? .as_str()?
.parse() .parse()
.map_err(|e| Error::new(Status::InvalidArg, format!("{}", e))) .map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
@ -622,7 +622,7 @@ impl Env {
{ {
let value = Value { let value = Value {
env: self.0, env: self.0,
value: value.raw_value(), value: value.raw(),
value_type: ValueType::Unknown, value_type: ValueType::Unknown,
}; };
let mut de = De(&value); let mut de = De(&value);
@ -631,9 +631,7 @@ impl Env {
pub fn strict_equals<A: NapiValue, B: NapiValue>(&self, a: A, b: B) -> Result<bool> { pub fn strict_equals<A: NapiValue, B: NapiValue>(&self, a: A, b: B) -> Result<bool> {
let mut result = false; let mut result = false;
check_status(unsafe { check_status(unsafe { sys::napi_strict_equals(self.0, a.raw(), b.raw(), &mut result) })?;
sys::napi_strict_equals(self.0, a.raw_value(), b.raw_value(), &mut result)
})?;
Ok(result) Ok(result)
} }
@ -646,7 +644,7 @@ impl Env {
} }
unsafe extern "C" fn drop_buffer(env: sys::napi_env, finalize_data: *mut c_void, len: *mut c_void) { 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 = len as *mut u64;
let length = *length as usize; let length = *length as usize;
let _ = Vec::from_raw_parts(finalize_data as *mut u8, length, length); let _ = Vec::from_raw_parts(finalize_data as *mut u8, length, length);
let mut changed = 0; let mut changed = 0;

View file

@ -126,7 +126,7 @@ impl JsArrayBuffer {
sys::napi_instanceof( sys::napi_instanceof(
self.value.0.env, self.value.0.env,
self.value.0.value, self.value.0.value,
constructor.raw_value(), constructor.raw(),
&mut result, &mut result,
) )
})?; })?;
@ -135,7 +135,7 @@ impl JsArrayBuffer {
} }
impl NapiValue for JsArrayBuffer { impl NapiValue for JsArrayBuffer {
fn raw_value(&self) -> sys::napi_value { fn raw(&self) -> sys::napi_value {
self.value.0.value self.value.0.value
} }

View file

@ -117,19 +117,14 @@ impl JsBigint {
pub fn instanceof<Constructor: NapiValue>(&self, constructor: Constructor) -> Result<bool> { pub fn instanceof<Constructor: NapiValue>(&self, constructor: Constructor) -> Result<bool> {
let mut result = false; let mut result = false;
check_status(unsafe { check_status(unsafe {
sys::napi_instanceof( sys::napi_instanceof(self.raw.env, self.raw.value, constructor.raw(), &mut result)
self.raw.env,
self.raw.value,
constructor.raw_value(),
&mut result,
)
})?; })?;
Ok(result) Ok(result)
} }
} }
impl NapiValue for JsBigint { impl NapiValue for JsBigint {
fn raw_value(&self) -> sys::napi_value { fn raw(&self) -> sys::napi_value {
self.raw.value self.raw.value
} }

View file

@ -4,6 +4,7 @@ use super::Value;
use crate::error::check_status; use crate::error::check_status;
use crate::{sys, Error, Result}; use crate::{sys, Error, Result};
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsBoolean(pub(crate) Value); pub struct JsBoolean(pub(crate) Value);

View file

@ -1,194 +1,88 @@
use std::convert::TryFrom; use std::mem;
use std::ops::Deref; use std::ops::Deref;
use std::ptr; use std::ptr;
use std::slice;
use super::{JsNumber, JsObject, JsString, JsUnknown, NapiValue, Status, Value, ValueType}; use super::Value;
#[cfg(feature = "serde-json")]
use super::ValueType;
use crate::error::check_status; use crate::error::check_status;
use crate::{sys, Error, Result}; use crate::{sys, JsUnknown, Ref, Result};
#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct JsBuffer(pub(crate) Value);
#[derive(Debug)] #[derive(Debug)]
pub struct JsBuffer<'buffer> { pub struct JsBufferValue {
pub value: JsObject, pub(crate) value: JsBuffer,
pub data: &'buffer [u8], data: mem::ManuallyDrop<Vec<u8>>,
} }
impl<'buffer> JsBuffer<'buffer> { impl JsBuffer {
pub(crate) fn new(env: sys::napi_env, value: sys::napi_value, data: *mut u8, len: usize) -> Self { pub fn into_value(self) -> Result<JsBufferValue> {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: unsafe { slice::from_raw_parts_mut(data, len) },
}
}
#[inline]
pub fn into_unknown(self) -> Result<JsUnknown> {
JsUnknown::from_raw(self.value.0.env, self.value.0.value)
}
#[inline]
pub fn coerce_to_number(self) -> Result<JsNumber> {
let mut new_raw_value = ptr::null_mut();
check_status(unsafe {
sys::napi_coerce_to_number(self.value.0.env, self.value.0.value, &mut new_raw_value)
})?;
Ok(JsNumber(Value {
env: self.value.0.env,
value: new_raw_value,
value_type: ValueType::Number,
}))
}
#[inline]
pub fn coerce_to_string(self) -> Result<JsString> {
let mut new_raw_value = ptr::null_mut();
check_status(unsafe {
sys::napi_coerce_to_string(self.value.0.env, self.value.0.value, &mut new_raw_value)
})?;
Ok(JsString(Value {
env: self.value.0.env,
value: new_raw_value,
value_type: ValueType::String,
}))
}
#[inline]
pub fn coerce_to_object(self) -> Result<JsObject> {
let mut new_raw_value = ptr::null_mut();
check_status(unsafe {
sys::napi_coerce_to_object(self.value.0.env, self.value.0.value, &mut new_raw_value)
})?;
Ok(JsObject(Value {
env: self.value.0.env,
value: new_raw_value,
value_type: ValueType::Object,
}))
}
#[inline]
#[cfg(napi5)]
pub fn is_date(&self) -> Result<bool> {
let mut is_date = true;
check_status(unsafe { sys::napi_is_date(self.value.0.env, self.value.0.value, &mut is_date) })?;
Ok(is_date)
}
#[inline]
pub fn is_error(&self) -> Result<bool> {
let mut result = false;
check_status(unsafe { sys::napi_is_error(self.value.0.env, self.value.0.value, &mut result) })?;
Ok(result)
}
#[inline]
pub fn is_typedarray(&self) -> Result<bool> {
let mut result = false;
check_status(unsafe {
sys::napi_is_typedarray(self.value.0.env, self.value.0.value, &mut result)
})?;
Ok(result)
}
#[inline]
pub fn is_dataview(&self) -> Result<bool> {
let mut result = false;
check_status(unsafe {
sys::napi_is_dataview(self.value.0.env, self.value.0.value, &mut result)
})?;
Ok(result)
}
#[inline]
pub fn is_array(&self) -> Result<bool> {
let mut is_array = false;
check_status(unsafe {
sys::napi_is_array(self.value.0.env, self.value.0.value, &mut is_array)
})?;
Ok(is_array)
}
#[inline]
pub fn is_buffer(&self) -> Result<bool> {
let mut is_buffer = false;
check_status(unsafe {
sys::napi_is_buffer(self.value.0.env, self.value.0.value, &mut is_buffer)
})?;
Ok(is_buffer)
}
#[inline]
pub fn instanceof<Constructor: NapiValue>(&self, constructor: Constructor) -> Result<bool> {
let mut result = false;
check_status(unsafe {
sys::napi_instanceof(
self.value.0.env,
self.value.0.value,
constructor.raw_value(),
&mut result,
)
})?;
Ok(result)
}
}
impl<'buffer> NapiValue for JsBuffer<'buffer> {
fn raw_value(&self) -> sys::napi_value {
self.value.0.value
}
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut data = ptr::null_mut(); let mut data = ptr::null_mut();
let mut len: u64 = 0; let mut len: u64 = 0;
check_status(unsafe { sys::napi_get_buffer_info(env, value, &mut data, &mut len) })?; check_status(unsafe {
Ok(JsBuffer { sys::napi_get_buffer_info(self.0.env, self.0.value, &mut data, &mut len)
value: JsObject(Value { })?;
env, Ok(JsBufferValue {
value, data: mem::ManuallyDrop::new(unsafe {
value_type: ValueType::Object, Vec::from_raw_parts(data as *mut _, len as usize, len as usize)
}), }),
data: unsafe { slice::from_raw_parts_mut(data as *mut _, len as usize) }, value: self,
}) })
} }
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self { #[inline]
pub fn into_ref(self) -> Result<Ref<JsBufferValue>> {
Ref::new(self.0, 1, self.into_value()?)
}
}
impl JsBufferValue {
#[cfg(feature = "serde-json")]
pub(crate) fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut data = ptr::null_mut(); let mut data = ptr::null_mut();
let mut len: u64 = 0; let mut len: u64 = 0;
let status = unsafe { sys::napi_get_buffer_info(env, value, &mut data, &mut len) }; check_status(unsafe { sys::napi_get_buffer_info(env, value, &mut data, &mut len) })?;
debug_assert!( Ok(Self {
Status::from(status) == Status::Ok, value: JsBuffer(Value {
"napi_get_buffer_info failed"
);
JsBuffer {
value: JsObject(Value {
env, env,
value, value,
value_type: ValueType::Object, value_type: ValueType::Object,
}), }),
data: unsafe { slice::from_raw_parts_mut(data as *mut _, len as usize) }, data: mem::ManuallyDrop::new(unsafe {
Vec::from_raw_parts(data as *mut _, len as usize, len as usize)
}),
})
}
pub fn new(value: JsBuffer, data: Vec<u8>) -> Self {
JsBufferValue {
value,
data: mem::ManuallyDrop::new(data),
} }
} }
}
impl<'buffer> AsRef<[u8]> for JsBuffer<'buffer> { pub fn into_raw(self) -> JsBuffer {
fn as_ref(&self) -> &[u8] { self.value
self.data }
pub fn into_unknown(self) -> Result<JsUnknown> {
self.value.into_unknown()
} }
} }
impl<'buffer> Deref for JsBuffer<'buffer> { impl AsRef<[u8]> for JsBufferValue {
fn as_ref(&self) -> &[u8] {
self.data.as_slice()
}
}
impl Deref for JsBufferValue {
type Target = [u8]; type Target = [u8];
fn deref(&self) -> &[u8] { fn deref(&self) -> &[u8] {
self.data self.data.as_slice()
}
}
impl<'buffer> TryFrom<JsUnknown> for JsBuffer<'buffer> {
type Error = Error;
fn try_from(value: JsUnknown) -> Result<JsBuffer<'buffer>> {
JsBuffer::from_raw(value.0.env, value.0.value)
} }
} }

View file

@ -6,7 +6,9 @@ use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, V
use super::{type_of, NapiValue, Value, ValueType}; use super::{type_of, NapiValue, Value, ValueType};
#[cfg(napi6)] #[cfg(napi6)]
use crate::JsBigint; use crate::JsBigint;
use crate::{Error, JsBoolean, JsBuffer, JsNumber, JsObject, JsString, JsUnknown, Result, Status}; use crate::{
Error, JsBoolean, JsBufferValue, JsNumber, JsObject, JsString, JsUnknown, Result, Status,
};
pub(crate) struct De<'env>(pub(crate) &'env Value); pub(crate) struct De<'env>(pub(crate) &'env Value);
@ -35,7 +37,7 @@ impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
} }
ValueType::String => { ValueType::String => {
let js_string = JsString::from_raw_unchecked(self.0.env, self.0.value); let js_string = JsString::from_raw_unchecked(self.0.env, self.0.value);
visitor.visit_str(js_string.as_str()?) visitor.visit_str(js_string.into_utf8()?.as_str()?)
} }
ValueType::Object => { ValueType::Object => {
let js_object = JsObject::from_raw_unchecked(self.0.env, self.0.value); let js_object = JsObject::from_raw_unchecked(self.0.env, self.0.value);
@ -44,7 +46,7 @@ impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?); JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?);
visitor.visit_seq(&mut deserializer) visitor.visit_seq(&mut deserializer)
} else if js_object.is_buffer()? { } else if js_object.is_buffer()? {
visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data) visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
} else { } else {
let mut deserializer = JsObjectAccess::new(&js_object)?; let mut deserializer = JsObjectAccess::new(&js_object)?;
visitor.visit_map(&mut deserializer) visitor.visit_map(&mut deserializer)
@ -72,14 +74,14 @@ impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
where where
V: Visitor<'x>, V: Visitor<'x>,
{ {
visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data) visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
} }
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value> fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
where where
V: Visitor<'x>, V: Visitor<'x>,
{ {
visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data) visitor.visit_bytes(&JsBufferValue::from_raw(self.0.env, self.0.value)?)
} }
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value> fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
@ -105,8 +107,8 @@ impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
match js_value_type { match js_value_type {
ValueType::String => visitor.visit_enum(JsEnumAccess::new( ValueType::String => visitor.visit_enum(JsEnumAccess::new(
JsString::from_raw_unchecked(self.0.env, self.0.value) JsString::from_raw_unchecked(self.0.env, self.0.value)
.as_str()? .into_utf8()?
.to_owned(), .to_owned()?,
None, None,
)), )),
ValueType::Object => { ValueType::Object => {
@ -124,7 +126,10 @@ impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> {
} else { } else {
let key = properties.get_element::<JsString>(0)?; let key = properties.get_element::<JsString>(0)?;
let value: JsUnknown = js_object.get_property(&key)?; let value: JsUnknown = js_object.get_property(&key)?;
visitor.visit_enum(JsEnumAccess::new(key.as_str()?.to_owned(), Some(&value.0))) visitor.visit_enum(JsEnumAccess::new(
key.into_utf8()?.to_owned()?,
Some(&value.0),
))
} }
} }
_ => Err(Error::new( _ => Err(Error::new(

View file

@ -26,10 +26,10 @@ impl<A: NapiValue, B: NapiValue> NapiValue for Either<A, B> {
Self::from_raw(env, value).unwrap() Self::from_raw(env, value).unwrap()
} }
fn raw_value(&self) -> sys::napi_value { fn raw(&self) -> sys::napi_value {
match self { match self {
Either::A(v) => v.raw_value(), Either::A(v) => v.raw(),
Either::B(v) => v.raw_value(), Either::B(v) => v.raw(),
} }
} }
} }

View file

@ -0,0 +1,39 @@
use std::ops::Deref;
use std::ptr;
use super::check_status;
use crate::{sys, Env, NapiValue, Result};
pub struct EscapableHandleScope<T: NapiValue> {
handle_scope: sys::napi_escapable_handle_scope,
value: T,
}
impl<T: NapiValue> EscapableHandleScope<T> {
#[inline]
pub fn open(env: sys::napi_env, value: T) -> Result<Self> {
let mut handle_scope = ptr::null_mut();
check_status(unsafe { sys::napi_open_escapable_handle_scope(env, &mut handle_scope) })?;
let mut result = ptr::null_mut();
check_status(unsafe {
sys::napi_escape_handle(env, handle_scope, NapiValue::raw(&value), &mut result)
})?;
Ok(Self {
value,
handle_scope,
})
}
#[must_use]
pub fn close(self, env: Env) -> Result<()> {
check_status(unsafe { sys::napi_close_escapable_handle_scope(env.0, self.handle_scope) })
}
}
impl<T: NapiValue> Deref for EscapableHandleScope<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}

View file

@ -5,6 +5,7 @@ use super::Value;
use crate::error::check_status; use crate::error::check_status;
use crate::{sys, Env, Error, JsObject, JsUnknown, NapiValue, Result, Status}; use crate::{sys, Env, Error, JsObject, JsUnknown, NapiValue, Result, Status};
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsFunction(pub(crate) Value); pub struct JsFunction(pub(crate) Value);
@ -26,12 +27,12 @@ impl JsFunction {
/// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function)
pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result<JsUnknown> { pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result<JsUnknown> {
let raw_this = this let raw_this = this
.map(|v| v.raw_value()) .map(|v| v.raw())
.or_else(|| { .or_else(|| {
Env::from_raw(self.0.env) Env::from_raw(self.0.env)
.get_undefined() .get_undefined()
.ok() .ok()
.map(|u| u.raw_value()) .map(|u| u.raw())
}) })
.ok_or(Error::new( .ok_or(Error::new(
Status::Unknown, Status::Unknown,

View file

@ -15,6 +15,7 @@ mod bigint;
mod boolean; mod boolean;
mod buffer; mod buffer;
mod either; mod either;
mod escapable_handle_scope;
mod function; mod function;
mod number; mod number;
mod object; mod object;
@ -30,33 +31,38 @@ pub use arraybuffer::JsArrayBuffer;
#[cfg(napi6)] #[cfg(napi6)]
pub use bigint::JsBigint; pub use bigint::JsBigint;
pub use boolean::JsBoolean; pub use boolean::JsBoolean;
pub use buffer::JsBuffer; pub use buffer::*;
#[cfg(feature = "serde-json")] #[cfg(feature = "serde-json")]
pub(crate) use de::De; pub(crate) use de::De;
pub use either::Either; pub use either::Either;
pub use escapable_handle_scope::EscapableHandleScope;
pub use function::JsFunction; pub use function::JsFunction;
pub use number::JsNumber; pub use number::JsNumber;
pub use object::JsObject; pub use object::JsObject;
pub use object_property::Property; pub use object_property::Property;
#[cfg(feature = "serde-json")] #[cfg(feature = "serde-json")]
pub(crate) use ser::Ser; pub(crate) use ser::Ser;
pub use string::JsString; pub use string::*;
pub(crate) use tagged_object::TaggedObject; pub(crate) use tagged_object::TaggedObject;
pub use undefined::JsUndefined; pub use undefined::JsUndefined;
pub(crate) use value::Value; pub(crate) use value::Value;
pub(crate) use value_ref::Ref; pub use value_ref::Ref;
pub use value_type::ValueType; pub use value_type::ValueType;
// Value types // Value types
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsUnknown(pub(crate) Value); pub struct JsUnknown(pub(crate) Value);
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsNull(pub(crate) Value); pub struct JsNull(pub(crate) Value);
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsSymbol(pub(crate) Value); pub struct JsSymbol(pub(crate) Value);
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsExternal(pub(crate) Value); pub struct JsExternal(pub(crate) Value);
@ -88,7 +94,7 @@ macro_rules! impl_napi_value_trait {
} }
} }
fn raw_value(&self) -> sys::napi_value { fn raw(&self) -> sys::napi_value {
self.0.value self.0.value
} }
@ -130,6 +136,7 @@ macro_rules! impl_js_value_methods {
value_type: ValueType::Number, value_type: ValueType::Number,
})) }))
} }
#[inline] #[inline]
pub fn coerce_to_string(self) -> Result<JsString> { pub fn coerce_to_string(self) -> Result<JsString> {
let mut new_raw_value = ptr::null_mut(); let mut new_raw_value = ptr::null_mut();
@ -204,12 +211,7 @@ macro_rules! impl_js_value_methods {
pub fn instanceof<Constructor: NapiValue>(&self, constructor: Constructor) -> Result<bool> { pub fn instanceof<Constructor: NapiValue>(&self, constructor: Constructor) -> Result<bool> {
let mut result = false; let mut result = false;
check_status(unsafe { check_status(unsafe {
sys::napi_instanceof( sys::napi_instanceof(self.0.env, self.0.value, constructor.raw(), &mut result)
self.0.env,
self.0.value,
constructor.raw_value(),
&mut result,
)
})?; })?;
Ok(result) Ok(result)
} }
@ -222,13 +224,14 @@ pub trait NapiValue: Sized {
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self; fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self;
fn raw_value(&self) -> sys::napi_value; fn raw(&self) -> sys::napi_value;
} }
impl_js_value_methods!(JsUnknown); impl_js_value_methods!(JsUnknown);
impl_js_value_methods!(JsUndefined); impl_js_value_methods!(JsUndefined);
impl_js_value_methods!(JsNull); impl_js_value_methods!(JsNull);
impl_js_value_methods!(JsBoolean); impl_js_value_methods!(JsBoolean);
impl_js_value_methods!(JsBuffer);
impl_js_value_methods!(JsNumber); impl_js_value_methods!(JsNumber);
impl_js_value_methods!(JsString); impl_js_value_methods!(JsString);
impl_js_value_methods!(JsObject); impl_js_value_methods!(JsObject);
@ -241,6 +244,7 @@ use ValueType::*;
impl_napi_value_trait!(JsUndefined, Undefined); impl_napi_value_trait!(JsUndefined, Undefined);
impl_napi_value_trait!(JsNull, Null); impl_napi_value_trait!(JsNull, Null);
impl_napi_value_trait!(JsBoolean, Boolean); impl_napi_value_trait!(JsBoolean, Boolean);
impl_napi_value_trait!(JsBuffer, Object);
impl_napi_value_trait!(JsNumber, Number); impl_napi_value_trait!(JsNumber, Number);
impl_napi_value_trait!(JsString, String); impl_napi_value_trait!(JsString, String);
impl_napi_value_trait!(JsObject, Object); impl_napi_value_trait!(JsObject, Object);
@ -265,7 +269,7 @@ impl NapiValue for JsUnknown {
}) })
} }
fn raw_value(&self) -> sys::napi_value { fn raw(&self) -> sys::napi_value {
self.0.value self.0.value
} }
} }

View file

@ -4,6 +4,7 @@ use super::Value;
use crate::error::check_status; use crate::error::check_status;
use crate::{sys, Error, Result}; use crate::{sys, Error, Result};
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsNumber(pub(crate) Value); pub struct JsNumber(pub(crate) Value);

View file

@ -5,6 +5,7 @@ use super::Value;
use crate::error::check_status; use crate::error::check_status;
use crate::{sys, Error, JsString, NapiValue, Property, Result, Status}; use crate::{sys, Error, JsString, NapiValue, Property, Result, Status};
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsObject(pub(crate) Value); pub struct JsObject(pub(crate) Value);
@ -14,7 +15,7 @@ impl JsObject {
V: NapiValue, V: NapiValue,
{ {
check_status(unsafe { check_status(unsafe {
sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw_value()) sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw())
}) })
} }
@ -25,7 +26,7 @@ impl JsObject {
{ {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
check_status(unsafe { check_status(unsafe {
sys::napi_get_property(self.0.env, self.0.value, key.raw_value(), &mut raw_value) sys::napi_get_property(self.0.env, self.0.value, key.raw(), &mut raw_value)
})?; })?;
T::from_raw(self.0.env, raw_value) T::from_raw(self.0.env, raw_value)
} }
@ -36,7 +37,7 @@ impl JsObject {
{ {
let key = CString::new(name)?; let key = CString::new(name)?;
check_status(unsafe { check_status(unsafe {
sys::napi_set_named_property(self.0.env, self.0.value, key.as_ptr(), value.raw_value()) sys::napi_set_named_property(self.0.env, self.0.value, key.as_ptr(), value.raw())
}) })
} }
@ -70,7 +71,7 @@ impl JsObject {
{ {
let mut result = false; let mut result = false;
check_status(unsafe { check_status(unsafe {
sys::napi_delete_property(self.0.env, self.0.value, name.raw_value(), &mut result) sys::napi_delete_property(self.0.env, self.0.value, name.raw(), &mut result)
})?; })?;
Ok(result) Ok(result)
} }
@ -107,7 +108,7 @@ impl JsObject {
{ {
let mut result = false; let mut result = false;
check_status(unsafe { check_status(unsafe {
sys::napi_has_own_property(self.0.env, self.0.value, key.raw_value(), &mut result) sys::napi_has_own_property(self.0.env, self.0.value, key.raw(), &mut result)
})?; })?;
Ok(result) Ok(result)
} }
@ -129,7 +130,7 @@ impl JsObject {
{ {
let mut result = false; let mut result = false;
check_status(unsafe { check_status(unsafe {
sys::napi_has_property(self.0.env, self.0.value, name.raw_value(), &mut result) sys::napi_has_property(self.0.env, self.0.value, name.raw(), &mut result)
})?; })?;
Ok(result) Ok(result)
} }
@ -157,9 +158,7 @@ impl JsObject {
where where
T: NapiValue, T: NapiValue,
{ {
check_status(unsafe { check_status(unsafe { sys::napi_set_element(self.0.env, self.0.value, index, value.raw()) })
sys::napi_set_element(self.0.env, self.0.value, index, value.raw_value())
})
} }
pub fn has_element(&self, index: u32) -> Result<bool> { pub fn has_element(&self, index: u32) -> Result<bool> {
@ -215,7 +214,7 @@ impl JsObject {
#[inline] #[inline]
pub fn get_array_length_unchecked(&self) -> Result<u32> { pub fn get_array_length_unchecked(&self) -> Result<u32> {
let mut length: u32 = 0; let mut length: u32 = 0;
check_status(unsafe { sys::napi_get_array_length(self.0.env, self.raw_value(), &mut length) })?; check_status(unsafe { sys::napi_get_array_length(self.0.env, self.raw(), &mut length) })?;
Ok(length) Ok(length)
} }
} }

View file

@ -55,7 +55,7 @@ impl<'env> Property<'env> {
} }
pub fn with_value<T: NapiValue>(mut self, value: T) -> Self { pub fn with_value<T: NapiValue>(mut self, value: T) -> Self {
self.raw_descriptor.value = T::raw_value(&value); self.raw_descriptor.value = T::raw(&value);
self self
} }

View file

@ -1,165 +0,0 @@
use std::convert::TryFrom;
use std::mem;
use std::ptr;
use std::slice;
use std::str;
#[cfg(feature = "latin1")]
use encoding_rs;
use super::Value;
use crate::error::check_status;
use crate::{sys, Error, Result, Status};
#[derive(Debug)]
pub struct JsString(pub(crate) Value);
impl JsString {
pub fn utf8_len(&self) -> Result<usize> {
let mut length = 0;
check_status(unsafe {
sys::napi_get_value_string_utf8(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn latin1_len(&self) -> Result<usize> {
let mut length = 0;
check_status(unsafe {
sys::napi_get_value_string_latin1(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
}
impl JsString {
pub fn get_utf8(&self) -> Result<&[u8]> {
let mut written_char_count: u64 = 0;
let len = self.utf8_len()? + 1;
let mut result = Vec::with_capacity(len);
unsafe {
check_status(sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
result.as_mut_ptr(),
len as u64,
&mut written_char_count,
))?;
let ptr = result.as_ptr();
mem::forget(result);
Ok(slice::from_raw_parts(
ptr as *const u8,
written_char_count as usize,
))
}
}
pub fn get_latin1(&self) -> Result<(&[u8], usize)> {
let mut written_char_count: u64 = 0;
let len = self.latin1_len()? + 1;
let mut result = Vec::with_capacity(len);
unsafe {
check_status(sys::napi_get_value_string_latin1(
self.0.env,
self.0.value,
result.as_mut_ptr(),
len as u64,
&mut written_char_count,
))?;
let ptr = result.as_ptr();
mem::forget(result);
Ok((
slice::from_raw_parts(ptr as *const u8, written_char_count as usize),
written_char_count as usize,
))
}
}
#[inline]
pub fn get_utf8_chars(&self) -> Result<&[char]> {
let mut written_char_count: u64 = 0;
let len = self.utf8_len()? + 1;
let mut result = Vec::with_capacity(len);
unsafe {
check_status(sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
result.as_mut_ptr(),
len as u64,
&mut written_char_count,
))?;
let ptr = result.as_ptr();
mem::forget(result);
Ok(slice::from_raw_parts(
ptr as *const char,
written_char_count as usize,
))
}
}
pub fn as_str(&self) -> Result<&str> {
str::from_utf8(self.get_utf8()?)
.map_err(|e| Error::new(Status::GenericFailure, format!("{:?}", e)))
}
#[cfg(feature = "latin1")]
pub fn as_latin1_string(&self) -> Result<String> {
let (latin1_bytes, len) = self.get_latin1()?;
let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; len * 2 + 1]) };
encoding_rs::mem::convert_latin1_to_str(latin1_bytes, dst_str.as_mut_str());
Ok(dst_str)
}
pub fn get_ref_mut(&mut self) -> Result<&mut [u8]> {
let mut written_char_count: u64 = 0;
let len = self.utf8_len()? + 1;
let mut result = Vec::with_capacity(len);
unsafe {
check_status(sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
result.as_mut_ptr(),
len as u64,
&mut written_char_count,
))?;
let ptr = result.as_ptr();
mem::forget(result);
Ok(slice::from_raw_parts_mut(
ptr as *mut _,
written_char_count as usize,
))
}
}
}
impl TryFrom<JsString> for Vec<u16> {
type Error = Error;
fn try_from(value: JsString) -> Result<Vec<u16>> {
let mut result = Vec::with_capacity(value.utf8_len()? + 1); // Leave room for trailing null byte
unsafe {
let mut written_char_count = 0;
check_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,
))?;
result.set_len(written_char_count as usize);
}
Ok(result)
}
}
impl TryFrom<JsString> for String {
type Error = Error;
fn try_from(value: JsString) -> Result<String> {
Ok(value.as_str()?.to_owned())
}
}

View file

@ -0,0 +1,45 @@
use std::mem::ManuallyDrop;
#[cfg(feature = "latin1")]
use encoding_rs;
use crate::JsString;
#[cfg(feature = "latin1")]
use crate::Result;
pub struct JsStringLatin1 {
pub(crate) inner: JsString,
pub(crate) buf: ManuallyDrop<Vec<u8>>,
}
impl JsStringLatin1 {
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn take(self) -> Vec<u8> {
self.as_slice().to_vec()
}
pub fn into_value(self) -> JsString {
self.inner
}
#[cfg(feature = "latin1")]
pub fn into_latin1_string(self) -> Result<String> {
let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; self.len() * 2 + 1]) };
encoding_rs::mem::convert_latin1_to_str(self.buf.as_slice(), dst_str.as_mut_str());
Ok(dst_str)
}
}
impl From<JsStringLatin1> for Vec<u8> {
fn from(value: JsStringLatin1) -> Self {
value.take()
}
}

View file

@ -0,0 +1,138 @@
use std::mem;
use std::ptr;
use super::Value;
use crate::error::check_status;
use crate::{sys, Ref, Result};
pub use latin1::JsStringLatin1;
pub use utf16::JsStringUtf16;
pub use utf8::JsStringUtf8;
mod latin1;
mod utf16;
mod utf8;
#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct JsString(pub(crate) Value);
impl JsString {
pub fn utf8_len(&self) -> Result<usize> {
let mut length = 0;
check_status(unsafe {
sys::napi_get_value_string_utf8(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn utf16_len(&self) -> Result<usize> {
let mut length = 0;
check_status(unsafe {
sys::napi_get_value_string_utf16(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn latin1_len(&self) -> Result<usize> {
let mut length = 0;
check_status(unsafe {
sys::napi_get_value_string_latin1(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length)
})?;
Ok(length as usize)
}
pub fn into_utf8(self) -> Result<JsStringUtf8> {
let mut written_char_count: u64 = 0;
let len = self.utf8_len()? + 1;
let mut result = Vec::with_capacity(len);
let buf_ptr = result.as_mut_ptr();
check_status(unsafe {
sys::napi_get_value_string_utf8(
self.0.env,
self.0.value,
buf_ptr,
len as u64,
&mut written_char_count,
)
})?;
mem::forget(result);
Ok(JsStringUtf8 {
inner: self,
buf: mem::ManuallyDrop::new(unsafe {
Vec::from_raw_parts(
buf_ptr as *mut _,
written_char_count as _,
written_char_count as _,
)
}),
})
}
pub fn into_utf8_ref(self) -> Result<Ref<JsStringUtf8>> {
Ref::new(self.0, 1, self.into_utf8()?)
}
pub fn into_utf16(self) -> Result<JsStringUtf16> {
let mut written_char_count: u64 = 0;
let len = self.utf16_len()? + 1;
let mut result = Vec::with_capacity(len);
let buf_ptr = result.as_mut_ptr();
check_status(unsafe {
sys::napi_get_value_string_utf16(
self.0.env,
self.0.value,
buf_ptr,
len as u64,
&mut written_char_count,
)
})?;
mem::forget(result);
Ok(JsStringUtf16 {
inner: self,
buf: mem::ManuallyDrop::new(unsafe {
Vec::from_raw_parts(buf_ptr, written_char_count as _, written_char_count as _)
}),
})
}
pub fn into_utf16_ref(self) -> Result<Ref<JsStringUtf16>> {
Ref::new(self.0, 1, self.into_utf16()?)
}
pub fn into_latin1(self) -> Result<JsStringLatin1> {
let mut written_char_count: u64 = 0;
let len = self.latin1_len()? + 1;
let mut result = Vec::with_capacity(len);
let buf_ptr = result.as_mut_ptr();
check_status(unsafe {
sys::napi_get_value_string_latin1(
self.0.env,
self.0.value,
buf_ptr,
len as u64,
&mut written_char_count,
)
})?;
mem::forget(result);
Ok(JsStringLatin1 {
inner: self,
buf: mem::ManuallyDrop::new(unsafe {
Vec::from_raw_parts(
buf_ptr as *mut _,
written_char_count as _,
written_char_count as _,
)
}),
})
}
pub fn into_latin1_ref(self) -> Result<Ref<JsStringLatin1>> {
Ref::new(self.0, 1, self.into_latin1()?)
}
}

View file

@ -0,0 +1,57 @@
use std::convert::TryFrom;
use std::mem;
use std::ops::Deref;
use crate::{Error, JsString, Result, Status};
pub struct JsStringUtf16 {
pub(crate) inner: JsString,
pub(crate) buf: mem::ManuallyDrop<Vec<u16>>,
}
impl JsStringUtf16 {
pub fn as_str(&self) -> Result<String> {
String::from_utf16(self.as_slice())
.map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
}
pub fn as_slice(&self) -> &[u16] {
self.buf.as_slice()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn into_value(self) -> JsString {
self.inner
}
}
impl TryFrom<JsStringUtf16> for String {
type Error = Error;
fn try_from(value: JsStringUtf16) -> Result<String> {
value.as_str()
}
}
impl Deref for JsStringUtf16 {
type Target = [u16];
fn deref(&self) -> &[u16] {
self.buf.as_slice()
}
}
impl AsRef<Vec<u16>> for JsStringUtf16 {
fn as_ref(&self) -> &Vec<u16> {
&self.buf
}
}
impl From<JsStringUtf16> for Vec<u16> {
fn from(value: JsStringUtf16) -> Self {
value.as_slice().to_vec()
}
}

View file

@ -0,0 +1,51 @@
use std::convert::TryFrom;
use std::mem::ManuallyDrop;
use std::str;
use crate::{Error, JsString, Result, Status};
pub struct JsStringUtf8 {
pub(crate) inner: JsString,
pub(crate) buf: ManuallyDrop<Vec<u8>>,
}
impl JsStringUtf8 {
pub fn as_str(&self) -> Result<&str> {
str::from_utf8(self.buf.as_slice())
.map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
}
pub fn as_slice(&self) -> &[u8] {
&self.buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn to_owned(self) -> Result<String> {
Ok(self.as_str()?.to_owned())
}
pub fn take(self) -> Vec<u8> {
self.as_slice().to_vec()
}
pub fn into_value(self) -> JsString {
self.inner
}
}
impl TryFrom<JsStringUtf8> for String {
type Error = Error;
fn try_from(value: JsStringUtf8) -> Result<String> {
Ok(value.as_str()?.to_owned())
}
}
impl From<JsStringUtf8> for Vec<u8> {
fn from(value: JsStringUtf8) -> Self {
value.take()
}
}

View file

@ -1,4 +1,5 @@
use super::Value; use super::Value;
#[repr(transparent)]
#[derive(Debug)] #[derive(Debug)]
pub struct JsUndefined(pub(crate) Value); pub struct JsUndefined(pub(crate) Value);

View file

@ -1,35 +1,62 @@
use std::marker::PhantomData; use std::ops::Deref;
use std::ptr;
use super::NapiValue; use super::{check_status, Value};
use crate::{sys, Status}; use crate::{sys, Env, Result};
pub struct Ref<T: NapiValue> { pub struct Ref<T> {
pub(crate) raw_env: sys::napi_env, pub(crate) raw_ref: sys::napi_ref,
pub(crate) ref_value: sys::napi_ref, count: u32,
_phantom: PhantomData<T>, inner: T,
} }
impl<T: NapiValue> Ref<T> { unsafe impl<T> Send for Ref<T> {}
pub fn new(raw_env: sys::napi_env, ref_value: sys::napi_ref) -> Ref<T> { unsafe impl<T> Sync for Ref<T> {}
Ref {
raw_env, impl<T> Ref<T> {
ref_value, pub(crate) fn new(js_value: Value, ref_count: u32, inner: T) -> Result<Ref<T>> {
_phantom: PhantomData, let mut raw_ref = ptr::null_mut();
let initial_ref_count = 1;
check_status(unsafe {
sys::napi_create_reference(
js_value.env,
js_value.value,
initial_ref_count,
&mut raw_ref,
)
})?;
Ok(Ref {
raw_ref,
count: ref_count,
inner,
})
}
#[must_use]
pub fn unref(mut self, env: Env) -> Result<u32> {
check_status(unsafe { sys::napi_reference_unref(env.0, self.raw_ref, &mut self.count) })?;
if self.count == 0 {
check_status(unsafe { sys::napi_delete_reference(env.0, self.raw_ref) })?;
} }
Ok(self.count)
} }
} }
impl<T: NapiValue> Drop for Ref<T> { impl<T> Deref for Ref<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
#[cfg(debug_assertions)]
impl<T> Drop for Ref<T> {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { debug_assert_eq!(
let mut ref_count = 0; self.count, 0,
let status = sys::napi_reference_unref(self.raw_env, self.ref_value, &mut ref_count); "Ref count is not equal to 0 while dropping Ref, potential memory leak"
debug_assert!(Status::from(status) == Status::Ok); );
if ref_count == 0 {
let status = sys::napi_delete_reference(self.raw_env, self.ref_value);
debug_assert!(Status::from(status) == Status::Ok);
}
}
} }
} }

View file

@ -199,7 +199,7 @@ macro_rules! register_module {
let hook_result = Ok(()); let hook_result = Ok(());
match hook_result.and_then(move |_| result) { match hook_result.and_then(move |_| result) {
Ok(_) => cjs_module.exports.raw_value(), Ok(_) => cjs_module.exports.raw(),
Err(e) => { Err(e) => {
unsafe { unsafe {
sys::napi_throw_error( sys::napi_throw_error(

View file

@ -106,7 +106,7 @@ unsafe extern "C" fn call_js_cb<T, V: NapiValue>(
let js_value_to_resolve = value.and_then(move |v| (resolver)(&mut env, v)); let js_value_to_resolve = value.and_then(move |v| (resolver)(&mut env, v));
match js_value_to_resolve { match js_value_to_resolve {
Ok(v) => { Ok(v) => {
let status = sys::napi_resolve_deferred(raw_env, deferred, v.raw_value()); let status = sys::napi_resolve_deferred(raw_env, deferred, v.raw());
debug_assert!( debug_assert!(
status == sys::napi_status::napi_ok, status == sys::napi_status::napi_ok,
"Resolve promise failed" "Resolve promise failed"

View file

@ -7,5 +7,5 @@ pub trait Task: Send {
fn compute(&mut self) -> Result<Self::Output>; fn compute(&mut self) -> Result<Self::Output>;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Self::JsValue>; fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue>;
} }

View file

@ -6,3 +6,9 @@ test('should be able to spawn thread and return promise', async (t) => {
const result = await bindings.testSpawnThread(20) const result = await bindings.testSpawnThread(20)
t.is(result, 6765) t.is(result, 6765)
}) })
test('should be able to spawn thread with ref value', async (t) => {
const fixture = 'hello'
const result = await bindings.testSpawnThreadWithRef(Buffer.from(fixture))
t.is(result, fixture.length)
})

View file

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

View file

@ -11,7 +11,7 @@ pub fn either_number_string(ctx: CallContext) -> Result<Either<JsNumber, JsStrin
ctx.env.create_uint32(n + 100).map(Either::A) ctx.env.create_uint32(n + 100).map(Either::A)
} }
Either::B(s) => { Either::B(s) => {
let content = format!("Either::B({})", s.as_str()?); let content = format!("Either::B({})", s.into_utf8()?.as_str()?);
ctx.env.create_string_from_std(content).map(Either::B) ctx.env.create_string_from_std(content).map(Either::B)
} }
} }

View file

@ -10,7 +10,7 @@ fn test_throw_with_reason(ctx: CallContext) -> Result<JsUnknown> {
let reason = ctx.get::<JsString>(0)?; let reason = ctx.get::<JsString>(0)?;
Err(Error::new( Err(Error::new(
Status::GenericFailure, Status::GenericFailure,
reason.as_str()?.to_owned(), reason.into_utf8()?.as_str()?.to_owned(),
)) ))
} }

View file

@ -9,7 +9,7 @@ use napi::{CallContext, Error, JsObject, JsString, Module, Result, Status};
pub fn uv_read_file(ctx: CallContext) -> Result<JsObject> { pub fn uv_read_file(ctx: CallContext) -> Result<JsObject> {
let path = ctx.get::<JsString>(0)?; let path = ctx.get::<JsString>(0)?;
let (sender, receiver) = oneshot::channel(); let (sender, receiver) = oneshot::channel();
let p = path.as_str()?.to_owned(); let p = path.into_utf8()?.to_owned()?;
thread::spawn(|| { thread::spawn(|| {
let res = fs::read(p).map_err(|e| Error::new(Status::Unknown, format!("{}", e))); let res = fs::read(p).map_err(|e| Error::new(Status::Unknown, format!("{}", e)));
sender.send(res).expect("Send data failed"); sender.send(res).expect("Send data failed");
@ -18,7 +18,7 @@ pub fn uv_read_file(ctx: CallContext) -> Result<JsObject> {
receiver receiver
.map_err(|e| Error::new(Status::Unknown, format!("{}", e))) .map_err(|e| Error::new(Status::Unknown, format!("{}", e)))
.map(|x| x.and_then(|x| x)), .map(|x| x.and_then(|x| x)),
|&mut env, data| env.create_buffer_with_data(data), |&mut env, data| env.create_buffer_with_data(data).map(|v| v.into_raw()),
) )
} }

View file

@ -85,22 +85,21 @@ impl ToJs for HandleBuffer {
async fn read_file_content(filepath: &Path) -> Result<Vec<u8>> { async fn read_file_content(filepath: &Path) -> Result<Vec<u8>> {
tokio::fs::read(filepath) tokio::fs::read(filepath)
.await .await
.map_err(|_| Error::new(Status::Unknown, "failed to read file".to_owned())) .map_err(|e| Error::new(Status::Unknown, format!("{}", e)))
} }
#[js_function(2)] #[js_function(2)]
pub fn test_tokio_readfile(ctx: CallContext) -> Result<JsUndefined> { pub fn test_tokio_readfile(ctx: CallContext) -> Result<JsUndefined> {
let js_filepath = ctx.get::<JsString>(0)?; let js_filepath = ctx.get::<JsString>(0)?;
let js_func = ctx.get::<JsFunction>(1)?; let js_func = ctx.get::<JsFunction>(1)?;
let path_str = js_filepath.as_str()?; let path_str = js_filepath.into_utf8()?.to_owned()?;
let to_js = HandleBuffer; let to_js = HandleBuffer;
let tsfn = ThreadsafeFunction::create(ctx.env, js_func, to_js, 0)?; let tsfn = ThreadsafeFunction::create(ctx.env, js_func, to_js, 0)?;
let mut rt = tokio::runtime::Runtime::new().unwrap(); let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async move { rt.block_on(async move {
let mut filepath = Path::new(path_str); let ret = read_file_content(&Path::new(&path_str)).await;
let ret = read_file_content(&mut filepath).await;
let _ = tsfn.call(ret, ThreadsafeFunctionCallMode::Blocking); let _ = tsfn.call(ret, ThreadsafeFunctionCallMode::Blocking);
tsfn tsfn
.release(ThreadsafeFunctionReleaseMode::Release) .release(ThreadsafeFunctionReleaseMode::Release)

View file

@ -31,14 +31,18 @@ fn test_get_named_property(ctx: CallContext) -> Result<JsUnknown> {
fn test_has_named_property(ctx: CallContext) -> Result<JsBoolean> { fn test_has_named_property(ctx: CallContext) -> Result<JsBoolean> {
let obj = ctx.get::<JsObject>(0)?; let obj = ctx.get::<JsObject>(0)?;
let key = ctx.get::<JsString>(1)?; let key = ctx.get::<JsString>(1)?;
ctx.env.get_boolean(obj.has_named_property(key.as_str()?)?) ctx
.env
.get_boolean(obj.has_named_property(key.into_utf8()?.as_str()?)?)
} }
#[js_function(2)] #[js_function(2)]
fn test_has_own_property(ctx: CallContext) -> Result<JsBoolean> { fn test_has_own_property(ctx: CallContext) -> Result<JsBoolean> {
let obj = ctx.get::<JsObject>(0)?; let obj = ctx.get::<JsObject>(0)?;
let key = ctx.get::<JsString>(1)?; let key = ctx.get::<JsString>(1)?;
ctx.env.get_boolean(obj.has_own_property(key.as_str()?)?) ctx
.env
.get_boolean(obj.has_own_property(key.into_utf8()?.as_str()?)?)
} }
#[js_function(2)] #[js_function(2)]
@ -52,7 +56,9 @@ fn test_has_own_property_js(ctx: CallContext) -> Result<JsBoolean> {
fn test_has_property(ctx: CallContext) -> Result<JsBoolean> { fn test_has_property(ctx: CallContext) -> Result<JsBoolean> {
let obj = ctx.get::<JsObject>(0)?; let obj = ctx.get::<JsObject>(0)?;
let key = ctx.get::<JsString>(1)?; let key = ctx.get::<JsString>(1)?;
ctx.env.get_boolean(obj.has_property(key.as_str()?)?) ctx
.env
.get_boolean(obj.has_property(key.into_utf8()?.as_str()?)?)
} }
#[js_function(2)] #[js_function(2)]
@ -75,7 +81,7 @@ fn test_delete_named_property(ctx: CallContext) -> Result<JsBoolean> {
let key = ctx.get::<JsString>(1)?; let key = ctx.get::<JsString>(1)?;
ctx ctx
.env .env
.get_boolean(obj.delete_named_property(key.as_str()?)?) .get_boolean(obj.delete_named_property(key.into_utf8()?.as_str()?)?)
} }
#[js_function(2)] #[js_function(2)]

View file

@ -3,14 +3,17 @@ use napi::{CallContext, JsString, Module, Result};
#[js_function(1)] #[js_function(1)]
fn concat_string(ctx: CallContext) -> Result<JsString> { fn concat_string(ctx: CallContext) -> Result<JsString> {
let in_string = ctx.get::<JsString>(0)?; let in_string = ctx.get::<JsString>(0)?;
let out_string = format!("{} + Rust 🦀 string!", in_string.as_str()?); let out_string = format!("{} + Rust 🦀 string!", in_string.into_utf8()?.as_str()?);
ctx.env.create_string_from_std(out_string) ctx.env.create_string_from_std(out_string)
} }
#[js_function(1)] #[js_function(1)]
fn concat_latin1_string(ctx: CallContext) -> Result<JsString> { fn concat_latin1_string(ctx: CallContext) -> Result<JsString> {
let in_string = ctx.get::<JsString>(0)?; let in_string = ctx.get::<JsString>(0)?;
let out_string = format!("{} + Rust 🦀 string!", in_string.as_latin1_string()?); let out_string = format!(
"{} + Rust 🦀 string!",
in_string.into_latin1()?.into_latin1_string()?
);
ctx.env.create_string_from_std(out_string) ctx.env.create_string_from_std(out_string)
} }

View file

@ -1,6 +1,8 @@
use std::convert::TryInto; use std::convert::TryInto;
use napi::{CallContext, Env, JsNumber, JsObject, Module, Result, Task}; use napi::{
CallContext, Env, JsBuffer, JsBufferValue, JsNumber, JsObject, Module, Ref, Result, Task,
};
struct ComputeFib { struct ComputeFib {
n: u32, n: u32,
@ -20,7 +22,7 @@ impl Task for ComputeFib {
Ok(fibonacci_native(self.n)) Ok(fibonacci_native(self.n))
} }
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Self::JsValue> { fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
env.create_uint32(output) env.create_uint32(output)
} }
} }
@ -41,7 +43,40 @@ fn test_spawn_thread(ctx: CallContext) -> Result<JsObject> {
Ok(async_promise.promise_object()) Ok(async_promise.promise_object())
} }
struct CountBufferLength {
data: Ref<JsBufferValue>,
}
impl CountBufferLength {
pub fn new(data: Ref<JsBufferValue>) -> Self {
Self { data }
}
}
impl Task for CountBufferLength {
type Output = usize;
type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> {
Ok((&self.data).len())
}
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
self.data.unref(env)?;
env.create_uint32(output as _)
}
}
#[js_function(1)]
fn test_spawn_thread_with_ref(ctx: CallContext) -> Result<JsObject> {
let n = ctx.get::<JsBuffer>(0)?.into_ref()?;
let task = CountBufferLength::new(n);
let async_work_promise = ctx.env.spawn(task)?;
Ok(async_work_promise.promise_object())
}
pub fn register_js(module: &mut Module) -> Result<()> { pub fn register_js(module: &mut Module) -> Result<()> {
module.create_named_method("testSpawnThread", test_spawn_thread)?; module.create_named_method("testSpawnThread", test_spawn_thread)?;
module.create_named_method("testSpawnThreadWithRef", test_spawn_thread_with_ref)?;
Ok(()) Ok(())
} }

View file

@ -5,20 +5,20 @@ use tokio;
#[js_function(1)] #[js_function(1)]
pub fn test_execute_tokio_readfile(ctx: CallContext) -> Result<JsObject> { pub fn test_execute_tokio_readfile(ctx: CallContext) -> Result<JsObject> {
let js_filepath = ctx.get::<JsString>(0)?; let js_filepath = ctx.get::<JsString>(0)?;
let path_str = js_filepath.as_str()?; let path_str = js_filepath.into_utf8()?.to_owned()?;
ctx.env.execute_tokio_future( ctx.env.execute_tokio_future(
tokio::fs::read(path_str.to_owned()) tokio::fs::read(path_str)
.map(|v| v.map_err(|e| Error::new(Status::Unknown, format!("failed to read file, {}", e)))), .map(|v| v.map_err(|e| Error::new(Status::Unknown, format!("failed to read file, {}", e)))),
|&mut env, data| env.create_buffer_with_data(data), |&mut env, data| env.create_buffer_with_data(data).map(|v| v.into_raw()),
) )
} }
#[js_function(1)] #[js_function(1)]
pub fn error_from_tokio_future(ctx: CallContext) -> Result<JsObject> { pub fn error_from_tokio_future(ctx: CallContext) -> Result<JsObject> {
let js_filepath = ctx.get::<JsString>(0)?; let js_filepath = ctx.get::<JsString>(0)?;
let path_str = js_filepath.as_str()?; let path_str = js_filepath.into_utf8()?.to_owned()?;
ctx.env.execute_tokio_future( ctx.env.execute_tokio_future(
tokio::fs::read(path_str.to_owned()) tokio::fs::read(path_str)
.map_err(Error::from) .map_err(Error::from)
.and_then(|_| async move { .and_then(|_| async move {
Err::<Vec<u8>, Error>(Error::new( Err::<Vec<u8>, Error>(Error::new(
@ -26,6 +26,6 @@ pub fn error_from_tokio_future(ctx: CallContext) -> Result<JsObject> {
"Error from tokio future".to_owned(), "Error from tokio future".to_owned(),
)) ))
}), }),
|&mut env, data| env.create_buffer_with_data(data), |&mut env, data| env.create_buffer_with_data(data).map(|v| v.into_raw()),
) )
} }