feat(napi): serde-json feature
This commit is contained in:
parent
c58d00b127
commit
ea3fff25ae
24 changed files with 1672 additions and 117 deletions
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
358
napi/src/js_values/de.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
510
napi/src/js_values/ser.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
// @ts-expect-error
|
||||
export const napiVersion = parseInt(process.versions.napi || '1', 10)
|
||||
export const napiVersion = parseInt(process.versions.napi ?? '1', 10)
|
||||
|
|
39
test_module/__test__/serde/de.spec.ts
Normal file
39
test_module/__test__/serde/de.spec.ts
Normal 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'),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
26
test_module/__test__/serde/ser.spec.ts
Normal file
26
test_module/__test__/serde/ser.spec.ts
Normal 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]())
|
||||
})
|
||||
}
|
130
test_module/__test__/serde/ser.spec.ts.md
Normal file
130
test_module/__test__/serde/ser.spec.ts.md
Normal 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'
|
BIN
test_module/__test__/serde/ser.spec.ts.snap
Normal file
BIN
test_module/__test__/serde/ser.spec.ts.snap
Normal file
Binary file not shown.
|
@ -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)?;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
182
test_module/src/serde.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in a new issue