From ea3fff25aeaaaaa936949c66a8bfcb7cc83f09b8 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 26 Aug 2020 00:07:27 +0800 Subject: [PATCH] feat(napi): serde-json feature --- napi/Cargo.toml | 8 + napi/src/env.rs | 41 +- napi/src/error.rs | 25 +- napi/src/js_values/arraybuffer.rs | 12 +- napi/src/js_values/bigint.rs | 195 +++++++- napi/src/js_values/buffer.rs | 13 +- napi/src/js_values/de.rs | 358 ++++++++++++++ napi/src/js_values/mod.rs | 22 +- napi/src/js_values/object.rs | 45 +- napi/src/js_values/ser.rs | 510 ++++++++++++++++++++ napi/src/js_values/string.rs | 24 + napi/src/js_values/value.rs | 2 +- napi/src/lib.rs | 4 + napi/src/threadsafe_function.rs | 67 ++- test_module/Cargo.toml | 6 +- test_module/__test__/napi-version.ts | 3 +- test_module/__test__/serde/de.spec.ts | 39 ++ test_module/__test__/serde/ser.spec.ts | 26 + test_module/__test__/serde/ser.spec.ts.md | 130 +++++ test_module/__test__/serde/ser.spec.ts.snap | Bin 0 -> 1038 bytes test_module/src/lib.rs | 22 +- test_module/src/napi4/tsfn.rs | 39 +- test_module/src/napi6/bigint.rs | 16 +- test_module/src/serde.rs | 182 +++++++ 24 files changed, 1672 insertions(+), 117 deletions(-) create mode 100644 napi/src/js_values/de.rs create mode 100644 napi/src/js_values/ser.rs create mode 100644 test_module/__test__/serde/de.spec.ts create mode 100644 test_module/__test__/serde/ser.spec.ts create mode 100644 test_module/__test__/serde/ser.spec.ts.md create mode 100644 test_module/__test__/serde/ser.spec.ts.snap create mode 100644 test_module/src/serde.rs diff --git a/napi/Cargo.toml b/napi/Cargo.toml index e68e90d0..34a6da47 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [features] libuv = ["futures"] tokio_rt = ["futures", "tokio", "once_cell"] +serde-json = ["serde", "serde_json"] [dependencies] napi-sys = { version = "0.4", path = "../sys" } @@ -29,6 +30,13 @@ optional = true version = "1.4" optional = true +[dependencies.serde] +version = "1" +optional = true + +[dependencies.serde_json] +version = "1" +optional = true [build-dependencies] napi-build = { version = "0.2", path = "../build" } diff --git a/napi/src/env.rs b/napi/src/env.rs index 0c2c72a6..00b47968 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -11,12 +11,18 @@ use crate::js_values::*; use crate::task::Task; use crate::{sys, Error, NodeVersion, Result, Status}; +#[cfg(all(feature = "serde-json"))] +use crate::js_values::{De, Ser}; #[cfg(all(any(feature = "libuv", feature = "tokio_rt"), napi4))] use crate::promise; #[cfg(all(feature = "tokio_rt", napi4))] use crate::tokio_rt::{get_tokio_sender, Message}; #[cfg(all(feature = "libuv", napi4))] use crate::uv; +#[cfg(all(feature = "serde-json"))] +use serde::de::DeserializeOwned; +#[cfg(all(feature = "serde-json"))] +use serde::Serialize; #[cfg(all(feature = "libuv", napi4))] use std::future::Future; #[cfg(all(feature = "tokio_rt", napi4))] @@ -98,7 +104,7 @@ impl Env { pub fn create_bigint_from_i64(&self, value: i64) -> Result { let mut raw_value = ptr::null_mut(); check_status(unsafe { sys::napi_create_bigint_int64(self.0, value, &mut raw_value) })?; - Ok(JsBigint::from_raw_unchecked(self.0, raw_value)) + Ok(JsBigint::from_raw_unchecked(self.0, raw_value, 1)) } #[cfg(napi6)] @@ -107,7 +113,7 @@ impl Env { pub fn create_bigint_from_u64(&self, value: u64) -> Result { let mut raw_value = ptr::null_mut(); check_status(unsafe { sys::napi_create_bigint_uint64(self.0, value, &mut raw_value) })?; - Ok(JsBigint::from_raw_unchecked(self.0, raw_value)) + Ok(JsBigint::from_raw_unchecked(self.0, raw_value, 1)) } #[cfg(napi6)] @@ -116,6 +122,7 @@ impl Env { /// The resulting BigInt will be negative when sign_bit is true. pub fn create_bigint_from_words(&self, sign_bit: bool, words: Vec) -> Result { let mut raw_value = ptr::null_mut(); + let len = words.len(); check_status(unsafe { sys::napi_create_bigint_words( self.0, @@ -123,12 +130,12 @@ impl Env { true => 1, false => 0, }, - words.len() as u64, + len as u64, words.as_ptr(), &mut raw_value, ) })?; - Ok(JsBigint::from_raw_unchecked(self.0, raw_value)) + Ok(JsBigint::from_raw_unchecked(self.0, raw_value, len as _)) } #[inline] @@ -608,6 +615,32 @@ impl Env { Ok(JsObject::from_raw_unchecked(self.0, raw_promise)) } + #[cfg(feature = "serde-json")] + #[inline] + pub fn to_js_value(&self, node: &T) -> Result + where + T: Serialize, + { + let s = Ser(self); + node.serialize(s).map(JsUnknown) + } + + #[cfg(feature = "serde-json")] + #[inline] + pub fn from_js_value(&self, value: V) -> Result + where + T: DeserializeOwned + ?Sized, + V: NapiValue, + { + let value = Value { + env: self.0, + value: value.raw_value(), + value_type: ValueType::Unknown, + }; + let mut de = De(&value); + T::deserialize(&mut de) + } + #[inline] pub fn strict_equals(&self, a: A, b: B) -> Result { let mut result = false; diff --git a/napi/src/error.rs b/napi/src/error.rs index 6c44603d..a515c5e6 100644 --- a/napi/src/error.rs +++ b/napi/src/error.rs @@ -1,7 +1,14 @@ +use std::convert::From; +use std::error::Error as StdError; use std::fmt; +#[cfg(feature = "serde-json")] +use std::fmt::Display; use std::os::raw::c_char; use std::ptr; +#[cfg(feature = "serde-json")] +use serde::{de, ser}; + use crate::{sys, Status}; pub type Result = std::result::Result; @@ -12,6 +19,22 @@ pub struct Error { pub reason: String, } +impl StdError for Error {} + +#[cfg(feature = "serde-json")] +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::new(Status::InvalidArg, msg.to_string()) + } +} + +#[cfg(feature = "serde-json")] +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::new(Status::InvalidArg, msg.to_string()) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}: {}", self.status, self.reason) @@ -82,6 +105,6 @@ pub fn check_status(code: sys::napi_status) -> Result<()> { let status = Status::from(code); match status { Status::Ok => Ok(()), - _ => Err(Error::from_status(status)), + _ => Err(Error::new(status, "".to_owned())), } } diff --git a/napi/src/js_values/arraybuffer.rs b/napi/src/js_values/arraybuffer.rs index a058c824..d156f6ab 100644 --- a/napi/src/js_values/arraybuffer.rs +++ b/napi/src/js_values/arraybuffer.rs @@ -1,8 +1,9 @@ +use std::convert::TryFrom; use std::ptr; -use super::{JsObject, NapiValue, Value, ValueType}; +use super::{JsObject, JsUnknown, NapiValue, Value, ValueType}; use crate::error::check_status; -use crate::{sys, Result}; +use crate::{sys, Error, Result}; #[derive(Debug)] pub struct JsArrayBuffer { @@ -46,3 +47,10 @@ impl NapiValue for JsArrayBuffer { }) } } + +impl TryFrom for JsArrayBuffer { + type Error = Error; + fn try_from(value: JsUnknown) -> Result { + JsArrayBuffer::from_raw(value.0.env, value.0.value) + } +} diff --git a/napi/src/js_values/bigint.rs b/napi/src/js_values/bigint.rs index 1001e709..5f583104 100644 --- a/napi/src/js_values/bigint.rs +++ b/napi/src/js_values/bigint.rs @@ -1,12 +1,146 @@ use std::convert::TryFrom; use std::ptr; -use super::{Error, Value}; +use super::*; use crate::error::check_status; use crate::{sys, Result}; #[derive(Debug)] -pub struct JsBigint(pub(crate) Value); +pub struct JsBigint { + pub(crate) raw: Value, + pub word_count: u64, +} + +impl JsBigint { + pub(crate) fn from_raw_unchecked( + env: sys::napi_env, + value: sys::napi_value, + word_count: u64, + ) -> Self { + Self { + raw: Value { + env, + value, + value_type: ValueType::Object, + }, + word_count, + } + } + + #[inline] + pub fn into_unknown(self) -> Result { + JsUnknown::from_raw(self.raw.env, self.raw.value) + } + + #[inline] + pub fn coerce_to_number(self) -> Result { + let mut new_raw_value = ptr::null_mut(); + let status = + unsafe { sys::napi_coerce_to_number(self.raw.env, self.raw.value, &mut new_raw_value) }; + check_status(status)?; + Ok(JsNumber(Value { + env: self.raw.env, + value: new_raw_value, + value_type: ValueType::Number, + })) + } + + #[inline] + pub fn coerce_to_string(self) -> Result { + let mut new_raw_value = ptr::null_mut(); + let status = + unsafe { sys::napi_coerce_to_string(self.raw.env, self.raw.value, &mut new_raw_value) }; + check_status(status)?; + Ok(JsString(Value { + env: self.raw.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.raw.env, self.raw.value, &mut new_raw_value) }; + check_status(status)?; + Ok(JsObject(Value { + env: self.raw.env, + value: new_raw_value, + value_type: ValueType::Object, + })) + } + + #[inline] + #[cfg(napi5)] + pub fn is_date(&self) -> Result { + let mut is_date = true; + let status = unsafe { sys::napi_is_date(self.raw.env, self.raw.value, &mut is_date) }; + check_status(status)?; + Ok(is_date) + } + + #[inline] + pub fn is_error(&self) -> Result { + let mut result = false; + check_status(unsafe { sys::napi_is_error(self.raw.env, self.raw.value, &mut result) })?; + Ok(result) + } + + #[inline] + pub fn is_typedarray(&self) -> Result { + let mut result = false; + check_status(unsafe { sys::napi_is_typedarray(self.raw.env, self.raw.value, &mut result) })?; + Ok(result) + } + + #[inline] + pub fn is_dataview(&self) -> Result { + let mut result = false; + check_status(unsafe { sys::napi_is_dataview(self.raw.env, self.raw.value, &mut result) })?; + Ok(result) + } + + #[inline] + pub fn instanceof(&self, constructor: Constructor) -> Result { + let mut result = false; + check_status(unsafe { + sys::napi_instanceof( + self.raw.env, + self.raw.value, + constructor.raw_value(), + &mut result, + ) + })?; + Ok(result) + } +} + +impl NapiValue for JsBigint { + fn raw_value(&self) -> sys::napi_value { + self.raw.value + } + + fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result { + let mut word_count: u64 = 0; + check_status(unsafe { + sys::napi_get_value_bigint_words( + env, + value, + ptr::null_mut(), + &mut word_count, + ptr::null_mut(), + ) + })?; + Ok(JsBigint { + raw: Value { + env, + value, + value_type: ValueType::Bigint, + }, + word_count, + }) + } +} /// The BigInt will be converted losslessly when the value is over what an int64 could hold. impl TryFrom for i64 { @@ -28,38 +162,26 @@ impl TryFrom for u64 { impl JsBigint { /// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words - pub fn get_words(&self, sign_bit: bool) -> Result> { - let mut word_count: u64 = 0; + #[inline] + pub fn get_words(&mut self) -> Result<(bool, Vec)> { + let mut words: Vec = Vec::with_capacity(self.word_count as usize); + let word_count = &mut self.word_count; + let mut sign_bit = 0; check_status(unsafe { sys::napi_get_value_bigint_words( - self.0.env, - self.0.value, - ptr::null_mut(), - &mut word_count, - ptr::null_mut(), - ) - })?; - - let mut words: Vec = Vec::with_capacity(word_count as usize); - let mut sign_bit = match sign_bit { - true => 1, - false => 0, - }; - check_status(unsafe { - sys::napi_get_value_bigint_words( - self.0.env, - self.0.value, + self.raw.env, + self.raw.value, &mut sign_bit, - &mut word_count, + word_count, words.as_mut_ptr(), ) })?; unsafe { - words.set_len(word_count as usize); + words.set_len(self.word_count as usize); }; - Ok(words) + Ok((sign_bit == 1, words)) } #[inline] @@ -67,7 +189,7 @@ impl JsBigint { let mut val: u64 = 0; let mut loss = false; check_status(unsafe { - sys::napi_get_value_bigint_uint64(self.0.env, self.0.value, &mut val, &mut loss) + sys::napi_get_value_bigint_uint64(self.raw.env, self.raw.value, &mut val, &mut loss) })?; Ok((val, loss)) @@ -78,8 +200,29 @@ impl JsBigint { let mut val: i64 = 0; let mut loss: bool = false; check_status(unsafe { - sys::napi_get_value_bigint_int64(self.0.env, self.0.value, &mut val, &mut loss) + sys::napi_get_value_bigint_int64(self.raw.env, self.raw.value, &mut val, &mut loss) })?; Ok((val, loss)) } + + #[inline] + pub fn get_i128(&mut self) -> Result<(i128, bool)> { + let (signed, words) = self.get_words()?; + let len = words.len(); + let i128_words: [i64; 2] = [words[0] as _, words[1] as _]; + let mut val = unsafe { ptr::read(i128_words.as_ptr() as *const i128) }; + if signed { + val = -val; + } + Ok((val, len > 2)) + } + + #[inline] + pub fn get_u128(&mut self) -> Result<(bool, u128, bool)> { + let (signed, words) = self.get_words()?; + let len = words.len(); + let u128_words: [u64; 2] = [words[0], words[1]]; + let val = unsafe { ptr::read(u128_words.as_ptr() as *const u128) }; + Ok((signed, val, len > 2)) + } } diff --git a/napi/src/js_values/buffer.rs b/napi/src/js_values/buffer.rs index 8091ae5a..3b3dc9ac 100644 --- a/napi/src/js_values/buffer.rs +++ b/napi/src/js_values/buffer.rs @@ -1,10 +1,11 @@ +use std::convert::TryFrom; use std::ops::Deref; use std::ptr; use std::slice; use super::{JsObject, JsUnknown, NapiValue, Value, ValueType}; use crate::error::check_status; -use crate::{sys, Result}; +use crate::{sys, Error, Result}; #[derive(Debug)] pub struct JsBuffer { @@ -42,8 +43,7 @@ impl NapiValue for JsBuffer { 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)?; + check_status(unsafe { sys::napi_get_buffer_info(env, value, &mut data, &mut len) })?; Ok(JsBuffer { value: JsObject(Value { env, @@ -68,3 +68,10 @@ impl Deref for JsBuffer { self.data } } + +impl TryFrom for JsBuffer { + type Error = Error; + fn try_from(value: JsUnknown) -> Result { + JsBuffer::from_raw(value.0.env, value.0.value) + } +} diff --git a/napi/src/js_values/de.rs b/napi/src/js_values/de.rs new file mode 100644 index 00000000..2026926d --- /dev/null +++ b/napi/src/js_values/de.rs @@ -0,0 +1,358 @@ +use std::convert::TryInto; + +use serde::de::Visitor; +use serde::de::{DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, VariantAccess}; + +use super::{type_of, NapiValue, Value, ValueType}; +#[cfg(napi6)] +use crate::JsBigint; +use crate::{Error, JsBoolean, JsBuffer, JsNumber, JsObject, JsString, JsUnknown, Result, Status}; + +pub(crate) struct De<'env>(pub(crate) &'env Value); + +#[doc(hidden)] +impl<'x, 'de, 'env> serde::de::Deserializer<'x> for &'de mut De<'env> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'x>, + { + let js_value_type = type_of(self.0.env, self.0.value)?; + match js_value_type { + ValueType::Null | ValueType::Undefined => visitor.visit_unit(), + ValueType::Boolean => { + let js_boolean = JsBoolean::from_raw_unchecked(self.0.env, self.0.value); + visitor.visit_bool(js_boolean.get_value()?) + } + ValueType::Number => { + let js_number: f64 = JsNumber::from_raw_unchecked(self.0.env, self.0.value).try_into()?; + if js_number.trunc() == js_number { + visitor.visit_i64(js_number as i64) + } else { + visitor.visit_f64(js_number) + } + } + ValueType::String => { + let js_string = JsString::from_raw_unchecked(self.0.env, self.0.value); + visitor.visit_str(js_string.as_str()?) + } + ValueType::Object => { + let js_object = JsObject::from_raw_unchecked(self.0.env, self.0.value); + if js_object.is_array()? { + let mut deserializer = + JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?); + visitor.visit_seq(&mut deserializer) + } else if js_object.is_buffer()? { + visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data) + } else { + let mut deserializer = JsObjectAccess::new(&js_object)?; + visitor.visit_map(&mut deserializer) + } + } + #[cfg(napi6)] + ValueType::Bigint => { + let mut js_bigint = JsBigint::from_raw(self.0.env, self.0.value)?; + let (signed, v, _loss) = js_bigint.get_u128()?; + if signed { + visitor.visit_i128(-(v as i128)) + } else { + visitor.visit_u128(v) + } + } + ValueType::External | ValueType::Function | ValueType::Symbol => Err(Error::new( + Status::InvalidArg, + format!("typeof {:?} value could not be deserialized", js_value_type), + )), + ValueType::Unknown => unreachable!(), + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'x>, + { + visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'x>, + { + visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data) + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'x>, + { + match type_of(self.0.env, self.0.value)? { + ValueType::Undefined | ValueType::Null => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'x>, + { + let js_value_type = type_of(self.0.env, self.0.value)?; + match js_value_type { + ValueType::String => visitor.visit_enum(JsEnumAccess::new( + JsString::from_raw_unchecked(self.0.env, self.0.value) + .as_str()? + .to_owned(), + None, + )), + ValueType::Object => { + let js_object = JsObject::from_raw_unchecked(self.0.env, self.0.value); + let properties = js_object.get_property_names::()?; + let property_len = properties.get_array_length_unchecked()?; + if property_len != 1 { + Err(Error::new( + Status::InvalidArg, + format!( + "object key length: {}, can not deserialize to Enum", + property_len + ), + )) + } else { + let key = properties.get_index::(0)?; + let value: JsUnknown = js_object.get_property(&key)?; + visitor.visit_enum(JsEnumAccess::new(key.as_str()?.to_owned(), Some(&value.0))) + } + } + _ => Err(Error::new( + Status::InvalidArg, + format!( + "{:?} type could not deserialize to Enum type", + js_value_type + ), + )), + } + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'x>, + { + visitor.visit_unit() + } + + forward_to_deserialize_any! { + > + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + unit unit_struct seq tuple tuple_struct map struct identifier + newtype_struct + } +} + +#[doc(hidden)] +pub(crate) struct JsEnumAccess<'env> { + variant: String, + value: Option<&'env Value>, +} + +#[doc(hidden)] +impl<'env> JsEnumAccess<'env> { + fn new(variant: String, value: Option<&'env Value>) -> Self { + Self { variant, value } + } +} + +#[doc(hidden)] +impl<'de, 'env> EnumAccess<'de> for JsEnumAccess<'env> { + type Error = Error; + type Variant = JsVariantAccess<'env>; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> + where + V: DeserializeSeed<'de>, + { + use serde::de::IntoDeserializer; + let variant = self.variant.into_deserializer(); + let variant_access = JsVariantAccess { value: self.value }; + seed.deserialize(variant).map(|v| (v, variant_access)) + } +} + +#[doc(hidden)] +pub(crate) struct JsVariantAccess<'env> { + value: Option<&'env Value>, +} + +#[doc(hidden)] +impl<'de, 'env> VariantAccess<'de> for JsVariantAccess<'env> { + type Error = Error; + fn unit_variant(self) -> Result<()> { + match self.value { + Some(val) => { + let mut deserializer = De(val); + serde::de::Deserialize::deserialize(&mut deserializer) + } + None => Ok(()), + } + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + match self.value { + Some(val) => { + let mut deserializer = De(val); + seed.deserialize(&mut deserializer) + } + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"newtype variant", + )), + } + } + + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.value { + Some(js_value) => { + let js_object = JsObject::from_raw(js_value.env, js_value.value)?; + if js_object.is_array()? { + let mut deserializer = + JsArrayAccess::new(&js_object, js_object.get_array_length_unchecked()?); + visitor.visit_seq(&mut deserializer) + } else { + Err(serde::de::Error::invalid_type( + Unexpected::Other("JsValue"), + &"tuple variant", + )) + } + } + None => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"tuple variant", + )), + } + } + + fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result + where + V: Visitor<'de>, + { + match self.value { + Some(js_value) => { + if let Ok(val) = JsObject::from_raw(js_value.env, js_value.value) { + let mut deserializer = JsObjectAccess::new(&val)?; + visitor.visit_map(&mut deserializer) + } else { + Err(serde::de::Error::invalid_type( + Unexpected::Other("JsValue"), + &"struct variant", + )) + } + } + _ => Err(serde::de::Error::invalid_type( + Unexpected::UnitVariant, + &"struct variant", + )), + } + } +} + +#[doc(hidden)] +struct JsArrayAccess<'env> { + input: &'env JsObject, + idx: u32, + len: u32, +} + +#[doc(hidden)] +impl<'env> JsArrayAccess<'env> { + fn new(input: &'env JsObject, len: u32) -> Self { + Self { input, idx: 0, len } + } +} + +#[doc(hidden)] +impl<'de, 'env> SeqAccess<'de> for JsArrayAccess<'env> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + if self.idx >= self.len { + return Ok(None); + } + let v = self.input.get_index::(self.idx)?; + self.idx += 1; + + let mut de = De(&v.0); + seed.deserialize(&mut de).map(Some) + } +} + +#[doc(hidden)] +pub(crate) struct JsObjectAccess<'env> { + value: &'env JsObject, + properties: JsObject, + idx: u32, + property_len: u32, +} + +#[doc(hidden)] +impl<'env> JsObjectAccess<'env> { + fn new(value: &'env JsObject) -> Result { + let properties = value.get_property_names::()?; + let property_len = properties.get_array_length_unchecked()?; + Ok(Self { + value, + properties, + idx: 0, + property_len, + }) + } +} + +#[doc(hidden)] +impl<'de, 'env> MapAccess<'de> for JsObjectAccess<'env> { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result> + where + K: DeserializeSeed<'de>, + { + if self.idx >= self.property_len { + return Ok(None); + } + + let prop_name = self.properties.get_index::(self.idx)?; + + let mut de = De(&prop_name.0); + seed.deserialize(&mut de).map(Some) + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de>, + { + if self.idx >= self.property_len { + return Err(Error::new( + Status::InvalidArg, + format!("Index:{} out of range: {}", self.property_len, self.idx), + )); + } + let prop_name = self.properties.get_index::(self.idx)?; + let value: JsUnknown = self.value.get_property(&prop_name)?; + + self.idx += 1; + let mut de = De(&value.0); + let res = seed.deserialize(&mut de)?; + Ok(res) + } +} diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index 7770e68a..f542ead8 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -1,9 +1,14 @@ -use std::convert::From; +use std::convert::{From, TryFrom}; use std::ptr; use crate::error::check_status; use crate::{sys, Error, Result, Status}; +#[cfg(feature = "serde-json")] +mod de; +#[cfg(feature = "serde-json")] +mod ser; + mod arraybuffer; #[cfg(napi6)] mod bigint; @@ -27,10 +32,14 @@ pub use bigint::JsBigint; pub use boolean::JsBoolean; pub use buffer::JsBuffer; pub use class_property::Property; +#[cfg(feature = "serde-json")] +pub(crate) use de::De; pub use either::Either; pub use function::JsFunction; pub use number::JsNumber; pub use object::JsObject; +#[cfg(feature = "serde-json")] +pub(crate) use ser::Ser; pub use string::JsString; pub(crate) use tagged_object::TaggedObject; pub use undefined::JsUndefined; @@ -94,6 +103,13 @@ macro_rules! impl_napi_value_trait { }) } } + + impl TryFrom for $js_value { + type Error = Error; + fn try_from(value: JsUnknown) -> Result<$js_value> { + $js_value::from_raw(value.0.env, value.0.value) + } + } }; } @@ -204,8 +220,6 @@ impl_js_value_methods!(JsString); impl_js_value_methods!(JsObject); impl_js_value_methods!(JsFunction); impl_js_value_methods!(JsExternal); -#[cfg(napi6)] -impl_js_value_methods!(JsBigint); impl_js_value_methods!(JsSymbol); use ValueType::*; @@ -218,8 +232,6 @@ impl_napi_value_trait!(JsString, String); impl_napi_value_trait!(JsObject, Object); impl_napi_value_trait!(JsFunction, Function); impl_napi_value_trait!(JsExternal, External); -#[cfg(napi6)] -impl_napi_value_trait!(JsBigint, Bigint); impl_napi_value_trait!(JsSymbol, Symbol); impl NapiValue for JsUnknown { diff --git a/napi/src/js_values/object.rs b/napi/src/js_values/object.rs index 0a1144d4..4e4cdba3 100644 --- a/napi/src/js_values/object.rs +++ b/napi/src/js_values/object.rs @@ -21,28 +21,32 @@ impl JsObject { 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(()) + check_status(unsafe { + sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw_value()) + }) } pub fn set_named_property(&mut self, name: &str, value: T) -> Result<()> { let key = CString::new(name)?; - let status = unsafe { + check_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 { + check_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(&self, key: &K) -> Result { + let mut raw_value = ptr::null_mut(); + check_status(unsafe { + sys::napi_get_property(self.0.env, self.0.value, key.raw_value(), &mut raw_value) + })?; T::from_raw(self.0.env, raw_value) } @@ -59,22 +63,21 @@ impl JsObject { 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)?; + check_status(unsafe { + sys::napi_get_element(self.0.env, self.0.value, index, &mut raw_value) + })?; 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)?; + check_status(unsafe { sys::napi_is_array(self.0.env, self.0.value, &mut is_array) })?; 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)?; + check_status(unsafe { sys::napi_is_buffer(self.0.env, self.0.value, &mut is_buffer) })?; Ok(is_buffer) } @@ -89,9 +92,13 @@ impl JsObject { "Object is not array".to_owned(), )); } + self.get_array_length_unchecked() + } + + #[inline] + pub fn get_array_length_unchecked(&self) -> Result { let mut length: u32 = 0; - let status = unsafe { sys::napi_get_array_length(self.0.env, self.raw_value(), &mut length) }; - check_status(status)?; + check_status(unsafe { sys::napi_get_array_length(self.0.env, self.raw_value(), &mut length) })?; Ok(length) } } diff --git a/napi/src/js_values/ser.rs b/napi/src/js_values/ser.rs new file mode 100644 index 00000000..aff74e06 --- /dev/null +++ b/napi/src/js_values/ser.rs @@ -0,0 +1,510 @@ +use std::result::Result as StdResult; +#[cfg(napi6)] +use std::slice; + +use serde::{ser, Serialize, Serializer}; + +use super::*; +use crate::{Env, Error, Result}; + +pub(crate) struct Ser<'env>(pub(crate) &'env Env); + +impl<'env> Ser<'env> { + fn new(env: &'env Env) -> Self { + Self(&env) + } +} + +impl<'env> Serializer for Ser<'env> { + type Ok = Value; + type Error = Error; + + type SerializeSeq = SeqSerializer; + type SerializeTuple = SeqSerializer; + type SerializeTupleStruct = SeqSerializer; + type SerializeTupleVariant = SeqSerializer; + type SerializeMap = MapSerializer; + type SerializeStruct = StructSerializer; + type SerializeStructVariant = StructSerializer; + + #[inline] + fn serialize_bool(self, v: bool) -> Result { + self.0.get_boolean(v).map(|js_value| js_value.0) + } + + #[inline] + fn serialize_bytes(self, v: &[u8]) -> Result { + self + .0 + .create_buffer_with_data(v.to_owned()) + .map(|js_value| js_value.value.0) + } + + #[inline] + fn serialize_char(self, v: char) -> Result { + let mut b = [0; 4]; + let result = v.encode_utf8(&mut b); + self.0.create_string(result).map(|js_string| js_string.0) + } + + #[inline] + fn serialize_f32(self, v: f32) -> Result { + self.0.create_double(v as _).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_f64(self, v: f64) -> Result { + self.0.create_double(v).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_i16(self, v: i16) -> Result { + self.0.create_int32(v as _).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_i32(self, v: i32) -> Result { + self.0.create_int32(v).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_i64(self, v: i64) -> Result { + self.0.create_int64(v).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_i8(self, v: i8) -> Result { + self.0.create_int32(v as _).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_u8(self, v: u8) -> Result { + self.0.create_uint32(v as _).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_u16(self, v: u16) -> Result { + self.0.create_uint32(v as _).map(|js_number| js_number.0) + } + + #[inline] + fn serialize_u32(self, v: u32) -> Result { + self.0.create_uint32(v).map(|js_number| js_number.0) + } + + #[cfg(all(any(napi2, napi3, napi4, napi5), not(napi6)))] + #[inline] + fn serialize_u64(self, v: u64) -> Result { + self.0.create_int64(v as _).map(|js_number| js_number.0) + } + + #[cfg(napi6)] + #[inline] + fn serialize_u64(self, v: u64) -> Result { + self + .0 + .create_bigint_from_u64(v) + .map(|js_number| js_number.raw) + } + + #[cfg(all(any(napi2, napi3, napi4, napi5), not(napi6)))] + #[inline] + fn serialize_u128(self, v: u128) -> Result { + self.0.create_string(v.to_string().as_str()).map(|v| v.0) + } + + #[cfg(napi6)] + #[inline] + fn serialize_u128(self, v: u128) -> Result { + let words_ref = &v as *const _; + let words = unsafe { slice::from_raw_parts(words_ref as *const u64, 2) }; + self + .0 + .create_bigint_from_words(false, words.to_vec()) + .map(|v| v.raw) + } + + #[cfg(all(any(napi2, napi3, napi4, napi5), not(napi6)))] + #[inline] + fn serialize_i128(self, v: i128) -> Result { + self.0.create_string(v.to_string().as_str()).map(|v| v.0) + } + + #[cfg(napi6)] + #[inline] + fn serialize_i128(self, v: i128) -> Result { + let words_ref = &(v as u128) as *const _; + let words = unsafe { slice::from_raw_parts(words_ref as *const u64, 2) }; + self + .0 + .create_bigint_from_words(v < 0, words.to_vec()) + .map(|v| v.raw) + } + + #[inline] + fn serialize_unit(self) -> Result { + self.0.get_null().map(|null| null.0) + } + + #[inline] + fn serialize_none(self) -> Result { + self.0.get_null().map(|null| null.0) + } + + #[inline] + fn serialize_str(self, v: &str) -> Result { + self.0.create_string(v).map(|string| string.0) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + #[inline] + fn serialize_map(self, _len: Option) -> Result { + let env = self.0; + let key = env.create_string("")?; + let obj = env.create_object()?; + Ok(MapSerializer { key, obj }) + } + + #[inline] + fn serialize_seq(self, len: Option) -> Result { + let array = self.0.create_array_with_length(len.unwrap_or(0))?; + Ok(SeqSerializer { + current_index: 0, + array, + }) + } + + #[inline] + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let env = self.0; + let array = env.create_array_with_length(len)?; + let mut object = env.create_object()?; + object.set_named_property( + variant, + JsObject(Value { + value: array.0.value, + env: array.0.env, + value_type: ValueType::Object, + }), + )?; + Ok(SeqSerializer { + current_index: 0, + array, + }) + } + + #[inline] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.0.get_null().map(|null| null.0) + } + + #[inline] + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.0.create_string(variant).map(|string| string.0) + } + + #[inline] + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + #[inline] + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + let mut obj = self.0.create_object()?; + obj.set_named_property(variant, JsUnknown(value.serialize(self)?))?; + Ok(obj.0) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + Ok(SeqSerializer { + array: self.0.create_array_with_length(len)?, + current_index: 0, + }) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + Ok(SeqSerializer { + array: self.0.create_array_with_length(len)?, + current_index: 0, + }) + } + + #[inline] + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + Ok(StructSerializer { + obj: self.0.create_object()?, + }) + } + + #[inline] + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + let mut outer = self.0.create_object()?; + let inner = self.0.create_object()?; + outer.set_named_property( + variant, + JsObject(Value { + env: inner.0.env, + value: inner.0.value, + value_type: ValueType::Object, + }), + )?; + Ok(StructSerializer { + obj: self.0.create_object()?, + }) + } +} + +pub struct SeqSerializer { + array: JsObject, + current_index: usize, +} + +impl ser::SerializeSeq for SeqSerializer { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> StdResult<(), Self::Error> + where + T: Serialize, + { + let env = Env::from_raw(self.array.0.env); + self.array.set_index( + self.current_index, + JsUnknown(value.serialize(Ser::new(&env))?), + )?; + self.current_index = self.current_index + 1; + Ok(()) + } + + fn end(self) -> Result { + Ok(self.array.0) + } +} + +#[doc(hidden)] +impl ser::SerializeTuple for SeqSerializer { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_element(&mut self, value: &T) -> StdResult<(), Self::Error> + where + T: Serialize, + { + let env = Env::from_raw(self.array.0.env); + self.array.set_index( + self.current_index, + JsUnknown(value.serialize(Ser::new(&env))?), + )?; + self.current_index = self.current_index + 1; + Ok(()) + } + + #[inline] + fn end(self) -> StdResult { + Ok(self.array.0) + } +} + +#[doc(hidden)] +impl ser::SerializeTupleStruct for SeqSerializer { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field(&mut self, value: &T) -> StdResult<(), Self::Error> + where + T: Serialize, + { + let env = Env::from_raw(self.array.0.env); + self.array.set_index( + self.current_index, + JsUnknown(value.serialize(Ser::new(&env))?), + )?; + self.current_index = self.current_index + 1; + Ok(()) + } + + #[inline] + fn end(self) -> StdResult { + Ok(self.array.0) + } +} + +#[doc(hidden)] +impl ser::SerializeTupleVariant for SeqSerializer { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field(&mut self, value: &T) -> StdResult<(), Self::Error> + where + T: Serialize, + { + let env = Env::from_raw(self.array.0.env); + self.array.set_index( + self.current_index, + JsUnknown(value.serialize(Ser::new(&env))?), + )?; + self.current_index = self.current_index + 1; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.array.0) + } +} + +pub struct MapSerializer { + key: JsString, + obj: JsObject, +} + +#[doc(hidden)] +impl ser::SerializeMap for MapSerializer { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_key(&mut self, key: &T) -> StdResult<(), Self::Error> + where + T: Serialize, + { + let env = Env::from_raw(self.obj.0.env); + self.key = JsString(key.serialize(Ser::new(&env))?); + Ok(()) + } + + #[inline] + fn serialize_value(&mut self, value: &T) -> StdResult<(), Self::Error> + where + T: Serialize, + { + let env = Env::from_raw(self.obj.0.env); + self.obj.set_property( + JsString(Value { + env: self.key.0.env, + value: self.key.0.value, + value_type: ValueType::String, + }), + JsUnknown(value.serialize(Ser::new(&env))?), + )?; + Ok(()) + } + + #[inline] + fn serialize_entry( + &mut self, + key: &K, + value: &V, + ) -> StdResult<(), Self::Error> + where + K: Serialize, + V: Serialize, + { + let env = Env::from_raw(self.obj.0.env); + self.obj.set_property( + JsString(key.serialize(Ser::new(&env))?), + JsUnknown(value.serialize(Ser::new(&env))?), + )?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.obj.0) + } +} + +pub struct StructSerializer { + obj: JsObject, +} + +#[doc(hidden)] +impl ser::SerializeStruct for StructSerializer { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field(&mut self, key: &'static str, value: &T) -> StdResult<(), Error> + where + T: Serialize, + { + let env = Env::from_raw(self.obj.0.env); + self + .obj + .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.obj.0) + } +} + +#[doc(hidden)] +impl ser::SerializeStructVariant for StructSerializer { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field(&mut self, key: &'static str, value: &T) -> StdResult<(), Error> + where + T: Serialize, + { + let env = Env::from_raw(self.obj.0.env); + self + .obj + .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.obj.0) + } +} diff --git a/napi/src/js_values/string.rs b/napi/src/js_values/string.rs index da8f599f..e87e965b 100644 --- a/napi/src/js_values/string.rs +++ b/napi/src/js_values/string.rs @@ -47,6 +47,30 @@ impl JsString { } } + #[inline] + pub fn chars(&self) -> Result<&[char]> { + 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 char, + 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))) diff --git a/napi/src/js_values/value.rs b/napi/src/js_values/value.rs index fb9f8137..0d575fa8 100644 --- a/napi/src/js_values/value.rs +++ b/napi/src/js_values/value.rs @@ -2,7 +2,7 @@ use crate::sys; use super::ValueType; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Value { pub env: sys::napi_env, pub value: sys::napi_value, diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 686e0485..e2e5411c 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -88,6 +88,10 @@ pub use version::NodeVersion; #[cfg(all(feature = "tokio_rt", napi4))] pub use tokio_rt::shutdown as shutdown_tokio_rt; +#[cfg(feature = "serde-json")] +#[macro_use] +extern crate serde; + /// register nodejs module /// /// ## Example diff --git a/napi/src/threadsafe_function.rs b/napi/src/threadsafe_function.rs index 68f2e561..92e0b1eb 100644 --- a/napi/src/threadsafe_function.rs +++ b/napi/src/threadsafe_function.rs @@ -1,3 +1,4 @@ +use std::convert::Into; use std::os::raw::{c_char, c_void}; use std::ptr; @@ -7,6 +8,44 @@ use crate::{sys, Env, JsFunction, JsUnknown, Result}; use sys::napi_threadsafe_function_call_mode; use sys::napi_threadsafe_function_release_mode; +#[repr(u8)] +pub enum ThreadsafeFunctionCallMode { + NonBlocking, + Blocking, +} + +#[repr(u8)] +pub enum ThreadsafeFunctionReleaseMode { + Release, + Abort, +} + +impl Into for ThreadsafeFunctionCallMode { + fn into(self) -> napi_threadsafe_function_call_mode { + match self { + ThreadsafeFunctionCallMode::Blocking => { + napi_threadsafe_function_call_mode::napi_tsfn_blocking + } + ThreadsafeFunctionCallMode::NonBlocking => { + napi_threadsafe_function_call_mode::napi_tsfn_nonblocking + } + } + } +} + +impl Into for ThreadsafeFunctionReleaseMode { + fn into(self) -> napi_threadsafe_function_release_mode { + match self { + ThreadsafeFunctionReleaseMode::Release => { + napi_threadsafe_function_release_mode::napi_tsfn_release + } + ThreadsafeFunctionReleaseMode::Abort => { + napi_threadsafe_function_release_mode::napi_tsfn_abort + } + } + } +} + pub trait ToJs: Copy + Clone { type Output; @@ -25,17 +64,9 @@ pub trait ToJs: Copy + Clone { /// use std::thread; /// use napi::{ /// Number, Result, Env, CallContext, JsUndefined, JsFunction, -/// sys::{ -/// napi_threadsafe_function_call_mode::{ -/// napi_tsfn_blocking, -/// }, -/// napi_threadsafe_function_release_mode::{ -/// napi_tsfn_release, -/// } -/// } /// }; /// use napi::threadsafe_function::{ -/// ToJs, ThreadsafeFunction, +/// ToJs, ThreadsafeFunction, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, /// }; /// /// // Define a struct for handling the data passed from `ThreadsafeFunction::call` @@ -67,12 +98,12 @@ pub trait ToJs: Copy + Clone { /// 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.call(Ok(output), ThreadsafeFunctionCallMode::Blocking).unwrap(); +/// tsfn.call(Ok(output), ThreadsafeFunctionCallMode::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 possibly cause memory leaks. -/// tsfn.release(napi_tsfn_release).unwrap(); +/// tsfn.release(ThreadsafeFunctionReleaseMode::Release).unwrap(); /// }); /// /// ctx.env.get_undefined() @@ -137,16 +168,12 @@ impl ThreadsafeFunction { /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// for more information. - pub fn call( - &self, - value: Result, - mode: napi_threadsafe_function_call_mode, - ) -> Result<()> { + pub fn call(&self, value: Result, mode: ThreadsafeFunctionCallMode) -> Result<()> { check_status(unsafe { sys::napi_call_threadsafe_function( self.raw_value, Box::into_raw(Box::from(value)) as *mut _ as *mut c_void, - mode, + mode.into(), ) }) } @@ -159,8 +186,8 @@ impl ThreadsafeFunction { /// See [napi_release_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_release_threadsafe_function) /// for more information. - pub fn release(&self, mode: napi_threadsafe_function_release_mode) -> Result<()> { - check_status(unsafe { sys::napi_release_threadsafe_function(self.raw_value, mode) }) + pub fn release(&self, mode: ThreadsafeFunctionReleaseMode) -> Result<()> { + check_status(unsafe { sys::napi_release_threadsafe_function(self.raw_value, mode.into()) }) } /// See [napi_ref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_ref_threadsafe_function) diff --git a/test_module/Cargo.toml b/test_module/Cargo.toml index c0e3f30d..8e02d109 100644 --- a/test_module/Cargo.toml +++ b/test_module/Cargo.toml @@ -9,8 +9,12 @@ crate-type = ["cdylib"] [dependencies] futures = "0.3" -napi = { path = "../napi", features = ["libuv", "tokio_rt"] } +napi = { path = "../napi", features = ["libuv", "tokio_rt", "serde-json"] } napi-derive = { path = "../napi-derive" } +serde = "1" +serde_bytes = "0.11" +serde_derive = "1" +serde_json = "1" tokio = { version = "0.2", features = ["default", "fs"]} [build-dependencies] diff --git a/test_module/__test__/napi-version.ts b/test_module/__test__/napi-version.ts index 2d277819..6cab1562 100644 --- a/test_module/__test__/napi-version.ts +++ b/test_module/__test__/napi-version.ts @@ -1,2 +1 @@ -// @ts-expect-error -export const napiVersion = parseInt(process.versions.napi || '1', 10) +export const napiVersion = parseInt(process.versions.napi ?? '1', 10) diff --git a/test_module/__test__/serde/de.spec.ts b/test_module/__test__/serde/de.spec.ts new file mode 100644 index 00000000..3281c006 --- /dev/null +++ b/test_module/__test__/serde/de.spec.ts @@ -0,0 +1,39 @@ +import test from 'ava' + +import { napiVersion } from '../napi-version' + +const bindings = require('../../index.node') + +test('deserialize string', (t) => { + t.notThrows(() => bindings.expect_hello_world('hello world')) +}) + +test('deserialize object', (t) => { + if (napiVersion < 6) { + t.throws(() => { + bindings.expect_obj({}) + }) + } else { + t.notThrows(() => + bindings.expect_obj({ + a: 1, + b: [1, 2], + c: 'abc', + d: false, + e: null, + f: null, + g: [9, false, 'efg'], + h: '🤷', + i: 'Empty', + j: { Tuple: [27, 'hij'] }, + k: { Struct: { a: 128, b: [9, 8, 7] } }, + l: 'jkl', + m: [0, 1, 2, 3, 4], + o: { Value: ['z', 'y', 'x'] }, + p: [1, 2, 3.5], + q: BigInt('9998881288248882845242411222333'), + r: BigInt('-3332323888900001232323022221345'), + }), + ) + } +}) diff --git a/test_module/__test__/serde/ser.spec.ts b/test_module/__test__/serde/ser.spec.ts new file mode 100644 index 00000000..24d46fa0 --- /dev/null +++ b/test_module/__test__/serde/ser.spec.ts @@ -0,0 +1,26 @@ +import test from 'ava' + +import { napiVersion } from '../napi-version' + +const bindings = require('../../index.node') + +const testFunc = [ + 'make_num_77', + 'make_num_32', + 'make_str_hello', + 'make_num_array', + 'make_buff', + 'make_obj', + 'make_map', +] + +if (napiVersion >= 6) { + // bigint inside + testFunc.push('make_object') +} + +for (const func of testFunc) { + test(`serialize ${func} from bindings`, (t) => { + t.snapshot(bindings[func]()) + }) +} diff --git a/test_module/__test__/serde/ser.spec.ts.md b/test_module/__test__/serde/ser.spec.ts.md new file mode 100644 index 00000000..00620da3 --- /dev/null +++ b/test_module/__test__/serde/ser.spec.ts.md @@ -0,0 +1,130 @@ +# Snapshot report for `test_module/__test__/serde/ser.spec.ts` + +The actual snapshot is saved in `ser.spec.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## serialize make_buff from bindings + +> Snapshot 1 + + Buffer @Uint8Array [ + fffefd + ] + +## serialize make_map from bindings + +> Snapshot 1 + + { + a: 1, + b: 2, + c: 3, + } + +## serialize make_num_32 from bindings + +> Snapshot 1 + + 32 + +## serialize make_num_77 from bindings + +> Snapshot 1 + + 77 + +## serialize make_num_array from bindings + +> Snapshot 1 + + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ] + +## serialize make_obj from bindings + +> Snapshot 1 + + { + a: 1, + b: [ + 0.1, + 1.1, + 2.2, + 3.3, + ], + c: 'Hi', + } + +## serialize make_object from bindings + +> Snapshot 1 + + { + a: 1, + b: [ + 1, + 2, + ], + c: 'abc', + d: false, + e: null, + f: null, + g: [ + 9, + false, + 'efg', + ], + h: '🤷', + i: 'Empty', + j: [ + 27, + 'hij', + ], + k: { + a: 128, + b: [ + 9, + 8, + 7, + ], + }, + l: 'jkl', + m: [ + 0, + 1, + 2, + 3, + 4, + ], + o: { + Value: [ + 'z', + 'y', + 'x', + ], + }, + p: [ + 1, + 2, + 3.5, + ], + q: 9998881288248882845242411222333n, + r: -340282363588614574563373375108745990111n, + } + +## serialize make_str_hello from bindings + +> Snapshot 1 + + 'Hello World' diff --git a/test_module/__test__/serde/ser.spec.ts.snap b/test_module/__test__/serde/ser.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..da74db6c6b3d928e2c0673199dce52b3ecc75aeb GIT binary patch literal 1038 zcmV+p1o8VpRzVr00000000B6 zm0O4#RT#(5nKP4Q(~U`1vGvyNWAPGqW|DLQVr{W?(UsN3tq7tfo5?1-X|{%JyQ_%B z3$+hLsTEd>wJwVymKKDzXsbTtam5R+LNEJJ5Tylat7!GXuKzhZXEJj(wt|NI$eI87 z&-dk=?_9nogha>qN7?S?`s+_tC~-b9@LRm^X!qQ%Qb|(7V+2TCqstMRDU+xAs%6>IhoecIZg*&bj*Z zz4^W;-+uRg%GEc)V*`_y;7+M?h6VAM`)0@*Xsjq|18kYISVYqhs}I z(d&rNWe?$Z6R^Qnumeni55Om27Mun@gKKFhNwszLdVZ2p(Td08ghu(d6Ap!3N12n~ zeWY~pnNlGruAm5kxdIb{>I$k5G*{4s5ORf(5W=ny7DB`oB0}hJg${4@D=Ew3_a3lO zj4rf(yi~I5&hVmxm0H7E9Ag4w>?U25EnNPK&=_$>6L-S$pc>4D$%fr$*M=KqF&f`2 zt4^M~z#u4lx%vwu_E5vg?%IqK8AMLTJCE|;?`OE)abNTXp`>5P#!(zO0JIk6{^R*34K|R!?Wvq%&F5T!U{$@3HWd%k}8G?kpnqJpRqg;0<=AO@&4O9t;P- zaqxv?W|Enmf#D}`NiwRku@eVv7r0%O)}a<$hY=cD3(E%Z2(ZCcutN>T4@>ctDH!(2 zu_JP9^)M{Qz$`c|#f-|wixNCuS~ml2ZQm&It^;A~2-=)w?>~H;g=Ck8+h({a_^M+q zS4Q~mTyj*nyHnXt_-ebT2DfLFc(>61d($mmD#*A}Yh~0n<*Va%+v#XGYw}@irZT)d z4 Result<()> { + serde::register_serde_func(module)?; module.create_named_method("testThrow", test_throw)?; module.create_named_method("testThrowWithReason", test_throw_with_reason)?; module.create_named_method("testSpawnThread", test_spawn_thread)?; diff --git a/test_module/src/napi4/tsfn.rs b/test_module/src/napi4/tsfn.rs index 459cb22c..777795fa 100644 --- a/test_module/src/napi4/tsfn.rs +++ b/test_module/src/napi4/tsfn.rs @@ -1,15 +1,10 @@ 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, JsFunction, JsString, JsUndefined, Result, Status, - JsUnknown, +use napi::threadsafe_function::{ + ThreadsafeFunction, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, ToJs, }; +use napi::{CallContext, Env, Error, JsFunction, JsString, JsUndefined, JsUnknown, Result, Status}; use tokio; #[derive(Clone, Copy)] @@ -38,9 +33,15 @@ pub fn test_threadsafe_function(ctx: CallContext) -> Result { thread::spawn(move || { let output: Vec = vec![42, 1, 2, 3]; // It's okay to call a threadsafe function multiple times. - tsfn.call(Ok(output.clone()), napi_tsfn_blocking).unwrap(); - tsfn.call(Ok(output.clone()), napi_tsfn_blocking).unwrap(); - tsfn.release(napi_tsfn_release).unwrap(); + tsfn + .call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking) + .unwrap(); + tsfn + .call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking) + .unwrap(); + tsfn + .release(ThreadsafeFunctionReleaseMode::Release) + .unwrap(); }); ctx.env.get_undefined() @@ -56,10 +57,12 @@ pub fn test_tsfn_error(ctx: CallContext) -> Result { tsfn .call( Err(Error::new(Status::Unknown, "invalid".to_owned())), - napi_tsfn_blocking, + ThreadsafeFunctionCallMode::Blocking, ) .unwrap(); - tsfn.release(napi_tsfn_release).unwrap(); + tsfn + .release(ThreadsafeFunctionReleaseMode::Release) + .unwrap(); }); ctx.env.get_undefined() @@ -72,7 +75,9 @@ impl ToJs for HandleBuffer { type Output = Vec; fn resolve(&self, env: &mut Env, output: Self::Output) -> Result> { - let value = env.create_buffer_with_data(output.to_vec())?.into_unknown()?; + let value = env + .create_buffer_with_data(output.to_vec())? + .into_unknown()?; Ok(vec![value]) } } @@ -96,8 +101,10 @@ pub fn test_tokio_readfile(ctx: CallContext) -> Result { 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(); + let _ = tsfn.call(ret, ThreadsafeFunctionCallMode::Blocking); + tsfn + .release(ThreadsafeFunctionReleaseMode::Release) + .unwrap(); }); ctx.env.get_undefined() diff --git a/test_module/src/napi6/bigint.rs b/test_module/src/napi6/bigint.rs index 1d8781bd..faac2761 100644 --- a/test_module/src/napi6/bigint.rs +++ b/test_module/src/napi6/bigint.rs @@ -1,5 +1,5 @@ +use napi::{CallContext, JsBigint, JsNumber, JsObject, Result}; use std::convert::TryFrom; -use napi::{CallContext, JsBigint, Result, JsNumber, JsObject}; #[js_function(0)] pub fn test_create_bigint_from_i64(ctx: CallContext) -> Result { @@ -13,7 +13,9 @@ pub fn test_create_bigint_from_u64(ctx: CallContext) -> Result { #[js_function(0)] pub fn test_create_bigint_from_words(ctx: CallContext) -> Result { - ctx.env.create_bigint_from_words(true, vec![u64::max_value(), u64::max_value()]) + ctx + .env + .create_bigint_from_words(true, vec![u64::max_value(), u64::max_value()]) } #[js_function(1)] @@ -32,16 +34,18 @@ pub fn test_get_bigint_u64(ctx: CallContext) -> Result { #[js_function(0)] pub fn test_get_bigint_words(ctx: CallContext) -> Result { - let js_bigint = ctx.env.create_bigint_from_words(true, vec![i64::max_value() as u64, i64::max_value() as u64])?; + let mut js_bigint = ctx + .env + .create_bigint_from_words(true, vec![i64::max_value() as u64, i64::max_value() as u64])?; let mut js_arr = ctx.env.create_array_with_length(2)?; - let words = js_bigint.get_words(true)?; + let (_signed, words) = js_bigint.get_words()?; js_arr.set_number_indexed_property( ctx.env.create_int64(0)?, - ctx.env.create_bigint_from_u64(words[0])? + ctx.env.create_bigint_from_u64(words[0])?, )?; js_arr.set_number_indexed_property( ctx.env.create_int64(1)?, - ctx.env.create_bigint_from_u64(words[1])? + ctx.env.create_bigint_from_u64(words[1])?, )?; Ok(js_arr) } diff --git a/test_module/src/serde.rs b/test_module/src/serde.rs new file mode 100644 index 00000000..ebc2ce14 --- /dev/null +++ b/test_module/src/serde.rs @@ -0,0 +1,182 @@ +use napi::{CallContext, JsObject, JsUndefined, JsUnknown, Module, Result}; + +#[derive(Serialize, Debug, Deserialize)] +struct AnObject { + a: u32, + b: Vec, + c: String, +} + +#[derive(Serialize, Debug, Deserialize, Eq, PartialEq)] +struct Inner; + +#[derive(Serialize, Debug, Deserialize, Eq, PartialEq)] +struct Inner2(i32, bool, String); + +#[derive(Serialize, Debug, Deserialize, Eq, PartialEq)] +enum TypeEnum { + Empty, + Tuple(u32, String), + Struct { a: u8, b: Vec }, + Value(Vec), +} + +#[derive(Serialize, Debug, Deserialize, PartialEq)] +struct AnObjectTwo { + a: u32, + b: Vec, + c: String, + d: Option, + e: Option, + f: Inner, + g: Inner2, + h: char, + i: TypeEnum, + j: TypeEnum, + k: TypeEnum, + l: String, + m: Vec, + o: TypeEnum, + p: Vec, + q: u128, + r: i128, +} + +macro_rules! make_test { + ($name:ident, $val:expr) => { + #[js_function] + fn $name(ctx: CallContext) -> Result { + let value = $val; + ctx.env.to_js_value(&value) + } + }; +} + +make_test!(make_num_77, 77i32); +make_test!(make_num_32, 32u8); +make_test!(make_str_hello, "Hello World"); +make_test!(make_num_array, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +make_test!( + make_obj, + AnObject { + a: 1, + b: vec![0.1f64, 1.1, 2.2, 3.3], + c: "Hi".into(), + } +); +make_test!(make_map, { + use std::collections::HashMap; + let mut map = HashMap::new(); + map.insert("a", 1); + map.insert("b", 2); + map.insert("c", 3); + map +}); + +make_test!(make_object, { + let value = AnObjectTwo { + a: 1, + b: vec![1, 2], + c: "abc".into(), + d: Some(false), + e: None, + f: Inner, + g: Inner2(9, false, "efg".into()), + h: '🤷', + i: TypeEnum::Empty, + j: TypeEnum::Tuple(27, "hij".into()), + k: TypeEnum::Struct { + a: 128, + b: vec![9, 8, 7], + }, + l: "jkl".into(), + m: vec![0, 1, 2, 3, 4], + o: TypeEnum::Value(vec!['z', 'y', 'x']), + p: vec![1., 2., 3.5], + q: 9998881288248882845242411222333, + r: -3332323888900001232323022221345, + }; + value +}); + +const NUMBER_BYTES: &'static [u8] = &[255u8, 254, 253]; + +make_test!(make_buff, { serde_bytes::Bytes::new(NUMBER_BYTES) }); + +macro_rules! make_expect { + ($name:ident, $val:expr, $val_type:ty) => { + #[js_function(1)] + fn $name(ctx: CallContext) -> Result { + let value = $val; + let arg0 = ctx.get::(0)?; + + let de_serialized: $val_type = ctx.env.from_js_value(arg0)?; + assert_eq!(value, de_serialized); + ctx.env.get_undefined() + } + }; +} + +make_expect!(expect_hello_world, "hello world", String); + +make_expect!( + expect_obj, + AnObjectTwo { + a: 1, + b: vec![1, 2], + c: "abc".into(), + d: Some(false), + e: None, + f: Inner, + g: Inner2(9, false, "efg".into()), + h: '🤷', + i: TypeEnum::Empty, + j: TypeEnum::Tuple(27, "hij".into()), + k: TypeEnum::Struct { + a: 128, + b: vec![9, 8, 7], + }, + l: "jkl".into(), + m: vec![0, 1, 2, 3, 4], + o: TypeEnum::Value(vec!['z', 'y', 'x']), + p: vec![1., 2., 3.5], + q: 9998881288248882845242411222333, + r: -3332323888900001232323022221345, + }, + AnObjectTwo +); + +make_expect!(expect_num_array, vec![0, 1, 2, 3], Vec); + +make_expect!( + expect_buffer, + serde_bytes::ByteBuf::from(vec![252u8, 251, 250]), + serde_bytes::ByteBuf +); + +#[js_function(1)] +fn roundtrip_object(ctx: CallContext) -> Result { + let arg0 = ctx.get::(0)?; + + let de_serialized: AnObjectTwo = ctx.env.from_js_value(arg0)?; + ctx.env.to_js_value(&de_serialized) +} + +pub fn register_serde_func(m: &mut Module) -> Result<()> { + m.create_named_method("make_num_77", make_num_77)?; + m.create_named_method("make_num_32", make_num_32)?; + m.create_named_method("make_str_hello", make_str_hello)?; + m.create_named_method("make_num_array", make_num_array)?; + m.create_named_method("make_buff", make_buff)?; + m.create_named_method("make_obj", make_obj)?; + m.create_named_method("make_object", make_object)?; + m.create_named_method("make_map", make_map)?; + + m.create_named_method("expect_hello_world", expect_hello_world)?; + m.create_named_method("expect_obj", expect_obj)?; + m.create_named_method("expect_num_array", expect_num_array)?; + m.create_named_method("expect_buffer", expect_buffer)?; + + m.create_named_method("roundtrip_object", roundtrip_object)?; + Ok(()) +}