feat(napi): serde-json feature

This commit is contained in:
LongYinan 2020-08-26 00:07:27 +08:00
parent c58d00b127
commit ea3fff25ae
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
24 changed files with 1672 additions and 117 deletions

View file

@ -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" }

View file

@ -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<JsBigint> {
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<JsBigint> {
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<u64>) -> Result<JsBigint> {
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<T>(&self, node: &T) -> Result<JsUnknown>
where
T: Serialize,
{
let s = Ser(self);
node.serialize(s).map(JsUnknown)
}
#[cfg(feature = "serde-json")]
#[inline]
pub fn from_js_value<T, V>(&self, value: V) -> Result<T>
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<A: NapiValue, B: NapiValue>(&self, a: A, b: B) -> Result<bool> {
let mut result = false;

View file

@ -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<T> = std::result::Result<T, Error>;
@ -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<T: Display>(msg: T) -> Self {
Error::new(Status::InvalidArg, msg.to_string())
}
}
#[cfg(feature = "serde-json")]
impl de::Error for Error {
fn custom<T: Display>(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())),
}
}

View file

@ -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<JsUnknown> for JsArrayBuffer {
type Error = Error;
fn try_from(value: JsUnknown) -> Result<JsArrayBuffer> {
JsArrayBuffer::from_raw(value.0.env, value.0.value)
}
}

View file

@ -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> {
JsUnknown::from_raw(self.raw.env, self.raw.value)
}
#[inline]
pub fn coerce_to_number(self) -> Result<JsNumber> {
let mut new_raw_value = ptr::null_mut();
let status =
unsafe { sys::napi_coerce_to_number(self.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<JsString> {
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<JsObject> {
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<bool> {
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<bool> {
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<bool> {
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<bool> {
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<Constructor: NapiValue>(&self, constructor: Constructor) -> Result<bool> {
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<Self> {
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<JsBigint> for i64 {
@ -28,38 +162,26 @@ impl TryFrom<JsBigint> 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<Vec<u64>> {
let mut word_count: u64 = 0;
#[inline]
pub fn get_words(&mut self) -> Result<(bool, Vec<u64>)> {
let mut words: Vec<u64> = 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<u64> = 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))
}
}

View file

@ -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<Self> {
let mut data = ptr::null_mut();
let mut len: u64 = 0;
let status = unsafe { sys::napi_get_buffer_info(env, value, &mut data, &mut len) };
check_status(status)?;
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<JsUnknown> for JsBuffer {
type Error = Error;
fn try_from(value: JsUnknown) -> Result<JsBuffer> {
JsBuffer::from_raw(value.0.env, value.0.value)
}
}

358
napi/src/js_values/de.rs Normal file
View file

@ -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<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data)
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
visitor.visit_bytes(JsBuffer::from_raw(self.0.env, self.0.value)?.data)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
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<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
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::<JsObject>()?;
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::<JsString>(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<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'x>,
{
visitor.visit_unit()
}
forward_to_deserialize_any! {
<V: Visitor<'x>>
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<V>(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<T>(self, seed: T) -> Result<T::Value>
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<V>(self, _len: usize, visitor: V) -> Result<V::Value>
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<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
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<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: DeserializeSeed<'de>,
{
if self.idx >= self.len {
return Ok(None);
}
let v = self.input.get_index::<JsUnknown>(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<Self> {
let properties = value.get_property_names::<JsObject>()?;
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<K>(&mut self, seed: K) -> Result<Option<K::Value>>
where
K: DeserializeSeed<'de>,
{
if self.idx >= self.property_len {
return Ok(None);
}
let prop_name = self.properties.get_index::<JsUnknown>(self.idx)?;
let mut de = De(&prop_name.0);
seed.deserialize(&mut de).map(Some)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
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::<JsString>(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)
}
}

View file

@ -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<JsUnknown> 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 {

View file

@ -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<T: NapiValue>(&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<T: NapiValue>(&self, name: &str) -> Result<T> {
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<K: NapiValue, T: NapiValue>(&self, key: &K) -> Result<T> {
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<T: NapiValue>(&self, index: u32) -> Result<T> {
let mut raw_value = ptr::null_mut();
let status = unsafe { sys::napi_get_element(self.0.env, self.0.value, index, &mut raw_value) };
check_status(status)?;
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<bool> {
let mut is_array = false;
let status = unsafe { sys::napi_is_array(self.0.env, self.0.value, &mut is_array) };
check_status(status)?;
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<bool> {
let mut is_buffer = false;
let status = unsafe { sys::napi_is_buffer(self.0.env, self.0.value, &mut is_buffer) };
check_status(status)?;
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<u32> {
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)
}
}

510
napi/src/js_values/ser.rs Normal file
View file

@ -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::Ok> {
self.0.get_boolean(v).map(|js_value| js_value.0)
}
#[inline]
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
self
.0
.create_buffer_with_data(v.to_owned())
.map(|js_value| js_value.value.0)
}
#[inline]
fn serialize_char(self, v: char) -> Result<Self::Ok> {
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::Ok> {
self.0.create_double(v as _).map(|js_number| js_number.0)
}
#[inline]
fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
self.0.create_double(v).map(|js_number| js_number.0)
}
#[inline]
fn serialize_i16(self, v: i16) -> Result<Self::Ok> {
self.0.create_int32(v as _).map(|js_number| js_number.0)
}
#[inline]
fn serialize_i32(self, v: i32) -> Result<Self::Ok> {
self.0.create_int32(v).map(|js_number| js_number.0)
}
#[inline]
fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
self.0.create_int64(v).map(|js_number| js_number.0)
}
#[inline]
fn serialize_i8(self, v: i8) -> Result<Self::Ok> {
self.0.create_int32(v as _).map(|js_number| js_number.0)
}
#[inline]
fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
self.0.create_uint32(v as _).map(|js_number| js_number.0)
}
#[inline]
fn serialize_u16(self, v: u16) -> Result<Self::Ok> {
self.0.create_uint32(v as _).map(|js_number| js_number.0)
}
#[inline]
fn serialize_u32(self, v: u32) -> Result<Self::Ok> {
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::Ok> {
self.0.create_int64(v as _).map(|js_number| js_number.0)
}
#[cfg(napi6)]
#[inline]
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
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::Ok> {
self.0.create_string(v.to_string().as_str()).map(|v| v.0)
}
#[cfg(napi6)]
#[inline]
fn serialize_u128(self, v: u128) -> Result<Self::Ok> {
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::Ok> {
self.0.create_string(v.to_string().as_str()).map(|v| v.0)
}
#[cfg(napi6)]
#[inline]
fn serialize_i128(self, v: i128) -> Result<Self::Ok> {
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::Ok> {
self.0.get_null().map(|null| null.0)
}
#[inline]
fn serialize_none(self) -> Result<Self::Ok> {
self.0.get_null().map(|null| null.0)
}
#[inline]
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
self.0.create_string(v).map(|string| string.0)
}
#[inline]
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok>
where
T: Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
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<usize>) -> Result<Self::SerializeSeq> {
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<Self::SerializeTupleVariant> {
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::Ok> {
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::Ok> {
self.0.create_string(variant).map(|string| string.0)
}
#[inline]
fn serialize_newtype_struct<T: ?Sized>(self, _name: &'static str, value: &T) -> Result<Self::Ok>
where
T: Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_newtype_variant<T: ?Sized>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok>
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<Self::SerializeTuple> {
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<Self::SerializeTupleStruct> {
Ok(SeqSerializer {
array: self.0.create_array_with_length(len)?,
current_index: 0,
})
}
#[inline]
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
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<Self::SerializeStructVariant> {
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<T: ?Sized>(&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<Self::Ok> {
Ok(self.array.0)
}
}
#[doc(hidden)]
impl ser::SerializeTuple for SeqSerializer {
type Ok = Value;
type Error = Error;
#[inline]
fn serialize_element<T: ?Sized>(&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<Self::Ok, Self::Error> {
Ok(self.array.0)
}
}
#[doc(hidden)]
impl ser::SerializeTupleStruct for SeqSerializer {
type Ok = Value;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(&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<Self::Ok, Self::Error> {
Ok(self.array.0)
}
}
#[doc(hidden)]
impl ser::SerializeTupleVariant for SeqSerializer {
type Ok = Value;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(&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<Self::Ok> {
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<T: ?Sized>(&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<T: ?Sized>(&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<K: ?Sized, V: ?Sized>(
&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<Self::Ok> {
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<T: ?Sized>(&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<Self::Ok> {
Ok(self.obj.0)
}
}
#[doc(hidden)]
impl ser::SerializeStructVariant for StructSerializer {
type Ok = Value;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(&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<Self::Ok> {
Ok(self.obj.0)
}
}

View file

@ -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)))

View file

@ -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,

View file

@ -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

View file

@ -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<napi_threadsafe_function_call_mode> 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<napi_threadsafe_function_release_mode> 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<T: ToJs> ThreadsafeFunction<T> {
/// 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<T::Output>,
mode: napi_threadsafe_function_call_mode,
) -> Result<()> {
pub fn call(&self, value: Result<T::Output>, 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<T: ToJs> ThreadsafeFunction<T> {
/// 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)

View file

@ -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]

View file

@ -1,2 +1 @@
// @ts-expect-error
export const napiVersion = parseInt(process.versions.napi || '1', 10)
export const napiVersion = parseInt(process.versions.napi ?? '1', 10)

View file

@ -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'),
}),
)
}
})

View file

@ -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]())
})
}

View file

@ -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'

Binary file not shown.

View file

@ -2,6 +2,8 @@
extern crate napi;
#[macro_use]
extern crate napi_derive;
#[macro_use]
extern crate serde_derive;
use napi::{CallContext, Error, JsBoolean, JsString, JsUnknown, Module, Result, Status};
@ -11,10 +13,10 @@ mod libuv;
mod napi4;
#[cfg(napi5)]
mod napi5;
#[cfg(napi4)]
mod tokio_rt;
#[cfg(napi6)]
mod napi6;
#[cfg(napi4)]
mod tokio_rt;
mod buffer;
mod class;
@ -22,6 +24,7 @@ mod either;
mod external;
mod function;
mod napi_version;
mod serde;
mod string;
mod symbol;
mod task;
@ -36,24 +39,21 @@ use libuv::read_file::uv_read_file;
use napi4::{test_threadsafe_function, test_tokio_readfile, test_tsfn_error};
#[cfg(napi5)]
use napi5::is_date::test_object_is_date;
#[cfg(napi6)]
use napi6::bigint::{
test_create_bigint_from_i64, test_create_bigint_from_u64, test_create_bigint_from_words,
test_get_bigint_i64, test_get_bigint_u64, test_get_bigint_words,
};
use napi_version::get_napi_version;
use symbol::{create_named_symbol, create_symbol_from_js_string, create_unnamed_symbol};
use task::test_spawn_thread;
#[cfg(napi4)]
use tokio_rt::{error_from_tokio_future, test_execute_tokio_readfile};
#[cfg(napi6)]
use napi6::bigint::{
test_create_bigint_from_i64,
test_create_bigint_from_u64,
test_create_bigint_from_words,
test_get_bigint_i64,
test_get_bigint_u64,
test_get_bigint_words,
};
register_module!(test_module, init);
fn init(module: &mut Module) -> 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)?;

View file

@ -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<JsUndefined> {
thread::spawn(move || {
let output: Vec<u8> = 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<JsUndefined> {
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<u8>;
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Vec<JsUnknown>> {
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<JsUndefined> {
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()

View file

@ -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<JsBigint> {
@ -13,7 +13,9 @@ pub fn test_create_bigint_from_u64(ctx: CallContext) -> Result<JsBigint> {
#[js_function(0)]
pub fn test_create_bigint_from_words(ctx: CallContext) -> Result<JsBigint> {
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<JsNumber> {
#[js_function(0)]
pub fn test_get_bigint_words(ctx: CallContext) -> Result<JsObject> {
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)
}

182
test_module/src/serde.rs Normal file
View file

@ -0,0 +1,182 @@
use napi::{CallContext, JsObject, JsUndefined, JsUnknown, Module, Result};
#[derive(Serialize, Debug, Deserialize)]
struct AnObject {
a: u32,
b: Vec<f64>,
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<u8> },
Value(Vec<char>),
}
#[derive(Serialize, Debug, Deserialize, PartialEq)]
struct AnObjectTwo {
a: u32,
b: Vec<i64>,
c: String,
d: Option<bool>,
e: Option<bool>,
f: Inner,
g: Inner2,
h: char,
i: TypeEnum,
j: TypeEnum,
k: TypeEnum,
l: String,
m: Vec<u8>,
o: TypeEnum,
p: Vec<f64>,
q: u128,
r: i128,
}
macro_rules! make_test {
($name:ident, $val:expr) => {
#[js_function]
fn $name(ctx: CallContext) -> Result<JsUnknown> {
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<JsUndefined> {
let value = $val;
let arg0 = ctx.get::<JsUnknown>(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<i32>);
make_expect!(
expect_buffer,
serde_bytes::ByteBuf::from(vec![252u8, 251, 250]),
serde_bytes::ByteBuf
);
#[js_function(1)]
fn roundtrip_object(ctx: CallContext) -> Result<JsUnknown> {
let arg0 = ctx.get::<JsObject>(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(())
}