diff --git a/.cargo/config.toml b/.cargo/config.toml index 61b55184a..650f04425 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,7 @@ [alias] # Neon defines mutually exclusive feature flags which prevents using `cargo clippy --all-features` # The following aliases simplify linting the entire workspace -neon-check = " check --all --all-targets --features napi-experimental,futures,external-buffers" -neon-clippy = "clippy --all --all-targets --features napi-experimental,futures,external-buffers -- -A clippy::missing_safety_doc" -neon-test = " test --all --features=doc-comment,napi-experimental,futures,external-buffers" -neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures,external-buffers -- --cfg docsrs" +neon-check = " check --all --all-targets --features napi-experimental,futures,external-buffers,serde" +neon-clippy = "clippy --all --all-targets --features napi-experimental,futures,external-buffers,serde -- -A clippy::missing_safety_doc" +neon-test = " test --all --features=doc-comment,napi-experimental,futures,external-buffers,serde" +neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures,external-buffers,serde -- --cfg docsrs" diff --git a/Cargo.lock b/Cargo.lock index 40c63133b..dbafc5ef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + [[package]] name = "lazy_static" version = "1.4.0" @@ -248,6 +254,9 @@ version = "0.1.0" dependencies = [ "neon", "once_cell", + "serde", + "serde_bytes", + "serde_json", "tokio", ] @@ -265,6 +274,7 @@ dependencies = [ "once_cell", "psd", "semver", + "serde", "smallvec", "tokio", "widestring", @@ -400,12 +410,58 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + [[package]] name = "semver" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "0.1.1" diff --git a/crates/neon/Cargo.toml b/crates/neon/Cargo.toml index f52660a8c..198405cec 100644 --- a/crates/neon/Cargo.toml +++ b/crates/neon/Cargo.toml @@ -29,6 +29,7 @@ once_cell = "1.10.0" neon-macros = { version = "=1.0.0-alpha.2", path = "../neon-macros" } aquamarine = { version = "0.1.11", optional = true } doc-comment = { version = "0.3.3", optional = true } +serde = { version = "1", optional = true } [dependencies.tokio] version = "1.18.2" diff --git a/crates/neon/src/lib.rs b/crates/neon/src/lib.rs index 959acb7ab..dc5fdffd4 100644 --- a/crates/neon/src/lib.rs +++ b/crates/neon/src/lib.rs @@ -86,6 +86,8 @@ pub mod object; pub mod prelude; pub mod reflect; pub mod result; +#[cfg(feature = "serde")] +mod serde; mod sys; #[cfg(feature = "napi-6")] pub mod thread; @@ -97,6 +99,9 @@ mod types_impl; pub use types_docs::exports as types; +#[cfg(feature = "serde")] +pub use crate::serde::{deserialize, serialize}; + #[doc(hidden)] pub mod macro_internal; diff --git a/crates/neon/src/serde/de.rs b/crates/neon/src/serde/de.rs new file mode 100644 index 000000000..f4f401f73 --- /dev/null +++ b/crates/neon/src/serde/de.rs @@ -0,0 +1,555 @@ +//! Implements a serde deserializer for JavaScript values + +use std::slice; + +use serde::de::{ + self, DeserializeSeed, EnumAccess, IntoDeserializer, MapAccess, SeqAccess, VariantAccess, + Visitor, +}; + +use super::{sys, Error}; + +#[derive(Debug)] +/// High level deserializer for all JavaScript +pub(super) struct Deserializer { + env: sys::Env, + value: sys::Value, +} + +impl Deserializer { + pub(super) unsafe fn new(env: sys::Env, value: sys::Value) -> Self { + Deserializer { env, value } + } +} + +#[derive(Debug)] +/// Specialized deserializer for `Array` +pub(super) struct ArrayAccessor { + env: sys::Env, + array: sys::Value, + len: u32, + index: u32, +} + +impl ArrayAccessor { + unsafe fn new(env: sys::Env, array: sys::Value) -> Result { + Ok(Self::new_with_length( + env, + array, + sys::get_array_length(env, array)?, + )) + } + + unsafe fn new_with_length(env: sys::Env, array: sys::Value, len: u32) -> Self { + Self { + env, + array, + len, + index: 0, + } + } + + unsafe fn next(&mut self) -> Result, Error> { + if self.index >= self.len { + return Ok(None); + } + + let element = sys::get_element(self.env, self.array, self.index)?; + + self.index += 1; + + Ok(Some(element)) + } +} + +#[derive(Debug)] +/// Specialized deserializer for generic `Object` +/// Only enumerable keys are read +pub(super) struct ObjectAccessor { + env: sys::Env, + object: sys::Value, + keys: ArrayAccessor, + // Store the most recent key for reading the next value + next: Option, +} + +impl ObjectAccessor { + unsafe fn new(env: sys::Env, object: sys::Value) -> Result { + let keys = sys::get_property_names(env, object)?; + let keys = ArrayAccessor::new(env, keys)?; + + Ok(Self { + env, + object, + keys, + next: None, + }) + } +} + +#[derive(Debug)] +/// Specialized deserializer for `Object` with known keys +struct StructObjectAccessor { + env: sys::Env, + object: sys::Value, + keys: slice::Iter<'static, &'static str>, + // Store the most recent key for reading the next value + next: Option<&'static str>, +} + +impl StructObjectAccessor { + unsafe fn new(env: sys::Env, object: sys::Value, keys: &'static [&'static str]) -> Self { + Self { + env, + object, + keys: keys.iter(), + next: None, + } + } +} + +impl de::Deserializer<'static> for Deserializer { + type Error = Error; + + // JavaScript is a self describing format, allowing us to provide a deserialization + // implementation without prior knowledge of the schema. This is useful for types + // like `serde_json::Value`. + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + match unsafe { sys::typeof_value(self.env, self.value)? } { + sys::ValueType::Undefined | sys::ValueType::Null => visitor.visit_unit(), + sys::ValueType::Boolean => self.deserialize_bool(visitor), + sys::ValueType::Number => { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + match (n.fract() == 0.0, n.is_sign_positive()) { + (true, true) => visitor.visit_u64(n as u64), + (true, false) => visitor.visit_i64(n as i64), + _ => visitor.visit_f64(n), + } + } + sys::ValueType::String => self.deserialize_string(visitor), + sys::ValueType::Object => { + if unsafe { sys::is_array(self.env, self.value)? } { + visitor.visit_seq(unsafe { ArrayAccessor::new(self.env, self.value)? }) + } else { + visitor.visit_map(unsafe { ObjectAccessor::new(self.env, self.value)? }) + } + } + typ => Err(Error::unsupported_type(typ)), + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + visitor.visit_bool(unsafe { sys::get_value_bool(self.env, self.value)? }) + } + + // XXX: JavaScript only provides an `f64` number type. All integer types + // will truncate fractional values when deserializing. + fn deserialize_i8(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_i8(n as i8) + } + + fn deserialize_i16(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_i16(n as i16) + } + + fn deserialize_i32(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_i32(n as i32) + } + + fn deserialize_i64(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_i64(n as i64) + } + + // XXX: Deserializing a negative number as unsigned will wrap + fn deserialize_u8(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_u8(n as u8) + } + + fn deserialize_u16(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_u16(n as u16) + } + + fn deserialize_u32(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_u32(n as u32) + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_u64(n as u64) + } + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_f32(n as f32) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + let n = unsafe { sys::get_value_double(self.env, self.value)? }; + + visitor.visit_f64(n) + } + + // `char` are serialized as a single character `string` + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + self.deserialize_string(visitor) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + self.deserialize_string(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + visitor.visit_string(unsafe { sys::get_value_string(self.env, self.value)? }) + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + self.deserialize_byte_buf(visitor) + } + + // Bytes are serialized as the idiomatic `ArrayBuffer` JavaScript type + // FIXME: This should support array views + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + match unsafe { sys::get_value_arraybuffer(self.env, self.value) } { + Ok(v) => visitor.visit_byte_buf(v), + Err(err) if err == sys::Status::InvalidArg => { + visitor.visit_byte_buf(unsafe { sys::get_value_arrayview(self.env, self.value)? }) + }, + Err(err) => Err(err.into()), + } + } + + // `None` are serialized as `null`, but when deserializing `undefined` is + // also accepted. + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + match unsafe { sys::typeof_value(self.env, self.value)? } { + sys::ValueType::Null | sys::ValueType::Undefined => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + // Since this is a transcoder, we need to do anything to consume + visitor.visit_unit() + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'static>, + { + visitor.visit_unit() + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'static>, + { + visitor.visit_newtype_struct(self) + } + + // `Array` is used since it is the only sequence type in JavaScript + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + match unsafe { ArrayAccessor::new(self.env, self.value) } { + Ok(accessor) => visitor.visit_seq(accessor), + Err(err) if err.is_array_expected() => self.deserialize_any(visitor), + Err(err) => Err(err), + } + } + + // `Array` are used to serialize tuples; this is a common pattern, especially in TypeScript + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'static>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'static>, + { + self.deserialize_seq(visitor) + } + + // Generic `Object` are used to serialize map + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + // FIXME: Optimize this for `Object` + self.deserialize_any(visitor) + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'static>, + { + // FIXME: Optimize this for known fields + self.deserialize_map(visitor) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'static>, + { + // No-value enums are serialized as `string` + if let Ok(s) = unsafe { sys::get_value_string(self.env, self.value) } { + visitor.visit_enum(s.into_deserializer()) + } else { + visitor.visit_enum(self) + } + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + self.deserialize_string(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'static>, + { + visitor.visit_unit() + } +} + +impl SeqAccess<'static> for ArrayAccessor { + type Error = Error; + + // This will have unpredictable results if the `Array` has a getter that mutates + // the object. It should be _safe_ and return an `Error`, but hopefully users + // don't do this. + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: DeserializeSeed<'static>, + { + unsafe { self.next()? } + .map(|v| seed.deserialize(unsafe { Deserializer::new(self.env, v) })) + .transpose() + } + + // We can efficiently provide a size hint since `Array` have known length + fn size_hint(&self) -> Option { + Some((self.len - self.index) as usize) + } +} + +impl MapAccess<'static> for ObjectAccessor { + type Error = Error; + + // This will have unpredictable results if the `Object` has a getter that mutates + // the object. It should be _safe_ and return an `Error`, but hopefully users + // don't do this on serializable types. + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: DeserializeSeed<'static>, + { + // Store the next `key` for deserializing the value in `next_value_seed` + self.next = unsafe { self.keys.next()? }; + self.next + .map(|v| seed.deserialize(unsafe { Deserializer::new(self.env, v) })) + .transpose() + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'static>, + { + // `Error::missing_key` should only happen in a buggy serde implementation + let key = self.next.ok_or_else(Error::missing_key)?; + let value = unsafe { sys::get_property(self.env, self.object, key)? }; + + seed.deserialize(unsafe { Deserializer::new(self.env, value) }) + } + + // We can efficiently provide a size hint since we fetch all keys ahead of time + fn size_hint(&self) -> Option { + self.keys.size_hint() + } +} + +impl MapAccess<'static> for StructObjectAccessor { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: DeserializeSeed<'static>, + { + // Store the next `key` for deserializing the value in `next_value_seed` + self.next = self.keys.next().copied(); + self.next + .map(|v| seed.deserialize(de::value::StrDeserializer::new(v))) + .transpose() + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'static>, + { + // `Error::missing_key` should only happen in a buggy serde implementation + let key = self.next.ok_or_else(Error::missing_key)?; + let value = unsafe { sys::get_named_property(self.env, self.object, key)? }; + + seed.deserialize(unsafe { Deserializer::new(self.env, value) }) + } + + // We can efficiently provide a size hint since we fetch all keys ahead of time + fn size_hint(&self) -> Option { + self.keys.size_hint().1 + } +} + +impl EnumAccess<'static> for Deserializer { + type Error = Error; + type Variant = Self; + + // Enums are serialized as `{ [type]: value }` + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: DeserializeSeed<'static>, + { + let keys = unsafe { sys::get_property_names(self.env, self.value)? }; + let key = unsafe { sys::get_element(self.env, keys, 0)? }; + let value = unsafe { sys::get_property(self.env, self.value, key)? }; + let deserializer = unsafe { Deserializer::new(self.env, value) }; + let key = seed.deserialize(unsafe { Deserializer::new(self.env, key) })?; + + Ok((key, deserializer)) + } +} + +// Externally tagged enum can be treated equivalent to the enclosed type +impl VariantAccess<'static> for Deserializer { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: DeserializeSeed<'static>, + { + seed.deserialize(self) + } + + fn tuple_variant(self, len: usize, visitor: V) -> Result + where + V: Visitor<'static>, + { + visitor + .visit_seq(unsafe { ArrayAccessor::new_with_length(self.env, self.value, len as u32) }) + } + + fn struct_variant( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'static>, + { + visitor.visit_map(unsafe { StructObjectAccessor::new(self.env, self.value, fields) }) + } +} diff --git a/crates/neon/src/serde/mod.rs b/crates/neon/src/serde/mod.rs new file mode 100644 index 000000000..83737fbc9 --- /dev/null +++ b/crates/neon/src/serde/mod.rs @@ -0,0 +1,145 @@ +use std::{error, fmt}; + +use serde::{de as der, ser, Serialize}; + +use crate::{ + context::Context, + handle::Handle, + result::{JsResult, NeonResult, ResultExt, Throw}, + types::{JsValue, Value}, +}; + +mod de; +mod se; +mod sys; + +/// Attempts to read a JavaScript value into a Rust data type using the serde::Deserialize implementation +pub fn deserialize<'cx, T, V, C>(cx: &mut C, v: Handle) -> NeonResult +where + T: der::DeserializeOwned + ?Sized, + V: Value, + C: Context<'cx>, +{ + unsafe { T::deserialize(de::Deserializer::new(cx.env().to_raw(), v.to_raw())).or_throw(cx) } +} + +/// Attempts to write Rust data into a JavaScript value using the serde::Serialize implementation +pub fn serialize<'cx, T, V, C>(cx: &mut C, v: &V) -> JsResult<'cx, T> +where + T: Value, + V: Serialize + ?Sized, + C: Context<'cx>, +{ + let v = unsafe { + v.serialize(se::Serializer::new(cx.env().to_raw())) + .or_throw(cx)? + }; + + JsValue::new_internal(v).downcast_or_throw(cx) +} + +#[derive(Clone, Debug, PartialEq)] +/// This type represents all possible errors that can occur when serializing or +/// deserializing JavaScript types. +struct Error { + kind: ErrorKind, +} + +impl error::Error for Error {} + +impl Error { + fn new(kind: ErrorKind) -> Self { + Self { kind } + } + + /// Indicates if the error was due to an exception in the JavaScript VM + /// If an exception is pending, all other JavaScript operations will fail + /// until it is cleared. + pub fn is_exception_pending(&self) -> bool { + self.kind == ErrorKind::NodeApi(sys::Status::PendingException) + } + + fn is_array_expected(&self) -> bool { + self.kind == ErrorKind::NodeApi(sys::Status::ArrayExpected) + } + + fn missing_key() -> Self { + ErrorKind::MissingKey.into() + } + + fn unsupported_type(typ: sys::ValueType) -> Self { + ErrorKind::UnsupportedType(typ).into() + } + + fn unsupported_key_type(typ: &'static str) -> Self { + ErrorKind::UnsupportedKeyType(typ).into() + } +} + +#[derive(Clone, Debug, PartialEq)] +enum ErrorKind { + // Serde codec errors + Custom(String), + + // Attempted to use a key type that is not supported by JavaScript + UnsupportedKeyType(&'static str), + + // Serde reads and writes key/value pairs as distinct steps requiring + // Neon to cache the intermediate key. This error is unexpected and should + // never occur outside of a buggy serde implementation. + MissingKey, + + // deserialize_any + UnsupportedType(sys::ValueType), + + // N-API + NodeApi(sys::Status), +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Error::new(kind) + } +} + +impl From for Error { + fn from(other: sys::Status) -> Self { + ErrorKind::NodeApi(other).into() + } +} + +impl der::Error for Error { + fn custom(err: T) -> Self { + Error { + kind: ErrorKind::Custom(err.to_string()), + } + } +} + +impl ser::Error for Error { + fn custom(err: T) -> Self { + der::Error::custom(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.kind { + ErrorKind::Custom(err) => f.write_str(err), + ErrorKind::UnsupportedKeyType(ty) => write!(f, "UnsupportedKeyType({})", ty), + ErrorKind::MissingKey => f.write_str("MissingKey"), + ErrorKind::UnsupportedType(typ) => write!(f, "UnsupportedType({:?})", typ), + ErrorKind::NodeApi(err) => write!(f, "Node-API ({:?})", err), + } + } +} + +impl ResultExt for Result { + fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult { + match self { + Ok(v) => Ok(v), + Err(e) if e.is_exception_pending() => Err(Throw::new()), + Err(e) => cx.throw_error(e.to_string()), + } + } +} diff --git a/crates/neon/src/serde/se.rs b/crates/neon/src/serde/se.rs new file mode 100644 index 000000000..9b022a903 --- /dev/null +++ b/crates/neon/src/serde/se.rs @@ -0,0 +1,619 @@ +//! Implements a serde serializer for JavaScript values + +use serde::{ser, Serialize}; + +use super::{sys, Error}; + +#[derive(Clone, Copy)] +#[repr(transparent)] +/// High level deserializer for all JavaScript values +pub(super) struct Serializer { + env: sys::Env, +} + +impl Serializer { + pub(super) unsafe fn new(env: sys::Env) -> Self { + Self { env } + } +} + +// Specialized serializer for writing to an `Array` +pub(super) struct ArraySerializer { + serializer: Serializer, + value: sys::Value, + offset: usize, +} + +impl ArraySerializer { + unsafe fn new(serializer: Serializer, value: sys::Value) -> Self { + Self { + serializer, + value, + offset: 0, + } + } +} + +// `Array` serializer for externally tagged enum `{ [key]: value }` +pub(super) struct WrappedArraySerializer { + serializer: ArraySerializer, + value: sys::Value, +} + +impl WrappedArraySerializer { + unsafe fn new(serializer: ArraySerializer, value: sys::Value) -> Self { + Self { serializer, value } + } +} + +// Specialized serializer for writing to a generic `Object` +pub(super) struct ObjectSerializer { + serializer: Serializer, + value: sys::Value, + key: Option, +} + +impl ObjectSerializer { + unsafe fn new(serializer: Serializer, value: sys::Value) -> Self { + Self { + serializer, + value, + key: None, + } + } +} + +// `Object` serializer for externally tagged enum `{ [key]: value }` +pub(super) struct WrappedObjectSerializer { + serializer: ObjectSerializer, + value: sys::Value, +} + +impl WrappedObjectSerializer { + unsafe fn new(serializer: ObjectSerializer, value: sys::Value) -> Self { + Self { serializer, value } + } +} + +// Specialized serializer that understands valid key types +struct KeySerializer { + serializer: Serializer, +} + +impl KeySerializer { + unsafe fn new(serializer: Serializer) -> Self { + Self { serializer } + } +} + +impl ser::Serializer for Serializer { + type Ok = sys::Value; + type Error = Error; + + // Limited JavaScript types require sequences and tuples to both use `Array` + type SerializeSeq = ArraySerializer; + type SerializeTuple = ArraySerializer; + type SerializeTupleStruct = ArraySerializer; + type SerializeTupleVariant = WrappedArraySerializer; + type SerializeMap = ObjectSerializer; + type SerializeStruct = ObjectSerializer; + type SerializeStructVariant = WrappedObjectSerializer; + + fn serialize_bool(self, v: bool) -> Result { + Ok(unsafe { sys::create_bool(self.env, v)? }) + } + + // All numeric types are serialized into `f64` + fn serialize_i8(self, v: i8) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + fn serialize_i16(self, v: i16) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + fn serialize_i32(self, v: i32) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + // XXX: Precision loss. Support `BigInt`? + fn serialize_i64(self, v: i64) -> Result { + Ok(unsafe { sys::create_double(self.env, v as f64)? }) + } + + fn serialize_u8(self, v: u8) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + fn serialize_u16(self, v: u16) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + fn serialize_u32(self, v: u32) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + // XXX: Precision loss. Support `BigInt`? + fn serialize_u64(self, v: u64) -> Result { + Ok(unsafe { sys::create_double(self.env, v as f64)? }) + } + + fn serialize_f32(self, v: f32) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + fn serialize_f64(self, v: f64) -> Result { + Ok(unsafe { sys::create_double(self.env, v)? }) + } + + // `char` are serialized as single character string + fn serialize_char(self, v: char) -> Result { + Ok(unsafe { sys::create_string(self.env, v.to_string())? }) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(unsafe { sys::create_string(self.env, v)? }) + } + + // Bytes are serialized as `ArrayBuffer` + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(unsafe { sys::create_arraybuffer(self.env, v)? }) + } + + // `None` is serialized as a `null` + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + // Serialized as the value with no wrapper + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + // JavaScript does not have a unit type; `null` is used instead + fn serialize_unit(self) -> Result { + Ok(unsafe { sys::get_null(self.env)? }) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + // Data-less enum are serialized as `string` + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + // New-type struct do not include a wrapper; serialized as the inner type + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + // Serialize as `{ [variant name]: [value] }` + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + unsafe { + let o = sys::create_object(self.env)?; + let v = value.serialize(self)?; + + sys::set_named_property(self.env, o, variant, v)?; + + Ok(o) + } + } + + fn serialize_seq(self, len: Option) -> Result { + let len = len.unwrap_or_default(); + + unsafe { + let value = sys::create_array_with_length(self.env, len)?; + + Ok(ArraySerializer::new(self, value)) + } + } + + // Tuple `(a, b, ...)` are serialized as array `[a, b, ...]` + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + // Externally tagged enum; `{ [variant]: value }` + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + unsafe { + let env = self.env; + let wrapper = sys::create_object(env)?; + let arr = sys::create_array_with_length(env, len)?; + let serializer = ArraySerializer::new(self, arr); + + sys::set_named_property(env, wrapper, variant, arr)?; + + Ok(WrappedArraySerializer::new(serializer, wrapper)) + } + } + + fn serialize_map(self, _len: Option) -> Result { + unsafe { + let value = sys::create_object(self.env)?; + Ok(ObjectSerializer::new(self, value)) + } + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + // Externally tagged enum; `{ [variant]: value }` + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + unsafe { + let env = self.env; + let wrapper = sys::create_object(env)?; + let value = sys::create_object(env)?; + let serializer = ObjectSerializer::new(self, value); + + sys::set_named_property(env, wrapper, variant, value)?; + + Ok(WrappedObjectSerializer::new(serializer, wrapper)) + } + } +} + +impl ser::SerializeSeq for ArraySerializer { + type Ok = sys::Value; + type Error = Error; + + // XXX: Silently truncates with more than 2^32 elements + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let value = value.serialize(self.serializer)?; + let k = self.offset as u32; + + unsafe { sys::set_element(self.serializer.env, self.value, k, value)? }; + self.offset += 1; + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.value) + } +} + +impl ser::SerializeTuple for ArraySerializer { + type Ok = sys::Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl ser::SerializeTupleStruct for ArraySerializer { + type Ok = sys::Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl ser::SerializeTupleVariant for WrappedArraySerializer { + type Ok = sys::Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(&mut self.serializer, value) + } + + fn end(self) -> Result { + Ok(self.value) + } +} + +impl ser::SerializeMap for ObjectSerializer { + type Ok = sys::Value; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.key = Some(key.serialize(unsafe { KeySerializer::new(self.serializer) })?); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let k = self.key.ok_or_else(Error::missing_key)?; + let v = value.serialize(self.serializer)?; + + unsafe { sys::set_property(self.serializer.env, self.value, k, v)? }; + + Ok(()) + } + + fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Self::Error> + where + K: ?Sized + Serialize, + V: ?Sized + Serialize, + { + let k = key.serialize(unsafe { KeySerializer::new(self.serializer) })?; + let v = value.serialize(self.serializer)?; + + unsafe { sys::set_property(self.serializer.env, self.value, k, v)? }; + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.value) + } +} + +impl ser::SerializeStruct for ObjectSerializer { + type Ok = sys::Value; + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ser::SerializeMap::serialize_entry(self, key, value) + } + + fn end(self) -> Result { + Ok(self.value) + } +} + +impl ser::SerializeStructVariant for WrappedObjectSerializer { + type Ok = sys::Value; + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ser::SerializeMap::serialize_entry(&mut self.serializer, key, value) + } + + fn end(self) -> Result { + Ok(self.value) + } +} + +impl ser::Serializer for KeySerializer { + type Ok = sys::Value; + type Error = Error; + + type SerializeSeq = ser::Impossible; + type SerializeTuple = ser::Impossible; + type SerializeTupleStruct = ser::Impossible; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = ser::Impossible; + type SerializeStruct = ser::Impossible; + type SerializeStructVariant = ser::Impossible; + + fn serialize_bool(self, _v: bool) -> Result { + Err(Error::unsupported_key_type("bool")) + } + + fn serialize_i8(self, _v: i8) -> Result { + Err(Error::unsupported_key_type("i8")) + } + + fn serialize_i16(self, _v: i16) -> Result { + Err(Error::unsupported_key_type("i16")) + } + + fn serialize_i32(self, _v: i32) -> Result { + Err(Error::unsupported_key_type("i32")) + } + + fn serialize_i64(self, _v: i64) -> Result { + Err(Error::unsupported_key_type("i64")) + } + + fn serialize_u8(self, _v: u8) -> Result { + Err(Error::unsupported_key_type("u8")) + } + + fn serialize_u16(self, _v: u16) -> Result { + Err(Error::unsupported_key_type("u16")) + } + + fn serialize_u32(self, _v: u32) -> Result { + Err(Error::unsupported_key_type("u32")) + } + + fn serialize_u64(self, _v: u64) -> Result { + Err(Error::unsupported_key_type("u64")) + } + + fn serialize_f32(self, _v: f32) -> Result { + Err(Error::unsupported_key_type("f32")) + } + + fn serialize_f64(self, _v: f64) -> Result { + Err(Error::unsupported_key_type("f64")) + } + + fn serialize_char(self, v: char) -> Result { + self.serializer.serialize_char(v) + } + + fn serialize_str(self, v: &str) -> Result { + self.serializer.serialize_str(v) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(Error::unsupported_key_type("&[u8]")) + } + + fn serialize_none(self) -> Result { + Err(Error::unsupported_key_type("none")) + } + + fn serialize_some(self, _value: &T) -> Result + where + T: ?Sized + Serialize, + { + Err(Error::unsupported_key_type("none")) + } + + fn serialize_unit(self) -> Result { + Err(Error::unsupported_key_type("()")) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Err(Error::unsupported_key_type("()")) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serializer.serialize_str(variant) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + Err(Error::unsupported_key_type("newtype_variant")) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(Error::unsupported_key_type("seq")) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(Error::unsupported_key_type("seq")) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(Error::unsupported_key_type("tuple struct")) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::unsupported_key_type("tuple struct variant")) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(Error::unsupported_key_type("map")) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(Error::unsupported_key_type("struct")) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::unsupported_key_type("struct variant")) + } +} diff --git a/crates/neon/src/serde/sys.rs b/crates/neon/src/serde/sys.rs new file mode 100644 index 000000000..fa88b830a --- /dev/null +++ b/crates/neon/src/serde/sys.rs @@ -0,0 +1,231 @@ +//! Node-API wrappers used by serde transcoding +//! +//! In many cases, these functions provide similar functionality to functions +//! available elsewhere in `neon-`. However, keeping serde fully self contained +//! has a few benefits: +//! +//! * Wrappers can be written, altered, combined and otherwise optimized for +//! providing the most efficient possible serde implementation +//! * All errors can be forwarded to avoid additional checks and panics +//! * The serde implementation remains self contained for potential extraction +//! into a separate crate +//! +//! _Do not export anything from this file outside of the serde module._ + +use std::{mem::MaybeUninit, ptr, slice}; + +use crate::sys; + +pub(super) use crate::sys::{Env, Status, Value, ValueType}; + +// Extension trait to more easily return early from a failed Node-API call +trait Verify { + fn verify(self) -> Result<(), Status>; +} + +impl Verify for Status { + fn verify(self) -> Result<(), Status> { + if self == Status::Ok { + Ok(()) + } else { + Err(self) + } + } +} + +pub(super) unsafe fn create_bool(env: Env, v: bool) -> Result { + let mut value = MaybeUninit::uninit(); + sys::get_boolean(env, v, value.as_mut_ptr()).verify()?; + Ok(value.assume_init()) +} + +pub(super) unsafe fn create_double(env: Env, v: impl Into) -> Result { + let mut value = MaybeUninit::uninit(); + sys::create_double(env, v.into(), value.as_mut_ptr()).verify()?; + Ok(value.assume_init()) +} + +pub(super) unsafe fn create_string(env: Env, v: impl AsRef) -> Result { + let mut value = MaybeUninit::uninit(); + let v = v.as_ref(); + sys::create_string_utf8(env, v.as_ptr().cast(), v.len(), value.as_mut_ptr()).verify()?; + Ok(value.assume_init()) +} + +pub(super) unsafe fn create_arraybuffer(env: Env, v: &[u8]) -> Result { + let mut value = MaybeUninit::uninit(); + let mut data = MaybeUninit::uninit(); + + sys::create_arraybuffer(env, v.len(), data.as_mut_ptr(), value.as_mut_ptr()).verify()?; + + let value = value.assume_init(); + let data = slice::from_raw_parts_mut(data.assume_init().cast(), v.len()); + + data.copy_from_slice(v); + + Ok(value) +} + +pub(super) unsafe fn get_null(env: Env) -> Result { + let mut value = MaybeUninit::uninit(); + sys::get_null(env, value.as_mut_ptr()).verify()?; + Ok(value.assume_init()) +} + +pub(super) unsafe fn create_object(env: Env) -> Result { + let mut value = MaybeUninit::uninit(); + sys::create_object(env, value.as_mut_ptr()).verify()?; + Ok(value.assume_init()) +} + +pub(super) unsafe fn set_property(env: Env, o: Value, k: Value, v: Value) -> Result<(), Status> { + sys::set_property(env, o, k, v).verify()?; + Ok(()) +} + +pub(super) unsafe fn set_named_property( + env: Env, + o: Value, + k: impl AsRef, + v: Value, +) -> Result<(), Status> { + sys::set_property(env, o, create_string(env, k)?, v).verify()?; + Ok(()) +} + +pub(super) unsafe fn create_array_with_length(env: Env, len: usize) -> Result { + let mut value = MaybeUninit::uninit(); + sys::create_array_with_length(env, len, value.as_mut_ptr()).verify()?; + Ok(value.assume_init()) +} + +pub(super) unsafe fn set_element(env: Env, arr: Value, k: u32, v: Value) -> Result<(), Status> { + sys::set_element(env, arr, k, v).verify()?; + Ok(()) +} + +pub(super) unsafe fn get_array_length(env: Env, value: Value) -> Result { + let mut len = 0u32; + sys::get_array_length(env, value, &mut len as *mut u32).verify()?; + Ok(len) +} + +pub(super) unsafe fn get_element(env: Env, arr: Value, i: u32) -> Result { + let mut out = MaybeUninit::uninit(); + sys::get_element(env, arr, i, out.as_mut_ptr()).verify()?; + Ok(out.assume_init()) +} + +pub(super) unsafe fn get_property_names(env: Env, value: Value) -> Result { + let mut out = MaybeUninit::uninit(); + sys::get_property_names(env, value, out.as_mut_ptr()).verify()?; + Ok(out.assume_init()) +} + +pub(super) unsafe fn typeof_value(env: Env, value: Value) -> Result { + let mut out = MaybeUninit::uninit(); + sys::typeof_value(env, value, out.as_mut_ptr()).verify()?; + Ok(out.assume_init()) +} + +pub(super) unsafe fn is_array(env: Env, value: Value) -> Result { + let mut result = false; + sys::is_array(env, value, &mut result).verify()?; + Ok(result) +} + +pub(super) unsafe fn get_value_bool(env: Env, value: Value) -> Result { + let mut out = false; + sys::get_value_bool(env, value, &mut out as *mut bool).verify()?; + Ok(out) +} + +pub(super) unsafe fn get_value_double(env: Env, value: Value) -> Result { + let mut out = 0f64; + sys::get_value_double(env, value, &mut out as *mut f64).verify()?; + Ok(out) +} + +unsafe fn get_string_len(env: Env, value: Value) -> Result { + let mut out = 0usize; + sys::get_value_string_utf8(env, value, ptr::null_mut(), 0, &mut out as *mut usize).verify()?; + Ok(out) +} + +pub(super) unsafe fn get_value_string(env: Env, value: Value) -> Result { + let mut out = 0usize; + let string_len = get_string_len(env, value)?; + let buf_len = string_len + 1; + let mut buf = Vec::::with_capacity(buf_len); + + sys::get_value_string_utf8( + env, + value, + buf.as_mut_ptr().cast(), + buf_len, + &mut out as *mut usize, + ) + .verify()?; + + debug_assert_eq!(out, string_len); + buf.set_len(string_len); + + Ok(String::from_utf8_unchecked(buf)) +} + +pub(super) unsafe fn get_value_arraybuffer(env: Env, value: Value) -> Result, Status> { + let mut len = 0usize; + let mut out = MaybeUninit::uninit(); + + sys::get_arraybuffer_info(env, value, out.as_mut_ptr(), &mut len as *mut usize).verify()?; + + let buf = if len == 0 { + &[] + } else { + slice::from_raw_parts(out.assume_init().cast(), len) + }; + + Ok(buf.to_vec()) +} + +pub(super) unsafe fn get_value_arrayview(env: Env, value: Value) -> Result, Status> { + let mut len = 0usize; + let mut typ = MaybeUninit::uninit(); + let mut out = MaybeUninit::uninit(); + + sys::get_typedarray_info( + env, + value, + typ.as_mut_ptr(), + &mut len, + out.as_mut_ptr(), + ptr::null_mut(), + ptr::null_mut(), + ).verify()?; + + if !matches!(typ.assume_init(), sys::TypedArrayType::U8 | sys::TypedArrayType::U8Clamped) { + return Err(Status::InvalidArg); + } + + let buf = if len == 0 { + &[] + } else { + slice::from_raw_parts(out.assume_init().cast(), len) + }; + + Ok(buf.to_vec()) +} + +pub(super) unsafe fn get_property(env: Env, object: Value, key: Value) -> Result { + let mut out = MaybeUninit::uninit(); + sys::get_property(env, object, key, out.as_mut_ptr()).verify()?; + Ok(out.assume_init()) +} + +pub(super) unsafe fn get_named_property( + env: Env, + object: Value, + key: impl AsRef, +) -> Result { + get_property(env, object, create_string(env, key)?) +} diff --git a/crates/neon/src/sys/async_work.rs b/crates/neon/src/sys/async_work.rs index 9aa9e07f2..8ed804a93 100644 --- a/crates/neon/src/sys/async_work.rs +++ b/crates/neon/src/sys/async_work.rs @@ -74,7 +74,7 @@ pub unsafe fn schedule( napi::Status::Ok => {} status => { // If queueing failed, delete the work to prevent a leak - let _ =napi::delete_async_work(env, *work); + let _ = napi::delete_async_work(env, *work); assert_eq!(status, napi::Status::Ok); } } diff --git a/crates/neon/src/sys/bindings/functions.rs b/crates/neon/src/sys/bindings/functions.rs index dc03b5271..6e580be5c 100644 --- a/crates/neon/src/sys/bindings/functions.rs +++ b/crates/neon/src/sys/bindings/functions.rs @@ -253,6 +253,9 @@ mod napi1 { message: *const c_char, message_len: usize, ); + + #[cfg(feature = "serde")] + fn get_property_names(env: Env, object: Value, result: *mut Value) -> Status; } ); } diff --git a/crates/neon/src/sys/primitive.rs b/crates/neon/src/sys/primitive.rs index c8db478a0..3e143eb32 100644 --- a/crates/neon/src/sys/primitive.rs +++ b/crates/neon/src/sys/primitive.rs @@ -5,7 +5,10 @@ use super::{ /// Mutates the `out` argument provided to refer to the global `undefined` object. pub unsafe fn undefined(out: &mut Local, env: Env) { - assert_eq!(napi::get_undefined(env, out as *mut Local), napi::Status::Ok); + assert_eq!( + napi::get_undefined(env, out as *mut Local), + napi::Status::Ok + ); } /// Mutates the `out` argument provided to refer to the global `null` object. @@ -15,7 +18,10 @@ pub unsafe fn null(out: &mut Local, env: Env) { /// Mutates the `out` argument provided to refer to one of the global `true` or `false` objects. pub unsafe fn boolean(out: &mut Local, env: Env, b: bool) { - assert_eq!(napi::get_boolean(env, b, out as *mut Local), napi::Status::Ok); + assert_eq!( + napi::get_boolean(env, b, out as *mut Local), + napi::Status::Ok + ); } /// Get the boolean value out of a `Local` object. If the `Local` object does not contain a @@ -32,7 +38,10 @@ pub unsafe fn boolean_value(env: Env, p: Local) -> bool { /// Mutates the `out` argument provided to refer to a newly created `Local` containing a /// JavaScript number. pub unsafe fn number(out: &mut Local, env: Env, v: f64) { - assert_eq!(napi::create_double(env, v, out as *mut Local), napi::Status::Ok); + assert_eq!( + napi::create_double(env, v, out as *mut Local), + napi::Status::Ok + ); } /// Gets the underlying value of an `Local` object containing a JavaScript number. Panics if diff --git a/test/napi/Cargo.toml b/test/napi/Cargo.toml index 02aea60eb..c8b752b45 100644 --- a/test/napi/Cargo.toml +++ b/test/napi/Cargo.toml @@ -11,9 +11,12 @@ crate-type = ["cdylib"] [dependencies] once_cell = "1" +serde = { version = "1", features = ["derive"] } +serde_bytes = "0.11" +serde_json = "1" tokio = { version = "1", features = ["rt-multi-thread"] } [dependencies.neon] version = "1.0.0-alpha.2" path = "../../crates/neon" -features = ["futures", "napi-experimental", "external-buffers"] +features = ["external-buffers", "futures", "napi-experimental", "serde"] diff --git a/test/napi/lib/serde.js b/test/napi/lib/serde.js new file mode 100644 index 000000000..fd59bfbf2 --- /dev/null +++ b/test/napi/lib/serde.js @@ -0,0 +1,9 @@ +const addon = require(".."); + +describe("Serde", () => { + const suite = addon.build_serde_test_suite(); + + for (const [k, v] of Object.entries(suite)) { + it(k, v); + } +}); \ No newline at end of file diff --git a/test/napi/package.json b/test/napi/package.json index fa9a6c6d5..692bda2f0 100644 --- a/test/napi/package.json +++ b/test/napi/package.json @@ -6,6 +6,7 @@ "license": "MIT", "scripts": { "install": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics", + "mocha": "mocha", "test": "mocha --v8-expose-gc --timeout 5000 --recursive lib" }, "devDependencies": { diff --git a/test/napi/src/js/serde.rs b/test/napi/src/js/serde.rs new file mode 100644 index 000000000..28adf0455 --- /dev/null +++ b/test/napi/src/js/serde.rs @@ -0,0 +1,1328 @@ +// Adapted from https://github.com/serde-rs/json/blob/master/tests/test.rs + +use std::{any, collections::BTreeMap, fmt, marker::PhantomData}; + +use neon::{prelude::*, types::buffer::TypedArray}; + +use serde::{ + de::{self, DeserializeOwned}, + ser, Deserialize, Serialize, +}; + +use serde_bytes::{ByteBuf, Bytes}; + +use serde_json::json; + +const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991; +const MIN_SAFE_INTEGER: i64 = -9_007_199_254_740_991; + +macro_rules! json_str { + ([]) => { + "[]" + }; + ([ $e0:tt $(, $e:tt)* $(,)? ]) => { + concat!("[", + json_str!($e0), + $(",", json_str!($e),)* + "]") + }; + ({}) => { + "{}" + }; + ({ $k0:tt : $v0:tt $(, $k:tt : $v:tt)* $(,)? }) => { + concat!("{", + stringify!($k0), ":", json_str!($v0), + $(",", stringify!($k), ":", json_str!($v),)* + "}") + }; + (($other:tt)) => { + $other + }; + ($other:tt) => { + stringify!($other) + }; +} + +macro_rules! treemap { + () => { + BTreeMap::new() + }; + ($($k:expr => $v:expr),+) => { + { + let mut m = BTreeMap::new(); + $( + m.insert($k, $v); + )+ + m + } + }; +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +enum Animal { + Dog, + Frog(String, Vec), + Cat { age: usize, name: String }, + AntHive(Vec), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +struct Inner { + a: (), + b: usize, + c: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +struct Outer { + inner: Vec, +} + +fn export(cx: &mut FunctionContext, o: &JsObject, f: F) -> NeonResult<()> +where + F: Fn(&mut FunctionContext) + 'static, +{ + let f = JsFunction::new(cx, move |mut cx| { + f(&mut cx); + Ok(cx.undefined()) + })?; + + o.set(cx, any::type_name::(), f)?; + + Ok(()) +} + +fn test_encode_ok(cx: &mut FunctionContext, tests: &[(T, &str)]) +where + T: PartialEq + fmt::Debug + ser::Serialize + de::DeserializeOwned, +{ + for &(ref value, out) in tests { + let out = out.to_string(); + let s = to_string(cx, value).unwrap(); + assert_eq!(s, out); + + // Make sure we can round trip + let v = neon::serialize(cx, value).unwrap(); + let d = neon::deserialize::(cx, v).unwrap(); + assert_eq!(value, &d); + } +} + +fn test_parse_ok(cx: &mut FunctionContext, tests: Vec<(&str, T)>) +where + T: Clone + fmt::Debug + PartialEq + ser::Serialize + de::DeserializeOwned, +{ + for (s, value) in tests { + let v: T = from_str(cx, s).unwrap(); + assert_eq!(v, value.clone()); + + // Make sure we can round trip + let s2 = to_string(cx, &v).unwrap(); + let v2 = from_str(cx, &s2).unwrap(); + assert_eq!(v, v2); + } +} + +// For testing representations that the deserializer accepts but the serializer +// never generates. These do not survive a round-trip. +fn test_parse_unusual_ok(cx: &mut FunctionContext, tests: Vec<(&str, T)>) +where + T: Clone + fmt::Debug + PartialEq + ser::Serialize + de::DeserializeOwned, +{ + for (s, value) in tests { + let v: T = from_str(cx, s).unwrap(); + assert_eq!(v, value); + } +} + +fn to_string(cx: &mut FunctionContext, v: &T) -> NeonResult +where + T: ?Sized + serde::Serialize, +{ + let v = neon::serialize(cx, v)?; + let s = cx + .global() + .get::(cx, "JSON")? + .get::(cx, "stringify")? + .call_with(cx) + .arg::(v) + .apply::(cx)?; + + Ok(s.value(cx)) +} + +fn from_str(cx: &mut FunctionContext, s: &str) -> NeonResult +where + T: de::DeserializeOwned, +{ + let v = cx + .global() + .get::(cx, "JSON")? + .get::(cx, "parse")? + .call_with(cx) + .arg(cx.string(s)) + .apply::(cx)?; + + neon::deserialize(cx, v) +} + +fn test_write_null(cx: &mut FunctionContext) { + let tests = &[((), "null")]; + test_encode_ok(cx, tests); +} + +fn test_write_u64(cx: &mut FunctionContext) { + let tests = &[ + (3u64, "3"), + (MAX_SAFE_INTEGER, &MAX_SAFE_INTEGER.to_string()), + ]; + test_encode_ok(cx, tests); +} + +fn test_write_i64(cx: &mut FunctionContext) { + let tests = &[ + (3i64, "3"), + (-2i64, "-2"), + (-1234i64, "-1234"), + (MIN_SAFE_INTEGER, &MIN_SAFE_INTEGER.to_string()), + ]; + test_encode_ok(cx, tests); +} + +fn test_write_f64(cx: &mut FunctionContext) { + let tests = &[ + (3.0, "3"), + (3.1, "3.1"), + (-1.5, "-1.5"), + (0.5, "0.5"), + (f64::MIN, "-1.7976931348623157e+308"), + (f64::MAX, "1.7976931348623157e+308"), + (f64::EPSILON, "2.220446049250313e-16"), + ]; + test_encode_ok(cx, tests); +} + +fn test_encode_nonfinite_float_yields_null(cx: &mut FunctionContext) { + let v = to_string(cx, &f64::NAN).unwrap(); + assert_eq!(v, "null"); + + let v = to_string(cx, &f64::INFINITY).unwrap(); + assert_eq!(v, "null"); + + let v = to_string(cx, &f32::NAN).unwrap(); + assert_eq!(v, "null"); + + let v = to_string(cx, &f32::INFINITY).unwrap(); + assert_eq!(v, "null"); +} + +fn test_write_str(cx: &mut FunctionContext) { + let tests = &[("".to_owned(), "\"\""), ("foo".to_owned(), "\"foo\"")]; + test_encode_ok(cx, tests); +} + +fn test_write_bool(cx: &mut FunctionContext) { + let tests = &[(true, "true"), (false, "false")]; + test_encode_ok(cx, tests); +} + +fn test_write_char(cx: &mut FunctionContext) { + let tests = &[ + ('n', "\"n\""), + ('"', "\"\\\"\""), + ('\\', "\"\\\\\""), + ('/', "\"/\""), + ('\x08', "\"\\b\""), + ('\x0C', "\"\\f\""), + ('\n', "\"\\n\""), + ('\r', "\"\\r\""), + ('\t', "\"\\t\""), + ('\x0B', "\"\\u000b\""), + ('\u{3A3}', "\"\u{3A3}\""), + ]; + test_encode_ok(cx, tests); +} + +fn test_write_list(cx: &mut FunctionContext) { + test_encode_ok( + cx, + &[ + (vec![], "[]"), + (vec![true], "[true]"), + (vec![true, false], "[true,false]"), + ], + ); + + test_encode_ok( + cx, + &[ + (vec![vec![], vec![], vec![]], "[[],[],[]]"), + (vec![vec![1, 2, 3], vec![], vec![]], "[[1,2,3],[],[]]"), + (vec![vec![], vec![1, 2, 3], vec![]], "[[],[1,2,3],[]]"), + (vec![vec![], vec![], vec![1, 2, 3]], "[[],[],[1,2,3]]"), + ], + ); + + let long_test_list = json!([false, null, ["foo\nbar", 3.5]]); + + test_encode_ok( + cx, + &[( + long_test_list, + json_str!([false, null, ["foo\nbar", 3.5]]), + )], + ); +} + +fn test_write_object(cx: &mut FunctionContext) { + test_encode_ok( + cx, + &[ + (treemap!(), "{}"), + (treemap!("a".to_string() => true), "{\"a\":true}"), + ( + treemap!( + "a".to_string() => true, + "b".to_string() => false + ), + "{\"a\":true,\"b\":false}", + ), + ], + ); + + test_encode_ok( + cx, + &[ + ( + treemap![ + "a".to_string() => treemap![], + "b".to_string() => treemap![], + "c".to_string() => treemap![] + ], + "{\"a\":{},\"b\":{},\"c\":{}}", + ), + ( + treemap![ + "a".to_string() => treemap![ + "a".to_string() => treemap!["a".to_string() => vec![1,2,3]], + "b".to_string() => treemap![], + "c".to_string() => treemap![] + ], + "b".to_string() => treemap![], + "c".to_string() => treemap![] + ], + "{\"a\":{\"a\":{\"a\":[1,2,3]},\"b\":{},\"c\":{}},\"b\":{},\"c\":{}}", + ), + ( + treemap![ + "a".to_string() => treemap![], + "b".to_string() => treemap![ + "a".to_string() => treemap!["a".to_string() => vec![1,2,3]], + "b".to_string() => treemap![], + "c".to_string() => treemap![] + ], + "c".to_string() => treemap![] + ], + "{\"a\":{},\"b\":{\"a\":{\"a\":[1,2,3]},\"b\":{},\"c\":{}},\"c\":{}}", + ), + ( + treemap![ + "a".to_string() => treemap![], + "b".to_string() => treemap![], + "c".to_string() => treemap![ + "a".to_string() => treemap!["a".to_string() => vec![1,2,3]], + "b".to_string() => treemap![], + "c".to_string() => treemap![] + ] + ], + "{\"a\":{},\"b\":{},\"c\":{\"a\":{\"a\":[1,2,3]},\"b\":{},\"c\":{}}}", + ), + ], + ); + + test_encode_ok(cx, &[(treemap!['c' => ()], "{\"c\":null}")]); + + let complex_obj = json!({ + "b": [ + {"c": "\x0c\x1f\r"}, + {"d": ""} + ] + }); + + test_encode_ok( + cx, + &[( + complex_obj, + json_str!({ + "b": [ + { + "c": (r#""\f\u001f\r""#) + }, + { + "d": "" + } + ] + }), + )], + ); +} + +fn test_write_tuple(cx: &mut FunctionContext) { + test_encode_ok(cx, &[((5,), "[5]")]); + + test_encode_ok(cx, &[((5, (6, "abc".to_owned())), "[5,[6,\"abc\"]]")]); +} + +fn test_write_enum(cx: &mut FunctionContext) { + test_encode_ok( + cx, + &[ + (Animal::Dog, "\"Dog\""), + ( + Animal::Frog("Henry".to_string(), vec![]), + "{\"Frog\":[\"Henry\",[]]}", + ), + ( + Animal::Frog("Henry".to_string(), vec![349]), + "{\"Frog\":[\"Henry\",[349]]}", + ), + ( + Animal::Frog("Henry".to_string(), vec![349, 102]), + "{\"Frog\":[\"Henry\",[349,102]]}", + ), + ( + Animal::Cat { + age: 5, + name: "Kate".to_string(), + }, + "{\"Cat\":{\"age\":5,\"name\":\"Kate\"}}", + ), + ( + Animal::AntHive(vec!["Bob".to_string(), "Stuart".to_string()]), + "{\"AntHive\":[\"Bob\",\"Stuart\"]}", + ), + ], + ); +} + +fn test_write_option(cx: &mut FunctionContext) { + test_encode_ok( + cx, + &[ + (None, "null"), + (Some("jodhpurs".to_owned()), "\"jodhpurs\""), + ], + ); + + test_encode_ok( + cx, + &[ + (None, "null"), + ( + Some(vec!["foo".to_owned(), "bar".to_owned()]), + "[\"foo\",\"bar\"]", + ), + ], + ); +} + +fn test_write_newtype_struct(cx: &mut FunctionContext) { + #[derive(Clone, Deserialize, Serialize, PartialEq, Debug)] + struct Newtype(BTreeMap); + + let inner = Newtype(treemap!(String::from("inner") => 123)); + + test_encode_ok(cx, &[(inner.clone(), r#"{"inner":123}"#)]); + + let outer = treemap!(String::from("outer") => inner); + + test_encode_ok(cx, &[(outer, r#"{"outer":{"inner":123}}"#)]); +} + +fn test_deserialize_number_to_untagged_enum(cx: &mut FunctionContext) { + #[derive(PartialEq, Deserialize, Debug)] + #[serde(untagged)] + enum E { + N(T), + } + + fn test(h: Handle, v: T, cx: &mut FunctionContext) + where + T: PartialEq + DeserializeOwned + fmt::Debug, + { + assert_eq!(neon::deserialize::, _, _>(cx, h).unwrap(), E::N(v)); + } + + test(cx.number(5), 5i64, cx); + test(cx.number(0), 0i64, cx); + test(cx.number(-0), 0i64, cx); + test(cx.number(-5), -5i64, cx); + test(cx.number(0), 0u64, cx); + test(cx.number(5), 5u64, cx); + test(cx.number(-5), -5f64, cx); + test(cx.number(-5.5), -5.5f64, cx); + test(cx.number(0), 0f64, cx); + test(cx.number(5), 5f64, cx); + test(cx.number(5.5), 5.5f64, cx); +} + +fn test_parse_null(cx: &mut FunctionContext) { + test_parse_ok(cx, vec![("null", ())]); +} + +fn test_parse_bool(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("true", true), + (" true ", true), + ("false", false), + (" false ", false), + ], + ); +} + +fn test_parse_char(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("\"n\"", 'n'), + ("\"\\\"\"", '"'), + ("\"\\\\\"", '\\'), + ("\"/\"", '/'), + ("\"\\b\"", '\x08'), + ("\"\\f\"", '\x0C'), + ("\"\\n\"", '\n'), + ("\"\\r\"", '\r'), + ("\"\\t\"", '\t'), + ("\"\\u000b\"", '\x0B'), + ("\"\\u000B\"", '\x0B'), + ("\"\u{3A3}\"", '\u{3A3}'), + ], + ); +} + +fn test_parse_i64(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("-2", -2), + ("-1234", -1234), + (" -1234 ", -1234), + (&i64::MIN.to_string(), i64::MIN), + (&i64::MAX.to_string(), i64::MAX), + ], + ); +} + +fn test_parse_u64(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("0", 0u64), + ("3", 3u64), + ("1234", 1234), + (&u64::MAX.to_string(), u64::MAX), + ], + ); +} + +fn test_parse_f64(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("0.0", 0.0f64), + ("3.0", 3.0f64), + ("3.1", 3.1), + ("-1.2", -1.2), + ("0.4", 0.4), + // Edge case from: + // https://github.com/serde-rs/json/issues/536#issuecomment-583714900 + ("2.638344616030823e-256", 2.638344616030823e-256), + ], + ); + + test_parse_ok( + cx, + vec![ + // With arbitrary-precision enabled, this parses as Number{"3.00"} + // but the float is Number{"3.0"} + ("3.00", 3.0f64), + ("0.4e5", 0.4e5), + ("0.4e+5", 0.4e5), + ("0.4e15", 0.4e15), + ("0.4e+15", 0.4e15), + ("0.4e-01", 0.4e-1), + (" 0.4e-01 ", 0.4e-1), + ("0.4e-001", 0.4e-1), + ("0.4e-0", 0.4e0), + ("0.00e00", 0.0), + ("0.00e+00", 0.0), + ("0.00e-00", 0.0), + ("3.5E-2147483647", 0.0), + ("0.0100000000000000000001", 0.01), + ( + &format!("{}", (i64::MIN as f64) - 1.0), + (i64::MIN as f64) - 1.0, + ), + ( + &format!("{}", (u64::MAX as f64) + 1.0), + (u64::MAX as f64) + 1.0, + ), + (&format!("{}", f64::EPSILON), f64::EPSILON), + ( + "0.0000000000000000000000000000000000000000000000000123e50", + 1.23, + ), + ("100e-777777777777777777777777777", 0.0), + ( + "1010101010101010101010101010101010101010", + 1.010_101_010_101_01e39, + ), + ( + "0.1010101010101010101010101010101010101010", + 0.101_010_101_010_101_01, + ), + ("0e1000000000000000000000000000000000000000000000", 0.0), + ( + "1000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 00000000", + 1e308, + ), + ( + "1000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + .0e8", + 1e308, + ), + ( + "1000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + e8", + 1e308, + ), + ( + "1000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000e-10", + 1e308, + ), + ], + ); +} + +fn test_value_as_f64(cx: &mut FunctionContext) { + test_parse_unusual_ok( + cx, + vec![ + ("1e1000", f64::INFINITY), // Serializes as `null` + ], + ); +} + +fn test_roundtrip_f64(cx: &mut FunctionContext) { + for &float in &[ + // Samples from quickcheck-ing roundtrip with `input: f64`. Comments + // indicate the value returned by the old deserializer. + 51.24817837550540_4, // 51.2481783755054_1 + -93.3113703768803_3, // -93.3113703768803_2 + -36.5739948427534_36, // -36.5739948427534_4 + 52.31400820410624_4, // 52.31400820410624_ + 97.4536532003468_5, // 97.4536532003468_4 + // Samples from `rng.next_u64` + `f64::from_bits` + `is_finite` filter. + 2.0030397744267762e-253, + 7.101215824554616e260, + 1.769268377902049e74, + -1.6727517818542075e58, + 3.9287532173373315e299, + ] { + let json = to_string(cx, &float).unwrap(); + let output: f64 = from_str(cx, &json).unwrap(); + assert_eq!(float, output); + } +} + +fn test_roundtrip_f32(cx: &mut FunctionContext) { + // This number has 1 ULP error if parsed via f64 and converted to f32. + // https://github.com/serde-rs/json/pull/671#issuecomment-628534468 + let float = 7.038531e-26; + let json = to_string(cx, &float).unwrap(); + let output: f32 = from_str(cx, &json).unwrap(); + assert_eq!(float, output); +} + +fn test_serialize_char(cx: &mut FunctionContext) { + let value = json!( + ({ + let mut map = BTreeMap::new(); + map.insert('c', ()); + map + }) + ); + + neon::serialize::(cx, &value) + .unwrap() + .get::(cx, "c") + .unwrap(); +} + +fn test_parse_number(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("0.0", 0.0f64), + ("3.0", 3.0f64), + ("3.1", 3.1), + ("-1.2", -1.2), + ("0.4", 0.4), + ], + ); +} + +fn test_parse_string(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("\"\"", String::new()), + ("\"foo\"", "foo".to_string()), + (" \"foo\" ", "foo".to_string()), + ("\"\\\"\"", "\"".to_string()), + ("\"\\b\"", "\x08".to_string()), + ("\"\\n\"", "\n".to_string()), + ("\"\\r\"", "\r".to_string()), + ("\"\\t\"", "\t".to_string()), + ("\"\\u12ab\"", "\u{12ab}".to_string()), + ("\"\\uAB12\"", "\u{AB12}".to_string()), + ("\"\\uD83C\\uDF95\"", "\u{1F395}".to_string()), + ], + ); +} + +fn test_parse_list(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("[]", vec![]), + ("[ ]", vec![]), + ("[null]", vec![()]), + (" [ null ] ", vec![()]), + ], + ); + + test_parse_ok(cx, vec![("[true]", vec![true])]); + + test_parse_ok( + cx, + vec![("[3,1]", vec![3u64, 1]), (" [ 3 , 1 ] ", vec![3, 1])], + ); + + test_parse_ok(cx, vec![("[[3], [1, 2]]", vec![vec![3u64], vec![1, 2]])]); + + test_parse_ok(cx, vec![("[1]", (1u64,))]); + + test_parse_ok(cx, vec![("[1, 2]", (1u64, 2u64))]); + + test_parse_ok(cx, vec![("[1, 2, 3]", (1u64, 2u64, 3u64))]); + + test_parse_ok(cx, vec![("[1, [2, 3]]", (1u64, (2u64, 3u64)))]); +} + +fn test_parse_object(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("{}", treemap!()), + ("{ }", treemap!()), + ("{\"a\":3}", treemap!("a".to_string() => 3u64)), + ("{ \"a\" : 3 }", treemap!("a".to_string() => 3)), + ( + "{\"a\":3,\"b\":4}", + treemap!("a".to_string() => 3, "b".to_string() => 4), + ), + ( + " { \"a\" : 3 , \"b\" : 4 } ", + treemap!("a".to_string() => 3, "b".to_string() => 4), + ), + ], + ); + + test_parse_ok( + cx, + vec![( + "{\"a\": {\"b\": 3, \"c\": 4}}", + treemap!( + "a".to_string() => treemap!( + "b".to_string() => 3u64, + "c".to_string() => 4 + ) + ), + )], + ); + + test_parse_ok(cx, vec![("{\"c\":null}", treemap!('c' => ()))]); +} + +fn test_parse_struct(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ( + "{ + \"inner\": [] + }", + Outer { inner: vec![] }, + ), + ( + "{ + \"inner\": [ + { \"a\": null, \"b\": 2, \"c\": [\"abc\", \"xyz\"] } + ] + }", + Outer { + inner: vec![Inner { + a: (), + b: 2, + c: vec!["abc".to_string(), "xyz".to_string()], + }], + }, + ), + ], + ); + + let v: Outer = from_str( + cx, + "[ + [ + [ null, 2, [\"abc\", \"xyz\"] ] + ] + ]", + ) + .unwrap(); + + assert_eq!( + v, + Outer { + inner: vec![Inner { + a: (), + b: 2, + c: vec!["abc".to_string(), "xyz".to_string()], + }], + } + ); +} + +fn test_parse_option(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("null", None::), + ("\"jodhpurs\"", Some("jodhpurs".to_string())), + ], + ); + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + struct Foo { + x: Option, + } + + let value: Foo = from_str(cx, "{}").unwrap(); + assert_eq!(value, Foo { x: None }); + + test_parse_ok( + cx, + vec![ + ("{\"x\": null}", Foo { x: None }), + ("{\"x\": 5}", Foo { x: Some(5) }), + ], + ); +} + +fn test_parse_enum(cx: &mut FunctionContext) { + test_parse_ok( + cx, + vec![ + ("\"Dog\"", Animal::Dog), + (" \"Dog\" ", Animal::Dog), + ( + "{\"Frog\":[\"Henry\",[]]}", + Animal::Frog("Henry".to_string(), vec![]), + ), + ( + " { \"Frog\": [ \"Henry\" , [ 349, 102 ] ] } ", + Animal::Frog("Henry".to_string(), vec![349, 102]), + ), + ( + "{\"Cat\": {\"age\": 5, \"name\": \"Kate\"}}", + Animal::Cat { + age: 5, + name: "Kate".to_string(), + }, + ), + ( + " { \"Cat\" : { \"age\" : 5 , \"name\" : \"Kate\" } } ", + Animal::Cat { + age: 5, + name: "Kate".to_string(), + }, + ), + ( + " { \"AntHive\" : [\"Bob\", \"Stuart\"] } ", + Animal::AntHive(vec!["Bob".to_string(), "Stuart".to_string()]), + ), + ], + ); + + test_parse_unusual_ok( + cx, + vec![ + ("{\"Dog\":null}", Animal::Dog), + (" { \"Dog\" : null } ", Animal::Dog), + ], + ); + + test_parse_ok( + cx, + vec![( + concat!( + "{", + " \"a\": \"Dog\",", + " \"b\": {\"Frog\":[\"Henry\", []]}", + "}" + ), + treemap!( + "a".to_string() => Animal::Dog, + "b".to_string() => Animal::Frog("Henry".to_string(), vec![]) + ), + )], + ); +} + +fn test_missing_option_field(cx: &mut FunctionContext) { + #[derive(Debug, PartialEq, Deserialize)] + struct Foo { + x: Option, + } + + let value: Foo = from_str(cx, "{}").unwrap(); + assert_eq!(value, Foo { x: None }); + + let value: Foo = from_str(cx, "{\"x\": 5}").unwrap(); + assert_eq!(value, Foo { x: Some(5) }); +} + +fn test_missing_renamed_field(cx: &mut FunctionContext) { + #[derive(Debug, PartialEq, Deserialize)] + struct Foo { + #[serde(rename = "y")] + x: Option, + } + + let value: Foo = from_str(cx, "{}").unwrap(); + assert_eq!(value, Foo { x: None }); + + let value: Foo = from_str(cx, "{\"y\": 5}").unwrap(); + assert_eq!(value, Foo { x: Some(5) }); +} + +fn test_serialize_seq_with_no_len(cx: &mut FunctionContext) { + #[derive(Clone, Debug, PartialEq)] + struct MyVec(Vec); + + impl ser::Serialize for MyVec + where + T: ser::Serialize, + { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + for elem in &self.0 { + ser::SerializeSeq::serialize_element(&mut seq, elem)?; + } + ser::SerializeSeq::end(seq) + } + } + + struct Visitor { + marker: PhantomData>, + } + + impl<'de, T> de::Visitor<'de> for Visitor + where + T: de::Deserialize<'de>, + { + type Value = MyVec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("array") + } + + #[inline] + fn visit_unit(self) -> Result, E> + where + E: de::Error, + { + Ok(MyVec(Vec::new())) + } + + #[inline] + fn visit_seq(self, mut visitor: V) -> Result, V::Error> + where + V: de::SeqAccess<'de>, + { + let mut values = Vec::new(); + + while let Some(value) = visitor.next_element()? { + values.push(value); + } + + Ok(MyVec(values)) + } + } + + impl<'de, T> de::Deserialize<'de> for MyVec + where + T: de::Deserialize<'de>, + { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_map(Visitor { + marker: PhantomData, + }) + } + } + + let vec = vec![MyVec(vec![]), MyVec(vec![])]; + let vec: MyVec> = MyVec(vec); + + test_encode_ok(cx, &[(vec, "[[],[]]")]); +} + +fn test_serialize_map_with_no_len(cx: &mut FunctionContext) { + #[derive(Clone, Debug, PartialEq)] + struct MyMap(BTreeMap); + + impl ser::Serialize for MyMap + where + K: ser::Serialize + Ord, + V: ser::Serialize, + { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + let mut map = serializer.serialize_map(None)?; + for (k, v) in &self.0 { + ser::SerializeMap::serialize_entry(&mut map, k, v)?; + } + ser::SerializeMap::end(map) + } + } + + struct Visitor { + marker: PhantomData>, + } + + impl<'de, K, V> de::Visitor<'de> for Visitor + where + K: de::Deserialize<'de> + Eq + Ord, + V: de::Deserialize<'de>, + { + type Value = MyMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("map") + } + + #[inline] + fn visit_unit(self) -> Result, E> + where + E: de::Error, + { + Ok(MyMap(BTreeMap::new())) + } + + #[inline] + fn visit_map(self, mut visitor: Visitor) -> Result, Visitor::Error> + where + Visitor: de::MapAccess<'de>, + { + let mut values = BTreeMap::new(); + + while let Some((key, value)) = visitor.next_entry()? { + values.insert(key, value); + } + + Ok(MyMap(values)) + } + } + + impl<'de, K, V> de::Deserialize<'de> for MyMap + where + K: de::Deserialize<'de> + Eq + Ord, + V: de::Deserialize<'de>, + { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_map(Visitor { + marker: PhantomData, + }) + } + } + + let mut map = BTreeMap::new(); + map.insert("a".to_owned(), MyMap(BTreeMap::new())); + map.insert("b".to_owned(), MyMap(BTreeMap::new())); + let map: MyMap<_, MyMap> = MyMap(map); + + test_encode_ok(cx, &[(map, "{\"a\":{},\"b\":{}}")]); +} + +fn test_serialize_rejects_bool_keys(cx: &mut FunctionContext) { + let map = treemap!( + true => 2, + false => 4 + ); + + let r = cx.try_catch(|cx| neon::serialize::(cx, &map)); + assert!(r.is_err()); +} + +fn test_serialize_rejects_adt_keys(cx: &mut FunctionContext) { + let map = treemap!( + Some("a") => 2, + Some("b") => 4, + None => 6 + ); + + let r = cx.try_catch(|cx| neon::serialize::(cx, &map)); + assert!(r.is_err()); +} + +fn test_bytes_ser(cx: &mut FunctionContext) { + let buf = vec![]; + let bytes = Bytes::new(&buf); + let v = neon::serialize::(cx, &bytes).unwrap(); + + assert_eq!(&buf, v.as_slice(cx)); + + let buf = vec![1, 2, 3]; + let bytes = Bytes::new(&buf); + let v = neon::serialize::(cx, &bytes).unwrap(); + + assert_eq!(&buf, v.as_slice(cx)); +} + +fn test_byte_buf_ser(cx: &mut FunctionContext) { + let bytes = ByteBuf::new(); + let v = neon::serialize::(cx, &bytes).unwrap(); + + assert_eq!(&bytes, v.as_slice(cx)); + + let bytes = ByteBuf::from(vec![1, 2, 3]); + let v = neon::serialize::(cx, &bytes).unwrap(); + + assert_eq!(&bytes, v.as_slice(cx)); +} + +fn test_byte_buf_de(cx: &mut FunctionContext) { + let bytes = ByteBuf::new(); + let buf = cx.array_buffer(0).unwrap(); + let v = neon::deserialize::(cx, buf).unwrap(); + assert_eq!(v, bytes); + + let bytes = ByteBuf::from(vec![1, 2, 3]); + let buf = JsArrayBuffer::from_slice(cx, &bytes).unwrap(); + + let v = neon::deserialize::(cx, buf).unwrap(); + assert_eq!(v, bytes); +} + +fn test_array_view_de(cx: &mut FunctionContext) { + let bytes = ByteBuf::new(); + let buf = JsUint8Array::from_slice(cx, &bytes).unwrap(); + let v = neon::deserialize::(cx, buf).unwrap(); + assert_eq!(v, bytes); + + let bytes = ByteBuf::from(vec![1, 2, 3]); + let buf = JsUint8Array::from_slice(cx, &bytes).unwrap(); + + let v = neon::deserialize::(cx, buf).unwrap(); + assert_eq!(v, bytes); +} + +fn test_byte_buf_de_multiple(cx: &mut FunctionContext) { + let a = ByteBuf::from(b"ab\nc".to_vec()); + let b = ByteBuf::from(b"cd\ne".to_vec()); + let left = vec![a, b]; + let v = neon::serialize(cx, &left).unwrap(); + let right = neon::deserialize::, JsValue, _>(cx, v).unwrap(); + + assert_eq!(left, right); +} + +fn test_serialize_rejects_int_keys(cx: &mut FunctionContext) { + let map = treemap!( + 1 => 2, + 2 => 4 + ); + + let r = cx.try_catch(|cx| neon::serialize::(cx, &map)); + assert!(r.is_err()); +} + +fn test_deny_float_key(cx: &mut FunctionContext) { + #[derive(Eq, PartialEq, Ord, PartialOrd)] + struct Float; + impl Serialize for Float { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_f32(1.0) + } + } + + // map with float key + let map = treemap!(Float => "x"); + let r = cx.try_catch(|cx| neon::serialize::(cx, &map)); + assert!(r.is_err()); +} + +fn test_effectively_string_keys(cx: &mut FunctionContext) { + #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Serialize, Deserialize)] + enum Enum { + One, + Two, + } + let map = treemap! { + Enum::One => 1, + Enum::Two => 2 + }; + let expected = r#"{"One":1,"Two":2}"#; + test_encode_ok(cx, &[(map.clone(), expected)]); + test_parse_ok(cx, vec![(expected, map)]); + + #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Serialize, Deserialize)] + struct Wrapper(String); + let map = treemap! { + Wrapper("zero".to_owned()) => 0, + Wrapper("one".to_owned()) => 1 + }; + let expected = r#"{"one":1,"zero":0}"#; + test_encode_ok(cx, &[(map.clone(), expected)]); + test_parse_ok(cx, vec![(expected, map)]); +} + +// Note: This is `Issue #220` in the `serde/serde-json` repository +fn issue_220(cx: &mut FunctionContext) { + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + enum E { + V(u8), + } + + assert_eq!(from_str::(cx, r#"{"V": 0}"#).unwrap(), E::V(0)); +} + +pub fn build_suite(mut cx: FunctionContext) -> JsResult { + let o = cx.empty_object(); + + export(&mut cx, &o, test_write_null)?; + export(&mut cx, &o, test_write_u64)?; + export(&mut cx, &o, test_write_i64)?; + export(&mut cx, &o, test_write_f64)?; + export(&mut cx, &o, test_encode_nonfinite_float_yields_null)?; + export(&mut cx, &o, test_write_str)?; + export(&mut cx, &o, test_write_bool)?; + export(&mut cx, &o, test_write_char)?; + export(&mut cx, &o, test_write_list)?; + export(&mut cx, &o, test_write_object)?; + export(&mut cx, &o, test_write_tuple)?; + export(&mut cx, &o, test_write_enum)?; + export(&mut cx, &o, test_write_option)?; + export(&mut cx, &o, test_write_newtype_struct)?; + export(&mut cx, &o, test_deserialize_number_to_untagged_enum)?; + export(&mut cx, &o, test_parse_null)?; + export(&mut cx, &o, test_parse_bool)?; + export(&mut cx, &o, test_parse_char)?; + export(&mut cx, &o, test_parse_i64)?; + export(&mut cx, &o, test_parse_u64)?; + export(&mut cx, &o, test_parse_f64)?; + export(&mut cx, &o, test_value_as_f64)?; + export(&mut cx, &o, test_roundtrip_f64)?; + export(&mut cx, &o, test_roundtrip_f32)?; + export(&mut cx, &o, test_serialize_char)?; + export(&mut cx, &o, test_parse_number)?; + export(&mut cx, &o, test_parse_string)?; + export(&mut cx, &o, test_parse_list)?; + export(&mut cx, &o, test_parse_object)?; + export(&mut cx, &o, test_parse_struct)?; + export(&mut cx, &o, test_parse_option)?; + export(&mut cx, &o, test_parse_enum)?; + export(&mut cx, &o, test_missing_option_field)?; + export(&mut cx, &o, test_missing_renamed_field)?; + export(&mut cx, &o, test_serialize_seq_with_no_len)?; + export(&mut cx, &o, test_serialize_map_with_no_len)?; + export(&mut cx, &o, test_serialize_rejects_bool_keys)?; + export(&mut cx, &o, test_serialize_rejects_adt_keys)?; + export(&mut cx, &o, test_bytes_ser)?; + export(&mut cx, &o, test_byte_buf_ser)?; + export(&mut cx, &o, test_byte_buf_de)?; + export(&mut cx, &o, test_array_view_de)?; + export(&mut cx, &o, test_byte_buf_de_multiple)?; + export(&mut cx, &o, test_serialize_rejects_int_keys)?; + export(&mut cx, &o, test_deny_float_key)?; + export(&mut cx, &o, test_effectively_string_keys)?; + export(&mut cx, &o, issue_220)?; + + Ok(o) +} + +/* +FIXME: Implement recursion limit +#[test] +fn test_stack_overflow() { + let brackets: String = iter::repeat('[') + .take(127) + .chain(iter::repeat(']').take(127)) + .collect(); + let _: Value = from_str(&brackets).unwrap(); + + let brackets = "[".repeat(129); + test_parse_err::(&[(&brackets, "recursion limit exceeded at line 1 column 128")]); +} + +#[test] +#[cfg(feature = "unbounded_depth")] +fn test_disable_recursion_limit() { + let brackets: String = iter::repeat('[') + .take(140) + .chain(iter::repeat(']').take(140)) + .collect(); + + let mut deserializer = Deserializer::from_str(&brackets); + deserializer.disable_recursion_limit(); + Value::deserialize(&mut deserializer).unwrap(); +} +*/ diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index c2bb89204..ae593c18b 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -15,6 +15,7 @@ mod js { pub mod futures; pub mod numbers; pub mod objects; + pub mod serde; pub mod strings; pub mod threads; pub mod typedarrays; @@ -393,5 +394,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("lazy_async_add", js::futures::lazy_async_add)?; cx.export_function("lazy_async_sum", js::futures::lazy_async_sum)?; + // Serde + cx.export_function("build_serde_test_suite", js::serde::build_suite)?; + Ok(()) }