From 271d06158aefbc81f8649a17dfafd29c8a274aea Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Mon, 8 Mar 2021 05:41:35 +0100 Subject: [PATCH 01/24] core: introduce serde_v8 Reduce encoding verbosity of some core functions --- core/Cargo.toml | 1 + core/bindings.rs | 143 +++++++++++++---------------------------------- 2 files changed, 39 insertions(+), 105 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index d3b70e90539682..7f521e7a8b0800 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,6 +23,7 @@ pin-project = "1.0.5" rusty_v8 = "0.21.0" serde = { version = "1.0.123", features = ["derive"] } serde_json = { version = "1.0.62", features = ["preserve_order"] } +serde_v8 = { version = "0.0.0", path = "../../serde_v8" } smallvec = "1.6.1" url = { version = "2.2.0", features = ["serde"] } diff --git a/core/bindings.rs b/core/bindings.rs index 7fb8aac70b23da..42eb013065442b 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -17,6 +17,9 @@ use std::option::Option; use url::Url; use v8::MapFnTo; +use serde::{Serialize}; +use serde_v8::{from_v8, to_v8}; + lazy_static! { pub static ref EXTERNAL_REFERENCES: v8::ExternalReferences = v8::ExternalReferences::new(&[ @@ -470,16 +473,17 @@ fn eval_context( let url = v8::Local::::try_from(args.get(1)) .map(|n| Url::from_file_path(n.to_rust_string_lossy(scope)).unwrap()); - let output = v8::Array::new(scope, 2); - /* - output[0] = result - output[1] = ErrorInfo | null - ErrorInfo = { - thrown: Error | any, - isNativeError: boolean, - isCompileError: boolean, - } - */ + #[derive(Serialize)] + struct Output<'s>(Option>, Option>); + + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct ErrInfo<'s> { + thrown: serde_v8::Value<'s>, + is_native_error: bool, + is_compile_error: bool, + }; + let tc_scope = &mut v8::TryCatch::new(scope); let name = v8::String::new( tc_scope, @@ -492,39 +496,12 @@ fn eval_context( if maybe_script.is_none() { assert!(tc_scope.has_caught()); let exception = tc_scope.exception().unwrap(); - - let js_zero = v8::Integer::new(tc_scope, 0); - let js_null = v8::null(tc_scope); - output.set(tc_scope, js_zero.into(), js_null.into()); - - let errinfo_obj = v8::Object::new(tc_scope); - - let is_compile_error_key = - v8::String::new(tc_scope, "isCompileError").unwrap(); - let is_compile_error_val = v8::Boolean::new(tc_scope, true); - errinfo_obj.set( - tc_scope, - is_compile_error_key.into(), - is_compile_error_val.into(), - ); - - let is_native_error_key = - v8::String::new(tc_scope, "isNativeError").unwrap(); - let is_native_error_val = - v8::Boolean::new(tc_scope, exception.is_native_error()); - errinfo_obj.set( - tc_scope, - is_native_error_key.into(), - is_native_error_val.into(), - ); - - let thrown_key = v8::String::new(tc_scope, "thrown").unwrap(); - errinfo_obj.set(tc_scope, thrown_key.into(), exception); - - let js_one = v8::Integer::new(tc_scope, 1); - output.set(tc_scope, js_one.into(), errinfo_obj.into()); - - rv.set(output.into()); + let output = Output(None, Some(ErrInfo{ + thrown: exception.into(), + is_native_error: exception.is_native_error(), + is_compile_error: true, + })); + rv.set(to_v8(tc_scope, output).unwrap()); return; } @@ -533,48 +510,17 @@ fn eval_context( if result.is_none() { assert!(tc_scope.has_caught()); let exception = tc_scope.exception().unwrap(); - - let js_zero = v8::Integer::new(tc_scope, 0); - let js_null = v8::null(tc_scope); - output.set(tc_scope, js_zero.into(), js_null.into()); - - let errinfo_obj = v8::Object::new(tc_scope); - - let is_compile_error_key = - v8::String::new(tc_scope, "isCompileError").unwrap(); - let is_compile_error_val = v8::Boolean::new(tc_scope, false); - errinfo_obj.set( - tc_scope, - is_compile_error_key.into(), - is_compile_error_val.into(), - ); - - let is_native_error_key = - v8::String::new(tc_scope, "isNativeError").unwrap(); - let is_native_error_val = - v8::Boolean::new(tc_scope, exception.is_native_error()); - errinfo_obj.set( - tc_scope, - is_native_error_key.into(), - is_native_error_val.into(), - ); - - let thrown_key = v8::String::new(tc_scope, "thrown").unwrap(); - errinfo_obj.set(tc_scope, thrown_key.into(), exception); - - let js_one = v8::Integer::new(tc_scope, 1); - output.set(tc_scope, js_one.into(), errinfo_obj.into()); - - rv.set(output.into()); + let output = Output(None, Some(ErrInfo{ + thrown: exception.into(), + is_native_error: exception.is_native_error(), + is_compile_error: false, + })); + rv.set(to_v8(tc_scope, output).unwrap()); return; } - - let js_zero = v8::Integer::new(tc_scope, 0); - let js_one = v8::Integer::new(tc_scope, 1); - let js_null = v8::null(tc_scope); - output.set(tc_scope, js_zero.into(), result.unwrap()); - output.set(tc_scope, js_one.into(), js_null.into()); - rv.set(output.into()); + + let output = Output(Some(result.unwrap().into()), None); + rv.set(to_v8(tc_scope, output).unwrap()); } fn encode( @@ -835,6 +781,7 @@ fn get_promise_details( args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { + let promise = match v8::Local::::try_from(args.get(0)) { Ok(val) => val, Err(_) => { @@ -842,31 +789,21 @@ fn get_promise_details( return; } }; - - let promise_details = v8::Array::new(scope, 2); + + #[derive(Serialize)] + struct PromiseDetails<'s>(u32, Option>); match promise.state() { v8::PromiseState::Pending => { - let js_zero = v8::Integer::new(scope, 0); - promise_details.set(scope, js_zero.into(), js_zero.into()); - rv.set(promise_details.into()); + rv.set(to_v8(scope, PromiseDetails(0, None)).unwrap()); } v8::PromiseState::Fulfilled => { - let js_zero = v8::Integer::new(scope, 0); - let js_one = v8::Integer::new(scope, 1); let promise_result = promise.result(scope); - promise_details.set(scope, js_zero.into(), js_one.into()); - promise_details.set(scope, js_one.into(), promise_result); - rv.set(promise_details.into()); + rv.set(to_v8(scope, PromiseDetails(1, Some(promise_result.into()))).unwrap()); } v8::PromiseState::Rejected => { - let js_zero = v8::Integer::new(scope, 0); - let js_one = v8::Integer::new(scope, 1); - let js_two = v8::Integer::new(scope, 2); let promise_result = promise.result(scope); - promise_details.set(scope, js_zero.into(), js_two.into()); - promise_details.set(scope, js_one.into(), promise_result); - rv.set(promise_details.into()); + rv.set(to_v8(scope, PromiseDetails(2, Some(promise_result.into()))).unwrap()); } } } @@ -905,14 +842,10 @@ fn get_proxy_details( } }; - let proxy_details = v8::Array::new(scope, 2); - let js_zero = v8::Integer::new(scope, 0); - let js_one = v8::Integer::new(scope, 1); let target = proxy.get_target(scope); let handler = proxy.get_handler(scope); - proxy_details.set(scope, js_zero.into(), target); - proxy_details.set(scope, js_one.into(), handler); - rv.set(proxy_details.into()); + let p: (serde_v8::Value, serde_v8::Value) = (target.into(), handler.into()); + rv.set(to_v8(scope, p).unwrap()); } fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef) { From 9953b72586e7aa2b49bc62eaa06e3a3b61325c20 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 10 Mar 2021 01:28:50 +0100 Subject: [PATCH 02/24] tweaks --- core/bindings.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/bindings.rs b/core/bindings.rs index 42eb013065442b..a44b2e4f1ed4b8 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -17,8 +17,8 @@ use std::option::Option; use url::Url; use v8::MapFnTo; -use serde::{Serialize}; -use serde_v8::{from_v8, to_v8}; +use serde::Serialize; +use serde_v8::to_v8; lazy_static! { pub static ref EXTERNAL_REFERENCES: v8::ExternalReferences = @@ -482,7 +482,7 @@ fn eval_context( thrown: serde_v8::Value<'s>, is_native_error: bool, is_compile_error: bool, - }; + } let tc_scope = &mut v8::TryCatch::new(scope); let name = v8::String::new( @@ -781,7 +781,6 @@ fn get_promise_details( args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let promise = match v8::Local::::try_from(args.get(0)) { Ok(val) => val, Err(_) => { From 32b5674911f3e822cb9e9eb648b4fe9d7bdc3733 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 18 Mar 2021 22:22:19 +0100 Subject: [PATCH 03/24] Add trimmed serde_v8 to tree Mainly removed ABI stuff and benches (used nightly toolchain) --- Cargo.lock | 10 + Cargo.toml | 3 +- core/Cargo.toml | 2 +- serde_v8/Cargo.toml | 16 ++ serde_v8/README.md | 14 + serde_v8/examples/basic.rs | 55 ++++ serde_v8/src/de.rs | 396 ++++++++++++++++++++++++++ serde_v8/src/error.rs | 44 +++ serde_v8/src/lib.rs | 11 + serde_v8/src/magic.rs | 70 +++++ serde_v8/src/payload.rs | 33 +++ serde_v8/src/ser.rs | 569 +++++++++++++++++++++++++++++++++++++ serde_v8/src/utils.rs | 30 ++ serde_v8/tests/de.rs | 59 ++++ serde_v8/tests/lib.rs | 1 + serde_v8/tests/magic.rs | 53 ++++ 16 files changed, 1364 insertions(+), 2 deletions(-) create mode 100644 serde_v8/Cargo.toml create mode 100644 serde_v8/README.md create mode 100644 serde_v8/examples/basic.rs create mode 100644 serde_v8/src/de.rs create mode 100644 serde_v8/src/error.rs create mode 100644 serde_v8/src/lib.rs create mode 100644 serde_v8/src/magic.rs create mode 100644 serde_v8/src/payload.rs create mode 100644 serde_v8/src/ser.rs create mode 100644 serde_v8/src/utils.rs create mode 100644 serde_v8/tests/de.rs create mode 100644 serde_v8/tests/lib.rs create mode 100644 serde_v8/tests/magic.rs diff --git a/Cargo.lock b/Cargo.lock index 1686c6d0c12534..a89a82a2abf982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,6 +567,7 @@ dependencies = [ "rusty_v8", "serde", "serde_json", + "serde_v8", "smallvec", "tokio", "url", @@ -2738,6 +2739,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_v8" +version = "0.0.0" +dependencies = [ + "rusty_v8", + "serde", + "serde_json", +] + [[package]] name = "sha-1" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index e89ca19b7bee56..ef7549164f5bcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "cli", "core", "runtime", + "serde_v8", "test_plugin", "test_util", "op_crates/crypto", @@ -42,4 +43,4 @@ opt-level = 3 [profile.release.package.async-compression] opt-level = 3 [profile.release.package.brotli-decompressor] -opt-level = 3 \ No newline at end of file +opt-level = 3 diff --git a/core/Cargo.toml b/core/Cargo.toml index 7f521e7a8b0800..99db04214b87b9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,7 +23,7 @@ pin-project = "1.0.5" rusty_v8 = "0.21.0" serde = { version = "1.0.123", features = ["derive"] } serde_json = { version = "1.0.62", features = ["preserve_order"] } -serde_v8 = { version = "0.0.0", path = "../../serde_v8" } +serde_v8 = { version = "0.0.0", path = "../serde_v8" } smallvec = "1.6.1" url = { version = "2.2.0", features = ["serde"] } diff --git a/serde_v8/Cargo.toml b/serde_v8/Cargo.toml new file mode 100644 index 00000000000000..71cee640f101dc --- /dev/null +++ b/serde_v8/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "serde_v8" +version = "0.0.0" +authors = ["Aaron O'Mullan "] +edition = "2018" + + +[dependencies] +serde = { version = "1.0.123", features = ["derive"] } +rusty_v8 = "0.21.0" + +[dev-dependencies] +serde_json = "1.0.62" + +[[example]] +name = "basic" diff --git a/serde_v8/README.md b/serde_v8/README.md new file mode 100644 index 00000000000000..6eaca3f98828de --- /dev/null +++ b/serde_v8/README.md @@ -0,0 +1,14 @@ +# serde_v8 + +Serde support for (rusty_)v8 + +**WIP:** see [denoland/deno#9540]( https://github.com/denoland/deno/issues/9540) + +## TODO + +- [ ] Experiment with KeyCache to optimize struct keys +- [ ] Experiment with external v8 strings +- [ ] Explore using [json-stringifier.cc](https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/json/json-stringifier.cc)'s fast-paths for arrays +- [ ] Improve tests to test parity with `serde_json` (should be mostly interchangeable) +- [ ] Consider a `Payload` type that's deserializable by itself (holds scope & value) +- [ ] Ensure we return errors instead of panicking on `.unwrap()`s diff --git a/serde_v8/examples/basic.rs b/serde_v8/examples/basic.rs new file mode 100644 index 00000000000000..1d863ee5c421ad --- /dev/null +++ b/serde_v8/examples/basic.rs @@ -0,0 +1,55 @@ +use rusty_v8 as v8; +use serde_v8; + +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct MathOp { + pub a: u64, + pub b: u64, + pub operator: Option, +} + +fn main() { + let platform = v8::new_default_platform().unwrap(); + v8::V8::initialize_platform(platform); + v8::V8::initialize(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + fn exec<'s>(scope: &mut v8::HandleScope<'s>, src: &str) -> v8::Local<'s, v8::Value> { + let code = v8::String::new(scope, src).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + script.run(scope).unwrap() + } + + let v = exec(scope, "32"); + let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); + println!("x32 = {}", x32); + + let v = exec(scope, "({a: 1, b: 3, c: 'ignored'})"); + let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); + println!("mop = {:?}", mop); + + let v = exec(scope, "[1,2,3,4,5]"); + let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); + println!("arr = {:?}", arr); + + let v = exec(scope, "['hello', 'world']"); + let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); + println!("hi = {:?}", hi); + + let v: v8::Local = v8::Number::new(scope, 12345.0).into(); + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + println!("x = {}", x); + } + + unsafe { + v8::V8::dispose(); + } + v8::V8::shutdown_platform(); +} diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs new file mode 100644 index 00000000000000..8ebadd988b66a9 --- /dev/null +++ b/serde_v8/src/de.rs @@ -0,0 +1,396 @@ +use rusty_v8 as v8; +use serde::de::{self, Visitor}; +use serde::Deserialize; + +use std::collections::HashMap; +use std::convert::TryFrom; + +use crate::error::{Error, Result}; +use crate::payload::ValueType; + +use crate::magic; + +pub struct Deserializer<'a, 'b, 's> { + input: v8::Local<'a, v8::Value>, + scope: &'b mut v8::HandleScope<'s>, + _key_cache: Option<&'b mut KeyCache>, +} +pub type KeyCache = HashMap<&'static str, v8::Global>; + +impl<'a, 'b, 's> Deserializer<'a, 'b, 's> { + pub fn new( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, + key_cache: Option<&'b mut KeyCache>, + ) -> Self { + Deserializer { + input, + scope, + _key_cache: key_cache, + } + } +} + +// from_v8 deserializes a v8::Value into a Deserializable / rust struct +pub fn from_v8<'de, 'a, 'b, 's, T>( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, +) -> Result +where + T: Deserialize<'de>, +{ + let mut deserializer = Deserializer::new(scope, input, None); + let t = T::deserialize(&mut deserializer)?; + Ok(t) +} + +// like from_v8 except accepts a KeyCache to optimize struct key decoding +pub fn from_v8_cached<'de, 'a, 'b, 's, T>( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, + key_cache: &mut KeyCache, +) -> Result +where + T: Deserialize<'de>, +{ + let mut deserializer = Deserializer::new(scope, input, Some(key_cache)); + let t = T::deserialize(&mut deserializer)?; + Ok(t) +} + +macro_rules! wip { + ($method:ident) => { + fn $method(self, _v: V) -> Result + where + V: Visitor<'de>, + { + unimplemented!() + } + }; +} + +macro_rules! deserialize_signed { + ($dmethod:ident, $vmethod:ident, $t:tt) => { + fn $dmethod(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.$vmethod(self.input.integer_value(&mut self.scope).unwrap() as $t) + } + }; +} + +impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> for &'x mut Deserializer<'a, 'b, 's> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match ValueType::from_v8(self.input) { + ValueType::Null => self.deserialize_unit(visitor), + ValueType::Bool => self.deserialize_bool(visitor), + ValueType::Number => self.deserialize_f64(visitor), + ValueType::String => self.deserialize_string(visitor), + ValueType::Array => self.deserialize_seq(visitor), + ValueType::Object => self.deserialize_map(visitor), + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_boolean() { + visitor.visit_bool(self.input.boolean_value(&mut self.scope)) + } else { + Err(Error::ExpectedBoolean) + } + } + + deserialize_signed!(deserialize_i8, visit_i8, i8); + deserialize_signed!(deserialize_i16, visit_i16, i16); + deserialize_signed!(deserialize_i32, visit_i32, i32); + deserialize_signed!(deserialize_i64, visit_i64, i64); + // TODO: maybe handle unsigned by itself ? + deserialize_signed!(deserialize_u8, visit_u8, u8); + deserialize_signed!(deserialize_u16, visit_u16, u16); + deserialize_signed!(deserialize_u32, visit_u32, u32); + deserialize_signed!(deserialize_u64, visit_u64, u64); + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_f32(self.input.number_value(&mut self.scope).unwrap() as f32) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_f64(self.input.number_value(&mut self.scope).unwrap()) + } + + wip!(deserialize_char); + wip!(deserialize_str); + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_string() { + let string = self.input.to_rust_string_lossy(self.scope); + visitor.visit_string(string) + } else { + Err(Error::ExpectedString) + } + } + + wip!(deserialize_bytes); + wip!(deserialize_byte_buf); + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_null_or_undefined() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_null() { + visitor.visit_unit() + } else { + Err(Error::ExpectedNull) + } + } + + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + // As is done here, serializers are encouraged to treat newtype structs as + // insignificant wrappers around the data they contain. That means not + // parsing anything other than the contained value. + fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let arr = v8::Local::::try_from(self.input).unwrap(); + let len = arr.length(); + let obj = v8::Local::::from(arr); + let seq = SeqAccess { + pos: 0, + len, + obj, + scope: self.scope, + }; + visitor.visit_seq(seq) + } + + // Like deserialize_seq except it prefers tuple's length over input array's length + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + // TODO: error on length mismatch + let obj = v8::Local::::try_from(self.input).unwrap(); + let seq = SeqAccess { + pos: 0, + len: len as u32, + obj, + scope: self.scope, + }; + visitor.visit_seq(seq) + } + + // Tuple structs look just like sequences in JSON. + fn deserialize_tuple_struct( + self, + _name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_tuple(len, visitor) + } + + wip!(deserialize_map); + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + // Magic for serde_v8::magic::Value, to passthrough v8::Value + // TODO: ensure this is cross-platform and there's no alternative + if name == magic::NAME { + let mv = magic::Value { v8_value: self.input }; + let hack: u64 = unsafe { std::mem::transmute(mv) }; + return visitor.visit_u64(hack); + } + + // Regular struct + let obj = v8::Local::::try_from(self.input).unwrap(); + let map = ObjectAccess { + fields: fields, + obj: obj, + pos: 0, + scope: self.scope, + _cache: None, + }; + + visitor.visit_map(map) + } + + fn deserialize_enum( + self, + _name: &str, + _variants: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + unimplemented!(); + } + + // An identifier in Serde is the type that identifies a field of a struct or + // the variant of an enum. In JSON, struct fields and enum variants are + // represented as strings. In other formats they may be represented as + // numeric indices. + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_none() + } +} + +struct ObjectAccess<'a, 'b, 's> { + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + fields: &'static [&'static str], + pos: usize, + _cache: Option<&'b mut KeyCache>, +} + +fn str_deserializer(s: &str) -> de::value::StrDeserializer { + de::IntoDeserializer::into_deserializer(s) +} + +impl<'de, 'a, 'b, 's> de::MapAccess<'de> for ObjectAccess<'a, 'b, 's> { + type Error = Error; + + fn next_key_seed>(&mut self, seed: K) -> Result> { + Ok(match self.fields.get(self.pos) { + Some(&field) => Some(seed.deserialize(str_deserializer(field))?), + None => None, + }) + } + + fn next_value_seed>(&mut self, seed: V) -> Result { + if self.pos >= self.fields.len() { + return Err(Error::LengthMismatch) + } + let field = self.fields[self.pos]; + self.pos += 1; + let key = v8_struct_key(self.scope, field).into(); + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + seed.deserialize(&mut deserializer) + } + + fn next_entry_seed, V: de::DeserializeSeed<'de>>( + &mut self, + kseed: K, + vseed: V, + ) -> Result> { + if self.pos >= self.fields.len() { + return Ok(None) + } + let field = self.fields[self.pos]; + self.pos += 1; + Ok( + Some((kseed.deserialize(str_deserializer(field))?, { + let key = v8_struct_key(self.scope, field).into(); + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + vseed.deserialize(&mut deserializer)? + })) + ) + } +} + +// creates an optimized v8::String for a struct field +// TODO: experiment with external strings +// TODO: evaluate if own KeyCache is better than v8's dedupe +fn v8_struct_key<'s>(scope: &mut v8::HandleScope<'s>, field: &'static str) -> v8::Local<'s, v8::String> { + // Internalized v8 strings are significantly faster than "normal" v8 strings + // since v8 deduplicates re-used strings minimizing new allocations + // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 + v8::String::new_from_utf8(scope, field.as_ref(), v8::NewStringType::Internalized).unwrap().into() + + // TODO: consider external strings later + // right now non-deduped external strings (without KeyCache) + // are slower than the deduped internalized strings by ~2.5x + // since they're a new string in v8's eyes and needs to be hashed, etc... + // v8::String::new_external_onebyte_static(scope, field).unwrap() +} + +struct SeqAccess<'a, 'b, 's> { + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + len: u32, + pos: u32, +} + +impl<'de> de::SeqAccess<'de> for SeqAccess<'_, '_, '_> { + type Error = Error; + + fn next_element_seed>( + &mut self, + seed: T, + ) -> Result> { + let pos = self.pos; + self.pos += 1; + + if pos < self.len { + let val = self.obj.get_index(self.scope, pos).unwrap(); + let mut deserializer = Deserializer::new(self.scope, val, None); + Ok(Some(seed.deserialize(&mut deserializer)?)) + } else { + Ok(None) + } + } +} diff --git a/serde_v8/src/error.rs b/serde_v8/src/error.rs new file mode 100644 index 00000000000000..5ea749ebde7229 --- /dev/null +++ b/serde_v8/src/error.rs @@ -0,0 +1,44 @@ +use std; +use std::fmt::{self, Display}; + +use serde::{de, ser}; + +pub type Result = std::result::Result; + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + Message(String), + + ExpectedBoolean, + ExpectedInteger, + ExpectedString, + ExpectedNull, + ExpectedArray, + ExpectedMap, + ExpectedEnum, + + LengthMismatch, +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Message(msg) => formatter.write_str(msg), + _ => formatter.write_str("serde_v8 error: TODO"), + } + } +} + +impl std::error::Error for Error {} diff --git a/serde_v8/src/lib.rs b/serde_v8/src/lib.rs new file mode 100644 index 00000000000000..a033f527bde6bc --- /dev/null +++ b/serde_v8/src/lib.rs @@ -0,0 +1,11 @@ +mod de; +mod error; +mod magic; +mod payload; +mod ser; +pub mod utils; + +pub use de::{from_v8, from_v8_cached, Deserializer, KeyCache}; +pub use error::{Error, Result}; +pub use magic::{Value}; +pub use ser::{to_v8, Serializer}; diff --git a/serde_v8/src/magic.rs b/serde_v8/src/magic.rs new file mode 100644 index 00000000000000..8e9fa0931a766f --- /dev/null +++ b/serde_v8/src/magic.rs @@ -0,0 +1,70 @@ +use rusty_v8 as v8; +use serde; + +use std::fmt; +use std::marker::PhantomData; + +pub const FIELD: &'static str = "$__v8_magic_value"; +pub const NAME: &'static str = "$__v8_magic_Value"; + +pub struct Value<'s> { + pub v8_value: v8::Local<'s, v8::Value> +} + +impl<'s> From> for Value<'s> { + fn from(v8_value: v8::Local<'s, v8::Value>) -> Self { + Self { v8_value } + } +} + +impl<'s> From> for v8::Local<'s, v8::Value> { + fn from(v: Value<'s>) -> Self { + v.v8_value + } +} + +impl serde::Serialize for Value<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + let mut s = serializer.serialize_struct(NAME, 1)?; + let mv = Value { v8_value: self.v8_value }; + let hack: u64 = unsafe { std::mem::transmute(mv) }; + s.serialize_field(FIELD, &hack)?; + s.end() + } +} + +impl<'de, 's> serde::Deserialize<'de> for Value<'s> { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor<'s> { + p1: PhantomData<&'s ()>, + } + + impl<'de, 's> serde::de::Visitor<'de> for ValueVisitor<'s> { + type Value = Value<'s>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a v8::Value") + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + let mv: Value<'s> = unsafe { std::mem::transmute(v) }; + Ok(mv) + } + } + + static FIELDS: [&str; 1] = [FIELD]; + let visitor = ValueVisitor { p1: PhantomData }; + deserializer.deserialize_struct(NAME, &FIELDS, visitor) + } +} diff --git a/serde_v8/src/payload.rs b/serde_v8/src/payload.rs new file mode 100644 index 00000000000000..3f6bbbb6b14dca --- /dev/null +++ b/serde_v8/src/payload.rs @@ -0,0 +1,33 @@ +use rusty_v8 as v8; + +// TODO: maybe add a Payload type that holds scope & v8::Value +// so it can implement Deserialize by itself + +// Classifies v8::Values into sub-types +pub enum ValueType { + Null, + Bool, + Number, + String, + Array, + Object, +} + +impl ValueType { + pub fn from_v8(v: v8::Local) -> ValueType { + if v.is_boolean() { + return Self::Bool; + } else if v.is_number() { + return Self::Number; + } else if v.is_string() { + return Self::String; + } else if v.is_array() { + return Self::Array; + } else if v.is_object() { + return Self::Object; + } else if v.is_null_or_undefined() { + return Self::Null; + } + panic!("serde_v8: unknown ValueType for v8::Value") + } +} diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs new file mode 100644 index 00000000000000..f8c48671d6c984 --- /dev/null +++ b/serde_v8/src/ser.rs @@ -0,0 +1,569 @@ +use rusty_v8 as v8; +use serde::ser; +use serde::ser::{Impossible, Serialize}; + +use std::rc::Rc; +use std::cell::{RefCell}; + +use crate::error::{Error, Result}; +use crate::magic; + +type JsValue<'s> = v8::Local<'s, v8::Value>; +type JsResult<'s> = Result>; + +type ScopePtr<'a, 'b> = Rc>>; + +pub fn to_v8<'a, 'b, T>(scope: &mut v8::HandleScope<'a>, input: T) -> JsResult<'a> +where + T: Serialize, +{ + let subscope = v8::EscapableHandleScope::new(scope); + let scopeptr = Rc::new(RefCell::new(subscope)); + let serializer = Serializer::new(scopeptr.clone()); + let x = input.serialize(serializer)?; + let x = scopeptr.clone().borrow_mut().escape(x); + + Ok(x) + +} + +/// Wraps other serializers into an enum tagged variant form. +/// Uses {"Variant": ...payload...} for compatibility with serde-json. +pub struct VariantSerializer<'a, 'b, S> { + variant: &'static str, + inner: S, + scope: ScopePtr<'a, 'b>, +} + +impl<'a, 'b, S> VariantSerializer<'a, 'b, S> { + pub fn new(scope: ScopePtr<'a, 'b>, variant: &'static str, inner: S) -> Self { + Self { scope, variant, inner } + } + + fn end(self, inner: impl FnOnce(S) -> JsResult<'a>) -> JsResult<'a> { + let value = inner(self.inner)?; + let scope = &mut *self.scope.borrow_mut(); + let obj = v8::Object::new(scope); + let key = v8_struct_key(scope, self.variant).into(); + obj.set(scope, key, value); + Ok(obj.into()) + } +} + +impl<'a, 'b, S> ser::SerializeTupleVariant for VariantSerializer<'a, 'b, S> +where S: ser::SerializeTupleStruct, Error = Error> +{ + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> { + self.inner.serialize_field(value) + } + + fn end(self) -> JsResult<'a> { + self.end(S::end) + } +} + +impl<'a, 'b, S> ser::SerializeStructVariant for VariantSerializer<'a, 'b, S> +where S: ser::SerializeStruct, Error = Error> +{ + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + self.inner.serialize_field(key, value) + } + + fn end(self) -> JsResult<'a> { + self.end(S::end) + } +} + +pub struct ArraySerializer<'a, 'b> { + // serializer: Serializer<'a, 'b>, + pending: Vec>, + scope: ScopePtr<'a, 'b>, +} + +impl<'a, 'b> ArraySerializer<'a, 'b> { + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + // let serializer = Serializer::new(scope); + Self { + scope, + // serializer, + pending: vec![], + } + } +} + +impl<'a, 'b> ser::SerializeSeq for ArraySerializer<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> { + let x = value.serialize(Serializer::new(self.scope.clone()))?; + self.pending.push(x); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + let elements = self.pending.iter().as_slice(); + let scope = &mut *self.scope.borrow_mut(); + let arr = v8::Array::new_with_elements(scope, elements); + Ok(arr.into()) + } +} + +impl<'a, 'b> ser::SerializeTuple for ArraySerializer<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> JsResult<'a> { + ser::SerializeSeq::end(self) + } +} + +impl<'a, 'b> ser::SerializeTupleStruct for ArraySerializer<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> { + ser::SerializeTuple::serialize_element(self, value) + } + + fn end(self) -> JsResult<'a> { + ser::SerializeTuple::end(self) + } +} + +pub struct ObjectSerializer<'a, 'b> { + scope: ScopePtr<'a, 'b>, + obj: v8::Local<'a, v8::Object>, +} + +impl<'a, 'b> ObjectSerializer<'a, 'b> { + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + let obj = v8::Object::new(&mut *scope.borrow_mut()); + Self { + scope, + obj, + } + } +} + +impl<'a, 'b> ser::SerializeStruct for ObjectSerializer<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + let value = value.serialize(Serializer::new(self.scope.clone()))?; + let scope = &mut *self.scope.borrow_mut(); + let key = v8_struct_key(scope, key).into(); + self.obj.set(scope, key, value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + Ok(self.obj.into()) + } +} + +pub struct MagicSerializer<'a, 'b> { + scope: ScopePtr<'a, 'b>, + v8_value: Option>, +} + +impl<'a, 'b> ser::SerializeStruct for MagicSerializer<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + if key != magic::FIELD { + unreachable!(); + } + let v8_value = value.serialize(MagicTransmuter{ _scope: self.scope.clone() })?; + self.v8_value = Some(v8_value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + Ok(self.v8_value.unwrap()) + } +} + +// Dispatches between magic and regular struct serializers +pub enum StructSerializers<'a, 'b> { + Magic(MagicSerializer<'a, 'b>), + Regular(ObjectSerializer<'a, 'b>) +} + +impl<'a, 'b> ser::SerializeStruct for StructSerializers<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + match self { + StructSerializers::Magic(s) => s.serialize_field(key, value), + StructSerializers::Regular(s) => s.serialize_field(key, value), + } + } + + fn end(self) -> JsResult<'a> { + match self { + StructSerializers::Magic(s) => s.end(), + StructSerializers::Regular(s) => s.end(), + } + } +} + +#[derive(Clone)] +pub struct Serializer<'a, 'b> { + scope: ScopePtr<'a, 'b>, +} + +impl<'a, 'b> Serializer<'a, 'b> { + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + Serializer { scope } + } +} + +macro_rules! forward_to { + ($($name:ident($ty:ty, $to:ident, $lt:lifetime);)*) => { + $(fn $name(self, v: $ty) -> JsResult<$lt> { + self.$to(v as _) + })* + }; +} + +impl<'a, 'b> ser::Serializer for Serializer<'a, 'b> { + type Ok = v8::Local<'a, v8::Value>; + type Error = Error; + + type SerializeSeq = ArraySerializer<'a, 'b>; + type SerializeTuple = ArraySerializer<'a, 'b>; + type SerializeTupleStruct = ArraySerializer<'a, 'b>; + type SerializeTupleVariant = VariantSerializer<'a, 'b, ArraySerializer<'a, 'b>>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = StructSerializers<'a, 'b>; + type SerializeStructVariant = VariantSerializer<'a, 'b, StructSerializers<'a, 'b>>; + + forward_to! { + serialize_i8(i8, serialize_i32, 'a); + serialize_i16(i16, serialize_i32, 'a); + + serialize_u8(u8, serialize_u32, 'a); + serialize_u16(u16, serialize_u32, 'a); + + serialize_f32(f32, serialize_f64, 'a); + serialize_u64(u64, serialize_f64, 'a); + serialize_i64(i64, serialize_f64, 'a); + } + + fn serialize_i32(self, v: i32) -> JsResult<'a> { + Ok(v8::Integer::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_u32(self, v: u32) -> JsResult<'a> { + Ok(v8::Integer::new_from_unsigned(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_f64(self, v: f64) -> JsResult<'a> { + Ok(v8::Number::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_bool(self, v: bool) -> JsResult<'a> { + Ok(v8::Boolean::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_char(self, _v: char) -> JsResult<'a> { + unimplemented!(); + } + + fn serialize_str(self, v: &str) -> JsResult<'a> { + v8::String::new(&mut self.scope.borrow_mut(), v).map(|v| v.into()).ok_or(Error::ExpectedString) + } + + fn serialize_bytes(self, _v: &[u8]) -> JsResult<'a> { + // TODO: investigate using Uint8Arrays + unimplemented!() + } + + fn serialize_none(self) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + fn serialize_some(self, value: &T) -> JsResult<'a> { + value.serialize(self) + } + + fn serialize_unit(self) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + /// For compatibility with serde-json, serialises unit variants as "Variant" strings. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> JsResult<'a> { + Ok(v8_struct_key(&mut self.scope.borrow_mut(), variant).into()) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> JsResult<'a> { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> JsResult<'a> { + let scope = self.scope.clone(); + let x = self.serialize_newtype_struct(variant, value)?; + VariantSerializer::new(scope, variant, x).end(Ok) + } + + /// Serialises any Rust iterable into a JS Array + fn serialize_seq(self, _len: Option) -> Result { + Ok(ArraySerializer::new(self.scope)) + } + + 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_tuple(len) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(VariantSerializer::new( + self.scope.clone(), + variant, + self.serialize_tuple_struct(variant, len)?, + )) + } + + fn serialize_map(self, _len: Option) -> Result { + // TODO: serialize Maps (HashMap or BTreeMap) to v8 objects, + // ideally JS Maps since they're lighter and better suited for K/V data + // only allow certain keys (e.g: strings and numbers) + unimplemented!() + } + + /// Serialises Rust typed structs into plain JS objects. + fn serialize_struct(self, name: &'static str, _len: usize) -> Result { + if name == magic::NAME { + let m = MagicSerializer { scope: self.scope, v8_value: None }; + return Ok(StructSerializers::Magic(m)); + } + let o = ObjectSerializer::new(self.scope); + Ok(StructSerializers::Regular(o)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let scope = self.scope.clone(); + let x = self.serialize_struct(variant, len)?; + Ok(VariantSerializer::new( + scope, + variant, + x, + )) + } +} + +macro_rules! not_reachable { + ($($name:ident($ty:ty, $lt:lifetime);)*) => { + $(fn $name(self, _v: $ty) -> JsResult<$lt> { + unreachable!(); + })* + }; +} + +/// A VERY hackish serde::Serializer +/// that exists solely to transmute a u64 to a serde_v8::Value +struct MagicTransmuter<'a, 'b>{ + _scope: ScopePtr<'a, 'b>, +} + +impl<'a, 'b> ser::Serializer for MagicTransmuter<'a, 'b> { + type Ok = v8::Local<'a, v8::Value>; + type Error = Error; + + type SerializeSeq = Impossible, Error>; + type SerializeTuple = Impossible, Error>; + type SerializeTupleStruct = Impossible, Error>; + type SerializeTupleVariant = Impossible, Error>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = Impossible, Error>; + type SerializeStructVariant = Impossible, Error>; + + // The only serialize method for this hackish struct + fn serialize_u64(self, v: u64) -> JsResult<'a> { + let mv: magic::Value = unsafe { std::mem::transmute(v) }; + Ok(mv.v8_value) + } + + not_reachable! { + serialize_i8(i8, 'a); + serialize_i16(i16, 'a); + serialize_i32(i32, 'a); + serialize_i64(i64, 'a); + serialize_u8(u8, 'a); + serialize_u16(u16, 'a); + serialize_u32(u32, 'a); + // serialize_u64(u64, 'a); the chosen one + serialize_f32(f32, 'a); + serialize_f64(f64, 'a); + serialize_bool(bool, 'a); + serialize_char(char, 'a); + serialize_str(&str, 'a); + serialize_bytes(&[u8], 'a); + } + + fn serialize_none(self) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_some(self, _value: &T) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_unit(self) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { + unreachable!(); + } + + /// For compatibility with serde-json, serialises unit variants as "Variant" strings. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + _value: &T, + ) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> JsResult<'a> { + unreachable!(); + } + fn serialize_seq(self, _len: Option) -> Result { + unreachable!(); + } + + fn serialize_tuple(self, _len: usize) -> Result { + unreachable!(); + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_map(self, _len: Option) -> Result { + unreachable!(); + } + + /// Serialises Rust typed structs into plain JS objects. + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + unreachable!(); + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } +} + +// creates an optimized v8::String for a struct field +// TODO: experiment with external strings +// TODO: evaluate if own KeyCache is better than v8's dedupe +fn v8_struct_key<'s>(scope: &mut v8::HandleScope<'s>, field: &'static str) -> v8::Local<'s, v8::String> { + // Internalized v8 strings are significantly faster than "normal" v8 strings + // since v8 deduplicates re-used strings minimizing new allocations + // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 + v8::String::new_from_utf8(scope, field.as_ref(), v8::NewStringType::Internalized).unwrap().into() +} diff --git a/serde_v8/src/utils.rs b/serde_v8/src/utils.rs new file mode 100644 index 00000000000000..f564ca5c97c3ac --- /dev/null +++ b/serde_v8/src/utils.rs @@ -0,0 +1,30 @@ +use rusty_v8 as v8; +use std::sync::Once; + +pub fn js_exec<'s>(scope: &mut v8::HandleScope<'s>, src: &str) -> v8::Local<'s, v8::Value> { + let code = v8::String::new(scope, src).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + script.run(scope).unwrap() +} + +pub fn v8_init() { + let platform = v8::new_default_platform().unwrap(); + v8::V8::initialize_platform(platform); + v8::V8::initialize(); +} + +pub fn v8_shutdown() { + unsafe { + v8::V8::dispose(); + } + v8::V8::shutdown_platform(); +} + +pub fn v8_do(f: impl FnOnce()) { + static V8_INIT: Once = Once::new(); + V8_INIT.call_once(|| { + v8_init(); + }); + f(); + // v8_shutdown(); +} diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs new file mode 100644 index 00000000000000..3079315315a774 --- /dev/null +++ b/serde_v8/tests/de.rs @@ -0,0 +1,59 @@ +use rusty_v8 as v8; +use serde_v8; + +use serde::Deserialize; + +use serde_v8::utils::{js_exec, v8_init, v8_shutdown}; +//::{v8_init, v8_shutdown}; + +#[derive(Debug, Deserialize, PartialEq)] +struct MathOp { + pub a: u64, + pub b: u64, + pub operator: Option, +} + +#[test] +fn de_basic() { + v8_init(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + let v = js_exec(scope, "true"); + let b: bool = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(b, true); + + let v = js_exec(scope, "32"); + let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(x32, 32); + + let v = js_exec(scope, "({a: 1, b: 3, c: 'ignored'})"); + let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!( + mop, + MathOp { + a: 1, + b: 3, + operator: None + } + ); + + let v = js_exec(scope, "[1,2,3,4,5]"); + let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(arr, vec![1, 2, 3, 4, 5]); + + let v = js_exec(scope, "['hello', 'world']"); + let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(hi, vec!["hello", "world"]); + + let v: v8::Local = v8::Number::new(scope, 12345.0).into(); + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(x, 12345.0); + } + + v8_shutdown(); +} diff --git a/serde_v8/tests/lib.rs b/serde_v8/tests/lib.rs new file mode 100644 index 00000000000000..8208deb41bd77e --- /dev/null +++ b/serde_v8/tests/lib.rs @@ -0,0 +1 @@ +mod de; diff --git a/serde_v8/tests/magic.rs b/serde_v8/tests/magic.rs new file mode 100644 index 00000000000000..c344a59914ffe1 --- /dev/null +++ b/serde_v8/tests/magic.rs @@ -0,0 +1,53 @@ +use rusty_v8 as v8; +use serde_v8; + +use serde::{Deserialize, Serialize}; + +use std::convert::TryFrom; +use serde_v8::utils::{js_exec, v8_init, v8_shutdown}; + +#[derive(Deserialize)] +struct MagicOp<'s> { + pub a: u64, + pub b: u64, + pub c: serde_v8::Value<'s>, + pub operator: Option, +} + +#[derive(Serialize)] +struct MagicContainer<'s> { + pub magic: bool, + pub contains: serde_v8::Value<'s>, +} + + +#[test] +fn magic_basic() { + v8_init(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + // Decode + let v = js_exec(scope, "({a: 1, b: 3, c: 'abracadabra'})"); + let mop: MagicOp = serde_v8::from_v8(scope, v).unwrap(); + // Check string + let v8_value: v8::Local = mop.c.into(); + let vs = v8::Local::::try_from(v8_value).unwrap(); + let s = vs.to_rust_string_lossy(scope); + assert_eq!(s, "abracadabra"); + + // Encode + let container = MagicContainer { magic: true, contains: v.into() }; + let vc = serde_v8::to_v8(scope, container).unwrap(); + // JSON stringify & check + let json = v8::json::stringify(scope, vc).unwrap(); + let s2 = json.to_rust_string_lossy(scope); + assert_eq!(s2, r#"{"magic":true,"contains":{"a":1,"b":3,"c":"abracadabra"}}"#); + } + + v8_shutdown(); +} From 9fc710bb06aa08cb9efbb973c72670a6968a82de Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 18 Mar 2021 22:23:57 +0100 Subject: [PATCH 04/24] fmt --- core/bindings.rs | 42 +- serde_v8/examples/basic.rs | 87 ++-- serde_v8/src/de.rs | 670 +++++++++++++------------- serde_v8/src/error.rs | 44 +- serde_v8/src/lib.rs | 2 +- serde_v8/src/magic.rs | 86 ++-- serde_v8/src/payload.rs | 42 +- serde_v8/src/ser.rs | 963 +++++++++++++++++++------------------ serde_v8/src/utils.rs | 37 +- serde_v8/tests/de.rs | 88 ++-- serde_v8/tests/magic.rs | 75 +-- 11 files changed, 1113 insertions(+), 1023 deletions(-) diff --git a/core/bindings.rs b/core/bindings.rs index a44b2e4f1ed4b8..b5c9b990f4bd5d 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -475,7 +475,7 @@ fn eval_context( #[derive(Serialize)] struct Output<'s>(Option>, Option>); - + #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct ErrInfo<'s> { @@ -483,7 +483,7 @@ fn eval_context( is_native_error: bool, is_compile_error: bool, } - + let tc_scope = &mut v8::TryCatch::new(scope); let name = v8::String::new( tc_scope, @@ -496,11 +496,14 @@ fn eval_context( if maybe_script.is_none() { assert!(tc_scope.has_caught()); let exception = tc_scope.exception().unwrap(); - let output = Output(None, Some(ErrInfo{ - thrown: exception.into(), - is_native_error: exception.is_native_error(), - is_compile_error: true, - })); + let output = Output( + None, + Some(ErrInfo { + thrown: exception.into(), + is_native_error: exception.is_native_error(), + is_compile_error: true, + }), + ); rv.set(to_v8(tc_scope, output).unwrap()); return; } @@ -510,15 +513,18 @@ fn eval_context( if result.is_none() { assert!(tc_scope.has_caught()); let exception = tc_scope.exception().unwrap(); - let output = Output(None, Some(ErrInfo{ - thrown: exception.into(), - is_native_error: exception.is_native_error(), - is_compile_error: false, - })); + let output = Output( + None, + Some(ErrInfo { + thrown: exception.into(), + is_native_error: exception.is_native_error(), + is_compile_error: false, + }), + ); rv.set(to_v8(tc_scope, output).unwrap()); return; } - + let output = Output(Some(result.unwrap().into()), None); rv.set(to_v8(tc_scope, output).unwrap()); } @@ -788,7 +794,7 @@ fn get_promise_details( return; } }; - + #[derive(Serialize)] struct PromiseDetails<'s>(u32, Option>); @@ -798,11 +804,15 @@ fn get_promise_details( } v8::PromiseState::Fulfilled => { let promise_result = promise.result(scope); - rv.set(to_v8(scope, PromiseDetails(1, Some(promise_result.into()))).unwrap()); + rv.set( + to_v8(scope, PromiseDetails(1, Some(promise_result.into()))).unwrap(), + ); } v8::PromiseState::Rejected => { let promise_result = promise.result(scope); - rv.set(to_v8(scope, PromiseDetails(2, Some(promise_result.into()))).unwrap()); + rv.set( + to_v8(scope, PromiseDetails(2, Some(promise_result.into()))).unwrap(), + ); } } } diff --git a/serde_v8/examples/basic.rs b/serde_v8/examples/basic.rs index 1d863ee5c421ad..ce24b320185adb 100644 --- a/serde_v8/examples/basic.rs +++ b/serde_v8/examples/basic.rs @@ -5,51 +5,54 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] struct MathOp { - pub a: u64, - pub b: u64, - pub operator: Option, + pub a: u64, + pub b: u64, + pub operator: Option, } fn main() { - let platform = v8::new_default_platform().unwrap(); - v8::V8::initialize_platform(platform); - v8::V8::initialize(); - - { - let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); - let handle_scope = &mut v8::HandleScope::new(isolate); - let context = v8::Context::new(handle_scope); - let scope = &mut v8::ContextScope::new(handle_scope, context); - - fn exec<'s>(scope: &mut v8::HandleScope<'s>, src: &str) -> v8::Local<'s, v8::Value> { - let code = v8::String::new(scope, src).unwrap(); - let script = v8::Script::compile(scope, code, None).unwrap(); - script.run(scope).unwrap() - } - - let v = exec(scope, "32"); - let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); - println!("x32 = {}", x32); - - let v = exec(scope, "({a: 1, b: 3, c: 'ignored'})"); - let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); - println!("mop = {:?}", mop); - - let v = exec(scope, "[1,2,3,4,5]"); - let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); - println!("arr = {:?}", arr); - - let v = exec(scope, "['hello', 'world']"); - let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); - println!("hi = {:?}", hi); - - let v: v8::Local = v8::Number::new(scope, 12345.0).into(); - let x: f64 = serde_v8::from_v8(scope, v).unwrap(); - println!("x = {}", x); + let platform = v8::new_default_platform().unwrap(); + v8::V8::initialize_platform(platform); + v8::V8::initialize(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + fn exec<'s>( + scope: &mut v8::HandleScope<'s>, + src: &str, + ) -> v8::Local<'s, v8::Value> { + let code = v8::String::new(scope, src).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + script.run(scope).unwrap() } - unsafe { - v8::V8::dispose(); - } - v8::V8::shutdown_platform(); + let v = exec(scope, "32"); + let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); + println!("x32 = {}", x32); + + let v = exec(scope, "({a: 1, b: 3, c: 'ignored'})"); + let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); + println!("mop = {:?}", mop); + + let v = exec(scope, "[1,2,3,4,5]"); + let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); + println!("arr = {:?}", arr); + + let v = exec(scope, "['hello', 'world']"); + let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); + println!("hi = {:?}", hi); + + let v: v8::Local = v8::Number::new(scope, 12345.0).into(); + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + println!("x = {}", x); + } + + unsafe { + v8::V8::dispose(); + } + v8::V8::shutdown_platform(); } diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index 8ebadd988b66a9..aea059d4d4faa2 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -11,386 +11,414 @@ use crate::payload::ValueType; use crate::magic; pub struct Deserializer<'a, 'b, 's> { - input: v8::Local<'a, v8::Value>, - scope: &'b mut v8::HandleScope<'s>, - _key_cache: Option<&'b mut KeyCache>, + input: v8::Local<'a, v8::Value>, + scope: &'b mut v8::HandleScope<'s>, + _key_cache: Option<&'b mut KeyCache>, } pub type KeyCache = HashMap<&'static str, v8::Global>; impl<'a, 'b, 's> Deserializer<'a, 'b, 's> { - pub fn new( - scope: &'b mut v8::HandleScope<'s>, - input: v8::Local<'a, v8::Value>, - key_cache: Option<&'b mut KeyCache>, - ) -> Self { - Deserializer { - input, - scope, - _key_cache: key_cache, - } + pub fn new( + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, + key_cache: Option<&'b mut KeyCache>, + ) -> Self { + Deserializer { + input, + scope, + _key_cache: key_cache, } + } } // from_v8 deserializes a v8::Value into a Deserializable / rust struct pub fn from_v8<'de, 'a, 'b, 's, T>( - scope: &'b mut v8::HandleScope<'s>, - input: v8::Local<'a, v8::Value>, + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, ) -> Result where - T: Deserialize<'de>, + T: Deserialize<'de>, { - let mut deserializer = Deserializer::new(scope, input, None); - let t = T::deserialize(&mut deserializer)?; - Ok(t) + let mut deserializer = Deserializer::new(scope, input, None); + let t = T::deserialize(&mut deserializer)?; + Ok(t) } // like from_v8 except accepts a KeyCache to optimize struct key decoding pub fn from_v8_cached<'de, 'a, 'b, 's, T>( - scope: &'b mut v8::HandleScope<'s>, - input: v8::Local<'a, v8::Value>, - key_cache: &mut KeyCache, + scope: &'b mut v8::HandleScope<'s>, + input: v8::Local<'a, v8::Value>, + key_cache: &mut KeyCache, ) -> Result where - T: Deserialize<'de>, + T: Deserialize<'de>, { - let mut deserializer = Deserializer::new(scope, input, Some(key_cache)); - let t = T::deserialize(&mut deserializer)?; - Ok(t) + let mut deserializer = Deserializer::new(scope, input, Some(key_cache)); + let t = T::deserialize(&mut deserializer)?; + Ok(t) } macro_rules! wip { - ($method:ident) => { - fn $method(self, _v: V) -> Result - where - V: Visitor<'de>, - { - unimplemented!() - } - }; -} - -macro_rules! deserialize_signed { - ($dmethod:ident, $vmethod:ident, $t:tt) => { - fn $dmethod(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.$vmethod(self.input.integer_value(&mut self.scope).unwrap() as $t) - } - }; -} - -impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> for &'x mut Deserializer<'a, 'b, 's> { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result + ($method:ident) => { + fn $method(self, _v: V) -> Result where - V: Visitor<'de>, + V: Visitor<'de>, { - match ValueType::from_v8(self.input) { - ValueType::Null => self.deserialize_unit(visitor), - ValueType::Bool => self.deserialize_bool(visitor), - ValueType::Number => self.deserialize_f64(visitor), - ValueType::String => self.deserialize_string(visitor), - ValueType::Array => self.deserialize_seq(visitor), - ValueType::Object => self.deserialize_map(visitor), - } - } - - fn deserialize_bool(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.input.is_boolean() { - visitor.visit_bool(self.input.boolean_value(&mut self.scope)) - } else { - Err(Error::ExpectedBoolean) - } - } - - deserialize_signed!(deserialize_i8, visit_i8, i8); - deserialize_signed!(deserialize_i16, visit_i16, i16); - deserialize_signed!(deserialize_i32, visit_i32, i32); - deserialize_signed!(deserialize_i64, visit_i64, i64); - // TODO: maybe handle unsigned by itself ? - deserialize_signed!(deserialize_u8, visit_u8, u8); - deserialize_signed!(deserialize_u16, visit_u16, u16); - deserialize_signed!(deserialize_u32, visit_u32, u32); - deserialize_signed!(deserialize_u64, visit_u64, u64); - - fn deserialize_f32(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_f32(self.input.number_value(&mut self.scope).unwrap() as f32) - } - - fn deserialize_f64(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_f64(self.input.number_value(&mut self.scope).unwrap()) - } - - wip!(deserialize_char); - wip!(deserialize_str); - - fn deserialize_string(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.input.is_string() { - let string = self.input.to_rust_string_lossy(self.scope); - visitor.visit_string(string) - } else { - Err(Error::ExpectedString) - } - } - - wip!(deserialize_bytes); - wip!(deserialize_byte_buf); - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.input.is_null_or_undefined() { - visitor.visit_none() - } else { - visitor.visit_some(self) - } - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.input.is_null() { - visitor.visit_unit() - } else { - Err(Error::ExpectedNull) - } + unimplemented!() } + }; +} - fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result +macro_rules! deserialize_signed { + ($dmethod:ident, $vmethod:ident, $t:tt) => { + fn $dmethod(self, visitor: V) -> Result where - V: Visitor<'de>, + V: Visitor<'de>, { - self.deserialize_unit(visitor) + visitor.$vmethod(self.input.integer_value(&mut self.scope).unwrap() as $t) } + }; +} - // As is done here, serializers are encouraged to treat newtype structs as - // insignificant wrappers around the data they contain. That means not - // parsing anything other than the contained value. - fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) +impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> + for &'x mut Deserializer<'a, 'b, 's> +{ + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match ValueType::from_v8(self.input) { + ValueType::Null => self.deserialize_unit(visitor), + ValueType::Bool => self.deserialize_bool(visitor), + ValueType::Number => self.deserialize_f64(visitor), + ValueType::String => self.deserialize_string(visitor), + ValueType::Array => self.deserialize_seq(visitor), + ValueType::Object => self.deserialize_map(visitor), } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - let arr = v8::Local::::try_from(self.input).unwrap(); - let len = arr.length(); - let obj = v8::Local::::from(arr); - let seq = SeqAccess { - pos: 0, - len, - obj, - scope: self.scope, - }; - visitor.visit_seq(seq) + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_boolean() { + visitor.visit_bool(self.input.boolean_value(&mut self.scope)) + } else { + Err(Error::ExpectedBoolean) } - - // Like deserialize_seq except it prefers tuple's length over input array's length - fn deserialize_tuple(self, len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { - // TODO: error on length mismatch - let obj = v8::Local::::try_from(self.input).unwrap(); - let seq = SeqAccess { - pos: 0, - len: len as u32, - obj, - scope: self.scope, - }; - visitor.visit_seq(seq) + } + + deserialize_signed!(deserialize_i8, visit_i8, i8); + deserialize_signed!(deserialize_i16, visit_i16, i16); + deserialize_signed!(deserialize_i32, visit_i32, i32); + deserialize_signed!(deserialize_i64, visit_i64, i64); + // TODO: maybe handle unsigned by itself ? + deserialize_signed!(deserialize_u8, visit_u8, u8); + deserialize_signed!(deserialize_u16, visit_u16, u16); + deserialize_signed!(deserialize_u32, visit_u32, u32); + deserialize_signed!(deserialize_u64, visit_u64, u64); + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_f32(self.input.number_value(&mut self.scope).unwrap() as f32) + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_f64(self.input.number_value(&mut self.scope).unwrap()) + } + + wip!(deserialize_char); + wip!(deserialize_str); + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_string() { + let string = self.input.to_rust_string_lossy(self.scope); + visitor.visit_string(string) + } else { + Err(Error::ExpectedString) } - - // Tuple structs look just like sequences in JSON. - fn deserialize_tuple_struct( - self, - _name: &'static str, - len: usize, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_tuple(len, visitor) + } + + wip!(deserialize_bytes); + wip!(deserialize_byte_buf); + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_null_or_undefined() { + visitor.visit_none() + } else { + visitor.visit_some(self) } - - wip!(deserialize_map); - - fn deserialize_struct( - self, - name: &'static str, - fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - // Magic for serde_v8::magic::Value, to passthrough v8::Value - // TODO: ensure this is cross-platform and there's no alternative - if name == magic::NAME { - let mv = magic::Value { v8_value: self.input }; - let hack: u64 = unsafe { std::mem::transmute(mv) }; - return visitor.visit_u64(hack); - } - - // Regular struct - let obj = v8::Local::::try_from(self.input).unwrap(); - let map = ObjectAccess { - fields: fields, - obj: obj, - pos: 0, - scope: self.scope, - _cache: None, - }; - - visitor.visit_map(map) + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.input.is_null() { + visitor.visit_unit() + } else { + Err(Error::ExpectedNull) } - - fn deserialize_enum( - self, - _name: &str, - _variants: &'static [&'static str], - _visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - unimplemented!(); + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + // As is done here, serializers are encouraged to treat newtype structs as + // insignificant wrappers around the data they contain. That means not + // parsing anything other than the contained value. + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let arr = v8::Local::::try_from(self.input).unwrap(); + let len = arr.length(); + let obj = v8::Local::::from(arr); + let seq = SeqAccess { + pos: 0, + len, + obj, + scope: self.scope, + }; + visitor.visit_seq(seq) + } + + // Like deserialize_seq except it prefers tuple's length over input array's length + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + // TODO: error on length mismatch + let obj = v8::Local::::try_from(self.input).unwrap(); + let seq = SeqAccess { + pos: 0, + len: len as u32, + obj, + scope: self.scope, + }; + visitor.visit_seq(seq) + } + + // Tuple structs look just like sequences in JSON. + fn deserialize_tuple_struct( + self, + _name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_tuple(len, visitor) + } + + wip!(deserialize_map); + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + // Magic for serde_v8::magic::Value, to passthrough v8::Value + // TODO: ensure this is cross-platform and there's no alternative + if name == magic::NAME { + let mv = magic::Value { + v8_value: self.input, + }; + let hack: u64 = unsafe { std::mem::transmute(mv) }; + return visitor.visit_u64(hack); } - // An identifier in Serde is the type that identifies a field of a struct or - // the variant of an enum. In JSON, struct fields and enum variants are - // represented as strings. In other formats they may be represented as - // numeric indices. - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - self.deserialize_str(visitor) - } + // Regular struct + let obj = v8::Local::::try_from(self.input).unwrap(); + let map = ObjectAccess { + fields: fields, + obj: obj, + pos: 0, + scope: self.scope, + _cache: None, + }; - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_none() - } + visitor.visit_map(map) + } + + fn deserialize_enum( + self, + _name: &str, + _variants: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + unimplemented!(); + } + + // An identifier in Serde is the type that identifies a field of a struct or + // the variant of an enum. In JSON, struct fields and enum variants are + // represented as strings. In other formats they may be represented as + // numeric indices. + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_none() + } } struct ObjectAccess<'a, 'b, 's> { - obj: v8::Local<'a, v8::Object>, - scope: &'b mut v8::HandleScope<'s>, - fields: &'static [&'static str], - pos: usize, - _cache: Option<&'b mut KeyCache>, + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + fields: &'static [&'static str], + pos: usize, + _cache: Option<&'b mut KeyCache>, } fn str_deserializer(s: &str) -> de::value::StrDeserializer { - de::IntoDeserializer::into_deserializer(s) + de::IntoDeserializer::into_deserializer(s) } impl<'de, 'a, 'b, 's> de::MapAccess<'de> for ObjectAccess<'a, 'b, 's> { - type Error = Error; - - fn next_key_seed>(&mut self, seed: K) -> Result> { - Ok(match self.fields.get(self.pos) { - Some(&field) => Some(seed.deserialize(str_deserializer(field))?), - None => None, - }) + type Error = Error; + + fn next_key_seed>( + &mut self, + seed: K, + ) -> Result> { + Ok(match self.fields.get(self.pos) { + Some(&field) => Some(seed.deserialize(str_deserializer(field))?), + None => None, + }) + } + + fn next_value_seed>( + &mut self, + seed: V, + ) -> Result { + if self.pos >= self.fields.len() { + return Err(Error::LengthMismatch); } - - fn next_value_seed>(&mut self, seed: V) -> Result { - if self.pos >= self.fields.len() { - return Err(Error::LengthMismatch) - } - let field = self.fields[self.pos]; - self.pos += 1; - let key = v8_struct_key(self.scope, field).into(); - let v8_val = self.obj.get(self.scope, key).unwrap(); - let mut deserializer = Deserializer::new(self.scope, v8_val, None); - seed.deserialize(&mut deserializer) - } - - fn next_entry_seed, V: de::DeserializeSeed<'de>>( - &mut self, - kseed: K, - vseed: V, - ) -> Result> { - if self.pos >= self.fields.len() { - return Ok(None) - } - let field = self.fields[self.pos]; - self.pos += 1; - Ok( - Some((kseed.deserialize(str_deserializer(field))?, { - let key = v8_struct_key(self.scope, field).into(); - let v8_val = self.obj.get(self.scope, key).unwrap(); - let mut deserializer = Deserializer::new(self.scope, v8_val, None); - vseed.deserialize(&mut deserializer)? - })) - ) + let field = self.fields[self.pos]; + self.pos += 1; + let key = v8_struct_key(self.scope, field).into(); + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + seed.deserialize(&mut deserializer) + } + + fn next_entry_seed< + K: de::DeserializeSeed<'de>, + V: de::DeserializeSeed<'de>, + >( + &mut self, + kseed: K, + vseed: V, + ) -> Result> { + if self.pos >= self.fields.len() { + return Ok(None); } + let field = self.fields[self.pos]; + self.pos += 1; + Ok(Some((kseed.deserialize(str_deserializer(field))?, { + let key = v8_struct_key(self.scope, field).into(); + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + vseed.deserialize(&mut deserializer)? + }))) + } } // creates an optimized v8::String for a struct field // TODO: experiment with external strings // TODO: evaluate if own KeyCache is better than v8's dedupe -fn v8_struct_key<'s>(scope: &mut v8::HandleScope<'s>, field: &'static str) -> v8::Local<'s, v8::String> { - // Internalized v8 strings are significantly faster than "normal" v8 strings - // since v8 deduplicates re-used strings minimizing new allocations - // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 - v8::String::new_from_utf8(scope, field.as_ref(), v8::NewStringType::Internalized).unwrap().into() - - // TODO: consider external strings later - // right now non-deduped external strings (without KeyCache) - // are slower than the deduped internalized strings by ~2.5x - // since they're a new string in v8's eyes and needs to be hashed, etc... - // v8::String::new_external_onebyte_static(scope, field).unwrap() +fn v8_struct_key<'s>( + scope: &mut v8::HandleScope<'s>, + field: &'static str, +) -> v8::Local<'s, v8::String> { + // Internalized v8 strings are significantly faster than "normal" v8 strings + // since v8 deduplicates re-used strings minimizing new allocations + // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 + v8::String::new_from_utf8( + scope, + field.as_ref(), + v8::NewStringType::Internalized, + ) + .unwrap() + .into() + + // TODO: consider external strings later + // right now non-deduped external strings (without KeyCache) + // are slower than the deduped internalized strings by ~2.5x + // since they're a new string in v8's eyes and needs to be hashed, etc... + // v8::String::new_external_onebyte_static(scope, field).unwrap() } struct SeqAccess<'a, 'b, 's> { - obj: v8::Local<'a, v8::Object>, - scope: &'b mut v8::HandleScope<'s>, - len: u32, - pos: u32, + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + len: u32, + pos: u32, } impl<'de> de::SeqAccess<'de> for SeqAccess<'_, '_, '_> { - type Error = Error; - - fn next_element_seed>( - &mut self, - seed: T, - ) -> Result> { - let pos = self.pos; - self.pos += 1; - - if pos < self.len { - let val = self.obj.get_index(self.scope, pos).unwrap(); - let mut deserializer = Deserializer::new(self.scope, val, None); - Ok(Some(seed.deserialize(&mut deserializer)?)) - } else { - Ok(None) - } + type Error = Error; + + fn next_element_seed>( + &mut self, + seed: T, + ) -> Result> { + let pos = self.pos; + self.pos += 1; + + if pos < self.len { + let val = self.obj.get_index(self.scope, pos).unwrap(); + let mut deserializer = Deserializer::new(self.scope, val, None); + Ok(Some(seed.deserialize(&mut deserializer)?)) + } else { + Ok(None) } + } } diff --git a/serde_v8/src/error.rs b/serde_v8/src/error.rs index 5ea749ebde7229..e50ca9e334b0c4 100644 --- a/serde_v8/src/error.rs +++ b/serde_v8/src/error.rs @@ -7,38 +7,38 @@ pub type Result = std::result::Result; #[derive(Clone, Debug, PartialEq)] pub enum Error { - Message(String), - - ExpectedBoolean, - ExpectedInteger, - ExpectedString, - ExpectedNull, - ExpectedArray, - ExpectedMap, - ExpectedEnum, - - LengthMismatch, + Message(String), + + ExpectedBoolean, + ExpectedInteger, + ExpectedString, + ExpectedNull, + ExpectedArray, + ExpectedMap, + ExpectedEnum, + + LengthMismatch, } impl ser::Error for Error { - fn custom(msg: T) -> Self { - Error::Message(msg.to_string()) - } + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } } impl de::Error for Error { - fn custom(msg: T) -> Self { - Error::Message(msg.to_string()) - } + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } } impl Display for Error { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::Message(msg) => formatter.write_str(msg), - _ => formatter.write_str("serde_v8 error: TODO"), - } + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Message(msg) => formatter.write_str(msg), + _ => formatter.write_str("serde_v8 error: TODO"), } + } } impl std::error::Error for Error {} diff --git a/serde_v8/src/lib.rs b/serde_v8/src/lib.rs index a033f527bde6bc..1484339bbbefcf 100644 --- a/serde_v8/src/lib.rs +++ b/serde_v8/src/lib.rs @@ -7,5 +7,5 @@ pub mod utils; pub use de::{from_v8, from_v8_cached, Deserializer, KeyCache}; pub use error::{Error, Result}; -pub use magic::{Value}; +pub use magic::Value; pub use ser::{to_v8, Serializer}; diff --git a/serde_v8/src/magic.rs b/serde_v8/src/magic.rs index 8e9fa0931a766f..4d589590ac8d83 100644 --- a/serde_v8/src/magic.rs +++ b/serde_v8/src/magic.rs @@ -8,63 +8,65 @@ pub const FIELD: &'static str = "$__v8_magic_value"; pub const NAME: &'static str = "$__v8_magic_Value"; pub struct Value<'s> { - pub v8_value: v8::Local<'s, v8::Value> + pub v8_value: v8::Local<'s, v8::Value>, } impl<'s> From> for Value<'s> { - fn from(v8_value: v8::Local<'s, v8::Value>) -> Self { - Self { v8_value } - } + fn from(v8_value: v8::Local<'s, v8::Value>) -> Self { + Self { v8_value } + } } impl<'s> From> for v8::Local<'s, v8::Value> { - fn from(v: Value<'s>) -> Self { - v.v8_value - } + fn from(v: Value<'s>) -> Self { + v.v8_value + } } impl serde::Serialize for Value<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; - let mut s = serializer.serialize_struct(NAME, 1)?; - let mv = Value { v8_value: self.v8_value }; - let hack: u64 = unsafe { std::mem::transmute(mv) }; - s.serialize_field(FIELD, &hack)?; - s.end() - } + let mut s = serializer.serialize_struct(NAME, 1)?; + let mv = Value { + v8_value: self.v8_value, + }; + let hack: u64 = unsafe { std::mem::transmute(mv) }; + s.serialize_field(FIELD, &hack)?; + s.end() + } } impl<'de, 's> serde::Deserialize<'de> for Value<'s> { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - struct ValueVisitor<'s> { - p1: PhantomData<&'s ()>, - } - - impl<'de, 's> serde::de::Visitor<'de> for ValueVisitor<'s> { - type Value = Value<'s>; + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor<'s> { + p1: PhantomData<&'s ()>, + } - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a v8::Value") - } + impl<'de, 's> serde::de::Visitor<'de> for ValueVisitor<'s> { + type Value = Value<'s>; - fn visit_u64(self, v: u64) -> Result - where - E: serde::de::Error, - { - let mv: Value<'s> = unsafe { std::mem::transmute(v) }; - Ok(mv) - } - } + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a v8::Value") + } - static FIELDS: [&str; 1] = [FIELD]; - let visitor = ValueVisitor { p1: PhantomData }; - deserializer.deserialize_struct(NAME, &FIELDS, visitor) + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + let mv: Value<'s> = unsafe { std::mem::transmute(v) }; + Ok(mv) + } } + + static FIELDS: [&str; 1] = [FIELD]; + let visitor = ValueVisitor { p1: PhantomData }; + deserializer.deserialize_struct(NAME, &FIELDS, visitor) + } } diff --git a/serde_v8/src/payload.rs b/serde_v8/src/payload.rs index 3f6bbbb6b14dca..63562ac3469946 100644 --- a/serde_v8/src/payload.rs +++ b/serde_v8/src/payload.rs @@ -5,29 +5,29 @@ use rusty_v8 as v8; // Classifies v8::Values into sub-types pub enum ValueType { - Null, - Bool, - Number, - String, - Array, - Object, + Null, + Bool, + Number, + String, + Array, + Object, } impl ValueType { - pub fn from_v8(v: v8::Local) -> ValueType { - if v.is_boolean() { - return Self::Bool; - } else if v.is_number() { - return Self::Number; - } else if v.is_string() { - return Self::String; - } else if v.is_array() { - return Self::Array; - } else if v.is_object() { - return Self::Object; - } else if v.is_null_or_undefined() { - return Self::Null; - } - panic!("serde_v8: unknown ValueType for v8::Value") + pub fn from_v8(v: v8::Local) -> ValueType { + if v.is_boolean() { + return Self::Bool; + } else if v.is_number() { + return Self::Number; + } else if v.is_string() { + return Self::String; + } else if v.is_array() { + return Self::Array; + } else if v.is_object() { + return Self::Object; + } else if v.is_null_or_undefined() { + return Self::Null; } + panic!("serde_v8: unknown ValueType for v8::Value") + } } diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs index f8c48671d6c984..71e7d8d9ae14fa 100644 --- a/serde_v8/src/ser.rs +++ b/serde_v8/src/ser.rs @@ -2,8 +2,8 @@ use rusty_v8 as v8; use serde::ser; use serde::ser::{Impossible, Serialize}; +use std::cell::RefCell; use std::rc::Rc; -use std::cell::{RefCell}; use crate::error::{Error, Result}; use crate::magic; @@ -13,239 +13,258 @@ type JsResult<'s> = Result>; type ScopePtr<'a, 'b> = Rc>>; -pub fn to_v8<'a, 'b, T>(scope: &mut v8::HandleScope<'a>, input: T) -> JsResult<'a> +pub fn to_v8<'a, 'b, T>( + scope: &mut v8::HandleScope<'a>, + input: T, +) -> JsResult<'a> where - T: Serialize, -{ - let subscope = v8::EscapableHandleScope::new(scope); - let scopeptr = Rc::new(RefCell::new(subscope)); - let serializer = Serializer::new(scopeptr.clone()); - let x = input.serialize(serializer)?; - let x = scopeptr.clone().borrow_mut().escape(x); - - Ok(x) - + T: Serialize, +{ + let subscope = v8::EscapableHandleScope::new(scope); + let scopeptr = Rc::new(RefCell::new(subscope)); + let serializer = Serializer::new(scopeptr.clone()); + let x = input.serialize(serializer)?; + let x = scopeptr.clone().borrow_mut().escape(x); + + Ok(x) } /// Wraps other serializers into an enum tagged variant form. /// Uses {"Variant": ...payload...} for compatibility with serde-json. pub struct VariantSerializer<'a, 'b, S> { - variant: &'static str, - inner: S, - scope: ScopePtr<'a, 'b>, + variant: &'static str, + inner: S, + scope: ScopePtr<'a, 'b>, } impl<'a, 'b, S> VariantSerializer<'a, 'b, S> { - pub fn new(scope: ScopePtr<'a, 'b>, variant: &'static str, inner: S) -> Self { - Self { scope, variant, inner } - } - - fn end(self, inner: impl FnOnce(S) -> JsResult<'a>) -> JsResult<'a> { - let value = inner(self.inner)?; - let scope = &mut *self.scope.borrow_mut(); - let obj = v8::Object::new(scope); - let key = v8_struct_key(scope, self.variant).into(); - obj.set(scope, key, value); - Ok(obj.into()) - } + pub fn new(scope: ScopePtr<'a, 'b>, variant: &'static str, inner: S) -> Self { + Self { + scope, + variant, + inner, + } + } + + fn end(self, inner: impl FnOnce(S) -> JsResult<'a>) -> JsResult<'a> { + let value = inner(self.inner)?; + let scope = &mut *self.scope.borrow_mut(); + let obj = v8::Object::new(scope); + let key = v8_struct_key(scope, self.variant).into(); + obj.set(scope, key, value); + Ok(obj.into()) + } } impl<'a, 'b, S> ser::SerializeTupleVariant for VariantSerializer<'a, 'b, S> -where S: ser::SerializeTupleStruct, Error = Error> +where + S: ser::SerializeTupleStruct, Error = Error>, { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field(&mut self, value: &T) -> Result<()> { - self.inner.serialize_field(value) - } - - fn end(self) -> JsResult<'a> { - self.end(S::end) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<()> { + self.inner.serialize_field(value) + } + + fn end(self) -> JsResult<'a> { + self.end(S::end) + } } impl<'a, 'b, S> ser::SerializeStructVariant for VariantSerializer<'a, 'b, S> -where S: ser::SerializeStruct, Error = Error> +where + S: ser::SerializeStruct, Error = Error>, { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<()> { - self.inner.serialize_field(key, value) - } - - fn end(self) -> JsResult<'a> { - self.end(S::end) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + self.inner.serialize_field(key, value) + } + + fn end(self) -> JsResult<'a> { + self.end(S::end) + } } pub struct ArraySerializer<'a, 'b> { - // serializer: Serializer<'a, 'b>, - pending: Vec>, - scope: ScopePtr<'a, 'b>, + // serializer: Serializer<'a, 'b>, + pending: Vec>, + scope: ScopePtr<'a, 'b>, } impl<'a, 'b> ArraySerializer<'a, 'b> { - pub fn new(scope: ScopePtr<'a, 'b>) -> Self { - // let serializer = Serializer::new(scope); - Self { - scope, - // serializer, - pending: vec![], - } - } + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + // let serializer = Serializer::new(scope); + Self { + scope, + // serializer, + pending: vec![], + } + } } impl<'a, 'b> ser::SerializeSeq for ArraySerializer<'a, 'b> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_element(&mut self, value: &T) -> Result<()> { - let x = value.serialize(Serializer::new(self.scope.clone()))?; - self.pending.push(x); - Ok(()) - } - - fn end(self) -> JsResult<'a> { - let elements = self.pending.iter().as_slice(); - let scope = &mut *self.scope.borrow_mut(); - let arr = v8::Array::new_with_elements(scope, elements); - Ok(arr.into()) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_element( + &mut self, + value: &T, + ) -> Result<()> { + let x = value.serialize(Serializer::new(self.scope.clone()))?; + self.pending.push(x); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + let elements = self.pending.iter().as_slice(); + let scope = &mut *self.scope.borrow_mut(); + let arr = v8::Array::new_with_elements(scope, elements); + Ok(arr.into()) + } } impl<'a, 'b> ser::SerializeTuple for ArraySerializer<'a, 'b> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_element(&mut self, value: &T) -> Result<()> { - ser::SerializeSeq::serialize_element(self, value) - } - - fn end(self) -> JsResult<'a> { - ser::SerializeSeq::end(self) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_element( + &mut self, + value: &T, + ) -> Result<()> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> JsResult<'a> { + ser::SerializeSeq::end(self) + } } impl<'a, 'b> ser::SerializeTupleStruct for ArraySerializer<'a, 'b> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field(&mut self, value: &T) -> Result<()> { - ser::SerializeTuple::serialize_element(self, value) - } - - fn end(self) -> JsResult<'a> { - ser::SerializeTuple::end(self) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<()> { + ser::SerializeTuple::serialize_element(self, value) + } + + fn end(self) -> JsResult<'a> { + ser::SerializeTuple::end(self) + } } pub struct ObjectSerializer<'a, 'b> { - scope: ScopePtr<'a, 'b>, - obj: v8::Local<'a, v8::Object>, + scope: ScopePtr<'a, 'b>, + obj: v8::Local<'a, v8::Object>, } impl<'a, 'b> ObjectSerializer<'a, 'b> { - pub fn new(scope: ScopePtr<'a, 'b>) -> Self { - let obj = v8::Object::new(&mut *scope.borrow_mut()); - Self { - scope, - obj, - } - } + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + let obj = v8::Object::new(&mut *scope.borrow_mut()); + Self { scope, obj } + } } impl<'a, 'b> ser::SerializeStruct for ObjectSerializer<'a, 'b> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<()> { - let value = value.serialize(Serializer::new(self.scope.clone()))?; - let scope = &mut *self.scope.borrow_mut(); - let key = v8_struct_key(scope, key).into(); - self.obj.set(scope, key, value); - Ok(()) - } - - fn end(self) -> JsResult<'a> { - Ok(self.obj.into()) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + let value = value.serialize(Serializer::new(self.scope.clone()))?; + let scope = &mut *self.scope.borrow_mut(); + let key = v8_struct_key(scope, key).into(); + self.obj.set(scope, key, value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + Ok(self.obj.into()) + } } pub struct MagicSerializer<'a, 'b> { - scope: ScopePtr<'a, 'b>, - v8_value: Option>, + scope: ScopePtr<'a, 'b>, + v8_value: Option>, } impl<'a, 'b> ser::SerializeStruct for MagicSerializer<'a, 'b> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<()> { - if key != magic::FIELD { - unreachable!(); - } - let v8_value = value.serialize(MagicTransmuter{ _scope: self.scope.clone() })?; - self.v8_value = Some(v8_value); - Ok(()) - } - - fn end(self) -> JsResult<'a> { - Ok(self.v8_value.unwrap()) - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + if key != magic::FIELD { + unreachable!(); + } + let v8_value = value.serialize(MagicTransmuter { + _scope: self.scope.clone(), + })?; + self.v8_value = Some(v8_value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + Ok(self.v8_value.unwrap()) + } } // Dispatches between magic and regular struct serializers pub enum StructSerializers<'a, 'b> { - Magic(MagicSerializer<'a, 'b>), - Regular(ObjectSerializer<'a, 'b>) + Magic(MagicSerializer<'a, 'b>), + Regular(ObjectSerializer<'a, 'b>), } impl<'a, 'b> ser::SerializeStruct for StructSerializers<'a, 'b> { - type Ok = JsValue<'a>; - type Error = Error; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<()> { - match self { - StructSerializers::Magic(s) => s.serialize_field(key, value), - StructSerializers::Regular(s) => s.serialize_field(key, value), - } - } - - fn end(self) -> JsResult<'a> { - match self { - StructSerializers::Magic(s) => s.end(), - StructSerializers::Regular(s) => s.end(), - } - } + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<()> { + match self { + StructSerializers::Magic(s) => s.serialize_field(key, value), + StructSerializers::Regular(s) => s.serialize_field(key, value), + } + } + + fn end(self) -> JsResult<'a> { + match self { + StructSerializers::Magic(s) => s.end(), + StructSerializers::Regular(s) => s.end(), + } + } } #[derive(Clone)] pub struct Serializer<'a, 'b> { - scope: ScopePtr<'a, 'b>, + scope: ScopePtr<'a, 'b>, } impl<'a, 'b> Serializer<'a, 'b> { - pub fn new(scope: ScopePtr<'a, 'b>) -> Self { - Serializer { scope } - } + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + Serializer { scope } + } } macro_rules! forward_to { @@ -257,167 +276,174 @@ macro_rules! forward_to { } impl<'a, 'b> ser::Serializer for Serializer<'a, 'b> { - type Ok = v8::Local<'a, v8::Value>; - type Error = Error; - - type SerializeSeq = ArraySerializer<'a, 'b>; - type SerializeTuple = ArraySerializer<'a, 'b>; - type SerializeTupleStruct = ArraySerializer<'a, 'b>; - type SerializeTupleVariant = VariantSerializer<'a, 'b, ArraySerializer<'a, 'b>>; - type SerializeMap = Impossible, Error>; - type SerializeStruct = StructSerializers<'a, 'b>; - type SerializeStructVariant = VariantSerializer<'a, 'b, StructSerializers<'a, 'b>>; - - forward_to! { - serialize_i8(i8, serialize_i32, 'a); - serialize_i16(i16, serialize_i32, 'a); - - serialize_u8(u8, serialize_u32, 'a); - serialize_u16(u16, serialize_u32, 'a); - - serialize_f32(f32, serialize_f64, 'a); - serialize_u64(u64, serialize_f64, 'a); - serialize_i64(i64, serialize_f64, 'a); - } - - fn serialize_i32(self, v: i32) -> JsResult<'a> { - Ok(v8::Integer::new(&mut self.scope.borrow_mut(), v).into()) - } - - fn serialize_u32(self, v: u32) -> JsResult<'a> { - Ok(v8::Integer::new_from_unsigned(&mut self.scope.borrow_mut(), v).into()) - } - - fn serialize_f64(self, v: f64) -> JsResult<'a> { - Ok(v8::Number::new(&mut self.scope.borrow_mut(), v).into()) - } - - fn serialize_bool(self, v: bool) -> JsResult<'a> { - Ok(v8::Boolean::new(&mut self.scope.borrow_mut(), v).into()) - } - - fn serialize_char(self, _v: char) -> JsResult<'a> { - unimplemented!(); - } - - fn serialize_str(self, v: &str) -> JsResult<'a> { - v8::String::new(&mut self.scope.borrow_mut(), v).map(|v| v.into()).ok_or(Error::ExpectedString) - } - - fn serialize_bytes(self, _v: &[u8]) -> JsResult<'a> { - // TODO: investigate using Uint8Arrays - unimplemented!() - } - - fn serialize_none(self) -> JsResult<'a> { - Ok(v8::null(&mut self.scope.borrow_mut()).into()) - } - - fn serialize_some(self, value: &T) -> JsResult<'a> { - value.serialize(self) - } - - fn serialize_unit(self) -> JsResult<'a> { - Ok(v8::null(&mut self.scope.borrow_mut()).into()) - } - - fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { - Ok(v8::null(&mut self.scope.borrow_mut()).into()) - } - - /// For compatibility with serde-json, serialises unit variants as "Variant" strings. - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - ) -> JsResult<'a> { - Ok(v8_struct_key(&mut self.scope.borrow_mut(), variant).into()) - } - - fn serialize_newtype_struct( - self, - _name: &'static str, - value: &T, - ) -> JsResult<'a> { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - value: &T, - ) -> JsResult<'a> { - let scope = self.scope.clone(); - let x = self.serialize_newtype_struct(variant, value)?; - VariantSerializer::new(scope, variant, x).end(Ok) - } - - /// Serialises any Rust iterable into a JS Array - fn serialize_seq(self, _len: Option) -> Result { - Ok(ArraySerializer::new(self.scope)) - } - - 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_tuple(len) - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - len: usize, - ) -> Result { - Ok(VariantSerializer::new( - self.scope.clone(), - variant, - self.serialize_tuple_struct(variant, len)?, - )) - } - - fn serialize_map(self, _len: Option) -> Result { - // TODO: serialize Maps (HashMap or BTreeMap) to v8 objects, - // ideally JS Maps since they're lighter and better suited for K/V data - // only allow certain keys (e.g: strings and numbers) - unimplemented!() - } - - /// Serialises Rust typed structs into plain JS objects. - fn serialize_struct(self, name: &'static str, _len: usize) -> Result { - if name == magic::NAME { - let m = MagicSerializer { scope: self.scope, v8_value: None }; - return Ok(StructSerializers::Magic(m)); - } - let o = ObjectSerializer::new(self.scope); - Ok(StructSerializers::Regular(o)) - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - len: usize, - ) -> Result { - let scope = self.scope.clone(); - let x = self.serialize_struct(variant, len)?; - Ok(VariantSerializer::new( - scope, - variant, - x, - )) - } + type Ok = v8::Local<'a, v8::Value>; + type Error = Error; + + type SerializeSeq = ArraySerializer<'a, 'b>; + type SerializeTuple = ArraySerializer<'a, 'b>; + type SerializeTupleStruct = ArraySerializer<'a, 'b>; + type SerializeTupleVariant = + VariantSerializer<'a, 'b, ArraySerializer<'a, 'b>>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = StructSerializers<'a, 'b>; + type SerializeStructVariant = + VariantSerializer<'a, 'b, StructSerializers<'a, 'b>>; + + forward_to! { + serialize_i8(i8, serialize_i32, 'a); + serialize_i16(i16, serialize_i32, 'a); + + serialize_u8(u8, serialize_u32, 'a); + serialize_u16(u16, serialize_u32, 'a); + + serialize_f32(f32, serialize_f64, 'a); + serialize_u64(u64, serialize_f64, 'a); + serialize_i64(i64, serialize_f64, 'a); + } + + fn serialize_i32(self, v: i32) -> JsResult<'a> { + Ok(v8::Integer::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_u32(self, v: u32) -> JsResult<'a> { + Ok(v8::Integer::new_from_unsigned(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_f64(self, v: f64) -> JsResult<'a> { + Ok(v8::Number::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_bool(self, v: bool) -> JsResult<'a> { + Ok(v8::Boolean::new(&mut self.scope.borrow_mut(), v).into()) + } + + fn serialize_char(self, _v: char) -> JsResult<'a> { + unimplemented!(); + } + + fn serialize_str(self, v: &str) -> JsResult<'a> { + v8::String::new(&mut self.scope.borrow_mut(), v) + .map(|v| v.into()) + .ok_or(Error::ExpectedString) + } + + fn serialize_bytes(self, _v: &[u8]) -> JsResult<'a> { + // TODO: investigate using Uint8Arrays + unimplemented!() + } + + fn serialize_none(self) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + fn serialize_some(self, value: &T) -> JsResult<'a> { + value.serialize(self) + } + + fn serialize_unit(self) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { + Ok(v8::null(&mut self.scope.borrow_mut()).into()) + } + + /// For compatibility with serde-json, serialises unit variants as "Variant" strings. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> JsResult<'a> { + Ok(v8_struct_key(&mut self.scope.borrow_mut(), variant).into()) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> JsResult<'a> { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> JsResult<'a> { + let scope = self.scope.clone(); + let x = self.serialize_newtype_struct(variant, value)?; + VariantSerializer::new(scope, variant, x).end(Ok) + } + + /// Serialises any Rust iterable into a JS Array + fn serialize_seq(self, _len: Option) -> Result { + Ok(ArraySerializer::new(self.scope)) + } + + 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_tuple(len) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(VariantSerializer::new( + self.scope.clone(), + variant, + self.serialize_tuple_struct(variant, len)?, + )) + } + + fn serialize_map(self, _len: Option) -> Result { + // TODO: serialize Maps (HashMap or BTreeMap) to v8 objects, + // ideally JS Maps since they're lighter and better suited for K/V data + // only allow certain keys (e.g: strings and numbers) + unimplemented!() + } + + /// Serialises Rust typed structs into plain JS objects. + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + if name == magic::NAME { + let m = MagicSerializer { + scope: self.scope, + v8_value: None, + }; + return Ok(StructSerializers::Magic(m)); + } + let o = ObjectSerializer::new(self.scope); + Ok(StructSerializers::Regular(o)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let scope = self.scope.clone(); + let x = self.serialize_struct(variant, len)?; + Ok(VariantSerializer::new(scope, variant, x)) + } } macro_rules! not_reachable { @@ -430,140 +456,153 @@ macro_rules! not_reachable { /// A VERY hackish serde::Serializer /// that exists solely to transmute a u64 to a serde_v8::Value -struct MagicTransmuter<'a, 'b>{ - _scope: ScopePtr<'a, 'b>, +struct MagicTransmuter<'a, 'b> { + _scope: ScopePtr<'a, 'b>, } impl<'a, 'b> ser::Serializer for MagicTransmuter<'a, 'b> { - type Ok = v8::Local<'a, v8::Value>; - type Error = Error; - - type SerializeSeq = Impossible, Error>; - type SerializeTuple = Impossible, Error>; - type SerializeTupleStruct = Impossible, Error>; - type SerializeTupleVariant = Impossible, Error>; - type SerializeMap = Impossible, Error>; - type SerializeStruct = Impossible, Error>; - type SerializeStructVariant = Impossible, Error>; - - // The only serialize method for this hackish struct - fn serialize_u64(self, v: u64) -> JsResult<'a> { - let mv: magic::Value = unsafe { std::mem::transmute(v) }; - Ok(mv.v8_value) - } - - not_reachable! { - serialize_i8(i8, 'a); - serialize_i16(i16, 'a); - serialize_i32(i32, 'a); - serialize_i64(i64, 'a); - serialize_u8(u8, 'a); - serialize_u16(u16, 'a); - serialize_u32(u32, 'a); - // serialize_u64(u64, 'a); the chosen one - serialize_f32(f32, 'a); - serialize_f64(f64, 'a); - serialize_bool(bool, 'a); - serialize_char(char, 'a); - serialize_str(&str, 'a); - serialize_bytes(&[u8], 'a); - } - - fn serialize_none(self) -> JsResult<'a> { - unreachable!(); - } - - fn serialize_some(self, _value: &T) -> JsResult<'a> { - unreachable!(); - } - - fn serialize_unit(self) -> JsResult<'a> { - unreachable!(); - } - - fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { - unreachable!(); - } - - /// For compatibility with serde-json, serialises unit variants as "Variant" strings. - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - ) -> JsResult<'a> { - unreachable!(); - } - - fn serialize_newtype_struct( - self, - _name: &'static str, - _value: &T, - ) -> JsResult<'a> { - unreachable!(); - } - - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T, - ) -> JsResult<'a> { - unreachable!(); - } - fn serialize_seq(self, _len: Option) -> Result { - unreachable!(); - } - - fn serialize_tuple(self, _len: usize) -> Result { - unreachable!(); - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - unreachable!(); - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - unreachable!(); - } - - fn serialize_map(self, _len: Option) -> Result { - unreachable!(); - } - - /// Serialises Rust typed structs into plain JS objects. - fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { - unreachable!(); - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - unreachable!(); - } + type Ok = v8::Local<'a, v8::Value>; + type Error = Error; + + type SerializeSeq = Impossible, Error>; + type SerializeTuple = Impossible, Error>; + type SerializeTupleStruct = Impossible, Error>; + type SerializeTupleVariant = Impossible, Error>; + type SerializeMap = Impossible, Error>; + type SerializeStruct = Impossible, Error>; + type SerializeStructVariant = Impossible, Error>; + + // The only serialize method for this hackish struct + fn serialize_u64(self, v: u64) -> JsResult<'a> { + let mv: magic::Value = unsafe { std::mem::transmute(v) }; + Ok(mv.v8_value) + } + + not_reachable! { + serialize_i8(i8, 'a); + serialize_i16(i16, 'a); + serialize_i32(i32, 'a); + serialize_i64(i64, 'a); + serialize_u8(u8, 'a); + serialize_u16(u16, 'a); + serialize_u32(u32, 'a); + // serialize_u64(u64, 'a); the chosen one + serialize_f32(f32, 'a); + serialize_f64(f64, 'a); + serialize_bool(bool, 'a); + serialize_char(char, 'a); + serialize_str(&str, 'a); + serialize_bytes(&[u8], 'a); + } + + fn serialize_none(self) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_some(self, _value: &T) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_unit(self) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_unit_struct(self, _name: &'static str) -> JsResult<'a> { + unreachable!(); + } + + /// For compatibility with serde-json, serialises unit variants as "Variant" strings. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + _value: &T, + ) -> JsResult<'a> { + unreachable!(); + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> JsResult<'a> { + unreachable!(); + } + fn serialize_seq(self, _len: Option) -> Result { + unreachable!(); + } + + fn serialize_tuple(self, _len: usize) -> Result { + unreachable!(); + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_map(self, _len: Option) -> Result { + unreachable!(); + } + + /// Serialises Rust typed structs into plain JS objects. + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + unreachable!(); + } } // creates an optimized v8::String for a struct field // TODO: experiment with external strings // TODO: evaluate if own KeyCache is better than v8's dedupe -fn v8_struct_key<'s>(scope: &mut v8::HandleScope<'s>, field: &'static str) -> v8::Local<'s, v8::String> { - // Internalized v8 strings are significantly faster than "normal" v8 strings - // since v8 deduplicates re-used strings minimizing new allocations - // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 - v8::String::new_from_utf8(scope, field.as_ref(), v8::NewStringType::Internalized).unwrap().into() +fn v8_struct_key<'s>( + scope: &mut v8::HandleScope<'s>, + field: &'static str, +) -> v8::Local<'s, v8::String> { + // Internalized v8 strings are significantly faster than "normal" v8 strings + // since v8 deduplicates re-used strings minimizing new allocations + // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 + v8::String::new_from_utf8( + scope, + field.as_ref(), + v8::NewStringType::Internalized, + ) + .unwrap() + .into() } diff --git a/serde_v8/src/utils.rs b/serde_v8/src/utils.rs index f564ca5c97c3ac..f9f81837cfba30 100644 --- a/serde_v8/src/utils.rs +++ b/serde_v8/src/utils.rs @@ -1,30 +1,33 @@ use rusty_v8 as v8; use std::sync::Once; -pub fn js_exec<'s>(scope: &mut v8::HandleScope<'s>, src: &str) -> v8::Local<'s, v8::Value> { - let code = v8::String::new(scope, src).unwrap(); - let script = v8::Script::compile(scope, code, None).unwrap(); - script.run(scope).unwrap() +pub fn js_exec<'s>( + scope: &mut v8::HandleScope<'s>, + src: &str, +) -> v8::Local<'s, v8::Value> { + let code = v8::String::new(scope, src).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + script.run(scope).unwrap() } pub fn v8_init() { - let platform = v8::new_default_platform().unwrap(); - v8::V8::initialize_platform(platform); - v8::V8::initialize(); + let platform = v8::new_default_platform().unwrap(); + v8::V8::initialize_platform(platform); + v8::V8::initialize(); } pub fn v8_shutdown() { - unsafe { - v8::V8::dispose(); - } - v8::V8::shutdown_platform(); + unsafe { + v8::V8::dispose(); + } + v8::V8::shutdown_platform(); } pub fn v8_do(f: impl FnOnce()) { - static V8_INIT: Once = Once::new(); - V8_INIT.call_once(|| { - v8_init(); - }); - f(); - // v8_shutdown(); + static V8_INIT: Once = Once::new(); + V8_INIT.call_once(|| { + v8_init(); + }); + f(); + // v8_shutdown(); } diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs index 3079315315a774..e9322bfa4f28f9 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -8,52 +8,52 @@ use serde_v8::utils::{js_exec, v8_init, v8_shutdown}; #[derive(Debug, Deserialize, PartialEq)] struct MathOp { - pub a: u64, - pub b: u64, - pub operator: Option, + pub a: u64, + pub b: u64, + pub operator: Option, } #[test] fn de_basic() { - v8_init(); - - { - let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); - let handle_scope = &mut v8::HandleScope::new(isolate); - let context = v8::Context::new(handle_scope); - let scope = &mut v8::ContextScope::new(handle_scope, context); - - let v = js_exec(scope, "true"); - let b: bool = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(b, true); - - let v = js_exec(scope, "32"); - let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(x32, 32); - - let v = js_exec(scope, "({a: 1, b: 3, c: 'ignored'})"); - let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!( - mop, - MathOp { - a: 1, - b: 3, - operator: None - } - ); - - let v = js_exec(scope, "[1,2,3,4,5]"); - let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(arr, vec![1, 2, 3, 4, 5]); - - let v = js_exec(scope, "['hello', 'world']"); - let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(hi, vec!["hello", "world"]); - - let v: v8::Local = v8::Number::new(scope, 12345.0).into(); - let x: f64 = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(x, 12345.0); - } - - v8_shutdown(); + v8_init(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + let v = js_exec(scope, "true"); + let b: bool = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(b, true); + + let v = js_exec(scope, "32"); + let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(x32, 32); + + let v = js_exec(scope, "({a: 1, b: 3, c: 'ignored'})"); + let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!( + mop, + MathOp { + a: 1, + b: 3, + operator: None + } + ); + + let v = js_exec(scope, "[1,2,3,4,5]"); + let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(arr, vec![1, 2, 3, 4, 5]); + + let v = js_exec(scope, "['hello', 'world']"); + let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(hi, vec!["hello", "world"]); + + let v: v8::Local = v8::Number::new(scope, 12345.0).into(); + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(x, 12345.0); + } + + v8_shutdown(); } diff --git a/serde_v8/tests/magic.rs b/serde_v8/tests/magic.rs index c344a59914ffe1..96b87379f00883 100644 --- a/serde_v8/tests/magic.rs +++ b/serde_v8/tests/magic.rs @@ -3,51 +3,56 @@ use serde_v8; use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; use serde_v8::utils::{js_exec, v8_init, v8_shutdown}; +use std::convert::TryFrom; #[derive(Deserialize)] struct MagicOp<'s> { - pub a: u64, - pub b: u64, - pub c: serde_v8::Value<'s>, - pub operator: Option, + pub a: u64, + pub b: u64, + pub c: serde_v8::Value<'s>, + pub operator: Option, } #[derive(Serialize)] struct MagicContainer<'s> { - pub magic: bool, - pub contains: serde_v8::Value<'s>, + pub magic: bool, + pub contains: serde_v8::Value<'s>, } - #[test] fn magic_basic() { - v8_init(); - - { - let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); - let handle_scope = &mut v8::HandleScope::new(isolate); - let context = v8::Context::new(handle_scope); - let scope = &mut v8::ContextScope::new(handle_scope, context); - - // Decode - let v = js_exec(scope, "({a: 1, b: 3, c: 'abracadabra'})"); - let mop: MagicOp = serde_v8::from_v8(scope, v).unwrap(); - // Check string - let v8_value: v8::Local = mop.c.into(); - let vs = v8::Local::::try_from(v8_value).unwrap(); - let s = vs.to_rust_string_lossy(scope); - assert_eq!(s, "abracadabra"); - - // Encode - let container = MagicContainer { magic: true, contains: v.into() }; - let vc = serde_v8::to_v8(scope, container).unwrap(); - // JSON stringify & check - let json = v8::json::stringify(scope, vc).unwrap(); - let s2 = json.to_rust_string_lossy(scope); - assert_eq!(s2, r#"{"magic":true,"contains":{"a":1,"b":3,"c":"abracadabra"}}"#); - } - - v8_shutdown(); + v8_init(); + + { + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + // Decode + let v = js_exec(scope, "({a: 1, b: 3, c: 'abracadabra'})"); + let mop: MagicOp = serde_v8::from_v8(scope, v).unwrap(); + // Check string + let v8_value: v8::Local = mop.c.into(); + let vs = v8::Local::::try_from(v8_value).unwrap(); + let s = vs.to_rust_string_lossy(scope); + assert_eq!(s, "abracadabra"); + + // Encode + let container = MagicContainer { + magic: true, + contains: v.into(), + }; + let vc = serde_v8::to_v8(scope, container).unwrap(); + // JSON stringify & check + let json = v8::json::stringify(scope, vc).unwrap(); + let s2 = json.to_rust_string_lossy(scope); + assert_eq!( + s2, + r#"{"magic":true,"contains":{"a":1,"b":3,"c":"abracadabra"}}"# + ); + } + + v8_shutdown(); } From a2f620683ce7bda4a1562fc59383718947b762ec Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 18 Mar 2021 22:28:47 +0100 Subject: [PATCH 05/24] clippy --- serde_v8/examples/basic.rs | 1 - serde_v8/src/de.rs | 5 ++--- serde_v8/src/error.rs | 1 - serde_v8/src/magic.rs | 5 ++--- serde_v8/src/ser.rs | 6 +----- serde_v8/tests/de.rs | 3 +-- serde_v8/tests/magic.rs | 1 - 7 files changed, 6 insertions(+), 16 deletions(-) diff --git a/serde_v8/examples/basic.rs b/serde_v8/examples/basic.rs index ce24b320185adb..67bae9f6733af4 100644 --- a/serde_v8/examples/basic.rs +++ b/serde_v8/examples/basic.rs @@ -1,5 +1,4 @@ use rusty_v8 as v8; -use serde_v8; use serde::Deserialize; diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index aea059d4d4faa2..2725c59b523f55 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -268,8 +268,8 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> // Regular struct let obj = v8::Local::::try_from(self.input).unwrap(); let map = ObjectAccess { - fields: fields, - obj: obj, + fields, + obj, pos: 0, scope: self.scope, _cache: None, @@ -387,7 +387,6 @@ fn v8_struct_key<'s>( v8::NewStringType::Internalized, ) .unwrap() - .into() // TODO: consider external strings later // right now non-deduped external strings (without KeyCache) diff --git a/serde_v8/src/error.rs b/serde_v8/src/error.rs index e50ca9e334b0c4..f4230d86665b0a 100644 --- a/serde_v8/src/error.rs +++ b/serde_v8/src/error.rs @@ -1,4 +1,3 @@ -use std; use std::fmt::{self, Display}; use serde::{de, ser}; diff --git a/serde_v8/src/magic.rs b/serde_v8/src/magic.rs index 4d589590ac8d83..ef697027bc3b97 100644 --- a/serde_v8/src/magic.rs +++ b/serde_v8/src/magic.rs @@ -1,11 +1,10 @@ use rusty_v8 as v8; -use serde; use std::fmt; use std::marker::PhantomData; -pub const FIELD: &'static str = "$__v8_magic_value"; -pub const NAME: &'static str = "$__v8_magic_Value"; +pub const FIELD: &str = "$__v8_magic_value"; +pub const NAME: &str = "$__v8_magic_Value"; pub struct Value<'s> { pub v8_value: v8::Local<'s, v8::Value>, diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs index 71e7d8d9ae14fa..07cf702950942f 100644 --- a/serde_v8/src/ser.rs +++ b/serde_v8/src/ser.rs @@ -13,10 +13,7 @@ type JsResult<'s> = Result>; type ScopePtr<'a, 'b> = Rc>>; -pub fn to_v8<'a, 'b, T>( - scope: &mut v8::HandleScope<'a>, - input: T, -) -> JsResult<'a> +pub fn to_v8<'a, T>(scope: &mut v8::HandleScope<'a>, input: T) -> JsResult<'a> where T: Serialize, { @@ -604,5 +601,4 @@ fn v8_struct_key<'s>( v8::NewStringType::Internalized, ) .unwrap() - .into() } diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs index e9322bfa4f28f9..e41850908d6ec8 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -1,5 +1,4 @@ use rusty_v8 as v8; -use serde_v8; use serde::Deserialize; @@ -52,7 +51,7 @@ fn de_basic() { let v: v8::Local = v8::Number::new(scope, 12345.0).into(); let x: f64 = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(x, 12345.0); + assert!((x - 12345.0).abs() < f64::EPSILON); } v8_shutdown(); diff --git a/serde_v8/tests/magic.rs b/serde_v8/tests/magic.rs index 96b87379f00883..2825bcc9adedd8 100644 --- a/serde_v8/tests/magic.rs +++ b/serde_v8/tests/magic.rs @@ -1,5 +1,4 @@ use rusty_v8 as v8; -use serde_v8; use serde::{Deserialize, Serialize}; From 057e9157f043b5958455584a56a89bd9987d660c Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 18 Mar 2021 22:38:56 +0100 Subject: [PATCH 06/24] fmt serde_v8/README.md --- serde_v8/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/serde_v8/README.md b/serde_v8/README.md index 6eaca3f98828de..7fa4fa90c0fb7e 100644 --- a/serde_v8/README.md +++ b/serde_v8/README.md @@ -2,13 +2,17 @@ Serde support for (rusty_)v8 -**WIP:** see [denoland/deno#9540]( https://github.com/denoland/deno/issues/9540) +**WIP:** see [denoland/deno#9540](https://github.com/denoland/deno/issues/9540) ## TODO - [ ] Experiment with KeyCache to optimize struct keys - [ ] Experiment with external v8 strings -- [ ] Explore using [json-stringifier.cc](https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/json/json-stringifier.cc)'s fast-paths for arrays -- [ ] Improve tests to test parity with `serde_json` (should be mostly interchangeable) -- [ ] Consider a `Payload` type that's deserializable by itself (holds scope & value) +- [ ] Explore using + [json-stringifier.cc](https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/json/json-stringifier.cc)'s + fast-paths for arrays +- [ ] Improve tests to test parity with `serde_json` (should be mostly + interchangeable) +- [ ] Consider a `Payload` type that's deserializable by itself (holds scope & + value) - [ ] Ensure we return errors instead of panicking on `.unwrap()`s From 751a83df76e890cfcb1e2669aa1b0658b06af6f9 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 13:02:30 +0100 Subject: [PATCH 07/24] serde_v8: flesh out README --- serde_v8/README.md | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/serde_v8/README.md b/serde_v8/README.md index 7fa4fa90c0fb7e..78703c42d31729 100644 --- a/serde_v8/README.md +++ b/serde_v8/README.md @@ -1,8 +1,45 @@ # serde_v8 -Serde support for (rusty_)v8 +Serde support for encoding/decoding (rusty_)v8 values. -**WIP:** see [denoland/deno#9540](https://github.com/denoland/deno/issues/9540) +Broadly `serde_v8` aims to provide an expressive but ~maximally efficient +encoding layer to biject rust & v8/js values. It's a core component of deno's +op-layer and is used to encode/decode all non-buffer values. + +**Original issue:** +[denoland/deno#9540](https://github.com/denoland/deno/issues/9540) + +## Quickstart + +`serde_v8` fits naturally into the serde ecosystem, so if you've already used +`serde` or `serde_json`, `serde_v8`'s API should be very familiar. + +`serde_v8` exposes two key-functions: + +- `to_v8`: maps `rust->v8`, similar to `serde_json::to_string`, ... +- `from_v8`: maps `v8->rust`, similar to `serde_json::from_str`, ... + +## Best practices + +Whilst `serde_v8` is compatible with `serde_json::Value` it's important to keep +in mind that `serde_json::Value` is essentially a loosely-typed value (think +nested HashMaps), so when writing ops we recommend directly using rust +structs/tuples or primitives, since mapping to `serde_json::Value` will add +extra overhead and result in slower ops. + +I also recommend avoiding unecessary "wrappers", if your op takes a single-keyed +struct, consider unwrapping that as a plain value unless you plan to add fields +in the near-future. + +Instead of returning "nothing" via `Ok(json!({}))`, change your return type to +rust's unit type `()` and returning `Ok(())`, `serde_v8` will efficiently encode +that as a JS `null`. + +## Advanced features + +If you need to mix rust & v8 values in structs/tuples, you can use the special +`serde_v8::Value` type, which will passthrough the original v8 value untouched +when encoding/decoding. ## TODO From 886884d4a9de05bf11d49c9d187dd1e1a6a02f52 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 13:57:46 +0100 Subject: [PATCH 08/24] serde_v8: implement serialize_map To support serializing `json!()` and `serde_json::Value` --- serde_v8/src/ser.rs | 55 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs index 07cf702950942f..045af763902e98 100644 --- a/serde_v8/src/ser.rs +++ b/serde_v8/src/ser.rs @@ -253,6 +253,50 @@ impl<'a, 'b> ser::SerializeStruct for StructSerializers<'a, 'b> { } } +// Serializes to JS Objects, NOT JS Maps ... +pub struct MapSerializer<'a, 'b> { + scope: ScopePtr<'a, 'b>, + obj: v8::Local<'a, v8::Object>, + next_key: Option>, +} + +impl<'a, 'b> MapSerializer<'a, 'b> { + pub fn new(scope: ScopePtr<'a, 'b>) -> Self { + let obj = v8::Object::new(&mut *scope.borrow_mut()); + Self { + scope, + obj, + next_key: None, + } + } +} + +impl<'a, 'b> ser::SerializeMap for MapSerializer<'a, 'b> { + type Ok = JsValue<'a>; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> { + debug_assert!(self.next_key.is_none()); + self.next_key = Some(key.serialize(Serializer::new(self.scope.clone()))?); + Ok(()) + } + + fn serialize_value( + &mut self, + value: &T, + ) -> Result<()> { + let v8_value = value.serialize(Serializer::new(self.scope.clone()))?; + let scope = &mut *self.scope.borrow_mut(); + self.obj.set(scope, self.next_key.take().unwrap(), v8_value); + Ok(()) + } + + fn end(self) -> JsResult<'a> { + debug_assert!(self.next_key.is_none()); + Ok(self.obj.into()) + } +} + #[derive(Clone)] pub struct Serializer<'a, 'b> { scope: ScopePtr<'a, 'b>, @@ -281,7 +325,7 @@ impl<'a, 'b> ser::Serializer for Serializer<'a, 'b> { type SerializeTupleStruct = ArraySerializer<'a, 'b>; type SerializeTupleVariant = VariantSerializer<'a, 'b, ArraySerializer<'a, 'b>>; - type SerializeMap = Impossible, Error>; + type SerializeMap = MapSerializer<'a, 'b>; type SerializeStruct = StructSerializers<'a, 'b>; type SerializeStructVariant = VariantSerializer<'a, 'b, StructSerializers<'a, 'b>>; @@ -407,10 +451,11 @@ impl<'a, 'b> ser::Serializer for Serializer<'a, 'b> { } fn serialize_map(self, _len: Option) -> Result { - // TODO: serialize Maps (HashMap or BTreeMap) to v8 objects, - // ideally JS Maps since they're lighter and better suited for K/V data - // only allow certain keys (e.g: strings and numbers) - unimplemented!() + // Serializes a rust Map (e.g: BTreeMap, HashMap) to a v8 Object + // TODO: consider allowing serializing to v8 Maps (e.g: via a magic type) + // since they're lighter and better suited for K/V data + // and maybe restrict keys (e.g: strings and numbers) + Ok(MapSerializer::new(self.scope)) } /// Serialises Rust typed structs into plain JS objects. From f37a87bc5bb7150eafb5cf40a2b4d8852620e86e Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 13:59:16 +0100 Subject: [PATCH 09/24] serde_v8: remove pointless tests/lib.rs --- serde_v8/tests/lib.rs | 1 - 1 file changed, 1 deletion(-) delete mode 100644 serde_v8/tests/lib.rs diff --git a/serde_v8/tests/lib.rs b/serde_v8/tests/lib.rs deleted file mode 100644 index 8208deb41bd77e..00000000000000 --- a/serde_v8/tests/lib.rs +++ /dev/null @@ -1 +0,0 @@ -mod de; From 36c59dcd89eb09c020266ce011b0b33634a99941 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 14:37:53 +0100 Subject: [PATCH 10/24] serde_v8: modularize and extend tests/de --- serde_v8/tests/de.rs | 99 +++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs index e41850908d6ec8..003573514b32ee 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -2,8 +2,7 @@ use rusty_v8 as v8; use serde::Deserialize; -use serde_v8::utils::{js_exec, v8_init, v8_shutdown}; -//::{v8_init, v8_shutdown}; +use serde_v8::utils::{js_exec, v8_do}; #[derive(Debug, Deserialize, PartialEq)] struct MathOp { @@ -12,47 +11,71 @@ struct MathOp { pub operator: Option, } -#[test] -fn de_basic() { - v8_init(); - - { +fn dedo( + code: &str, + f: impl FnOnce(&mut v8::HandleScope, v8::Local), +) { + v8_do(|| { let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); let handle_scope = &mut v8::HandleScope::new(isolate); let context = v8::Context::new(handle_scope); let scope = &mut v8::ContextScope::new(handle_scope, context); + let v = js_exec(scope, code); - let v = js_exec(scope, "true"); - let b: bool = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(b, true); - - let v = js_exec(scope, "32"); - let x32: u64 = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(x32, 32); - - let v = js_exec(scope, "({a: 1, b: 3, c: 'ignored'})"); - let mop: MathOp = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!( - mop, - MathOp { - a: 1, - b: 3, - operator: None - } - ); - - let v = js_exec(scope, "[1,2,3,4,5]"); - let arr: Vec = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(arr, vec![1, 2, 3, 4, 5]); - - let v = js_exec(scope, "['hello', 'world']"); - let hi: Vec = serde_v8::from_v8(scope, v).unwrap(); - assert_eq!(hi, vec!["hello", "world"]); - - let v: v8::Local = v8::Number::new(scope, 12345.0).into(); - let x: f64 = serde_v8::from_v8(scope, v).unwrap(); - assert!((x - 12345.0).abs() < f64::EPSILON); + f(scope, v); + }) +} + +macro_rules! detest { + ($fn_name:ident, $t:ty, $src:expr, $rust:expr) => { + #[test] + fn $fn_name() { + dedo($src, |scope, v| { + let rt = serde_v8::from_v8(scope, v); + assert!(rt.is_ok(), format!("from_v8(\"{}\"): {:?}", $src, rt.err())); + let t: $t = rt.unwrap(); + assert_eq!(t, $rust); + }); + } + }; +} + +detest!(de_option_some, Option, "true", Some(true)); +detest!(de_option_null, Option, "null", None); +detest!(de_option_undefined, Option, "undefined", None); +detest!(de_unit_null, (), "null", ()); +detest!(de_unit_undefined, (), "undefined", ()); +detest!(de_bool, bool, "true", true); +detest!(de_u64, u64, "32", 32); +detest!(de_string, String, "'Hello'", "Hello".to_owned()); +detest!(de_vec_u64, Vec, "[1,2,3,4,5]", vec![1, 2, 3, 4, 5]); +detest!( + de_vec_str, + Vec, + "['hello', 'world']", + vec!["hello".to_owned(), "world".to_owned()] +); +detest!( + de_tuple, + (u64, bool, ()), + "[123, true, null]", + (123, true, ()) +); +detest!( + de_mathop, + MathOp, + "({a: 1, b: 3, c: 'ignored'})", + MathOp { + a: 1, + b: 3, + operator: None } +); - v8_shutdown(); +#[test] +fn de_f64() { + dedo("12345.0", |scope, v| { + let x: f64 = serde_v8::from_v8(scope, v).unwrap(); + assert!((x - 12345.0).abs() < f64::EPSILON); + }); } From 1a1ac2a3348e6056f26b808988261969a7cf07b4 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 14:48:44 +0100 Subject: [PATCH 11/24] serde_v8: test de_map --- serde_v8/tests/de.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs index 003573514b32ee..9556f939c168a6 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -79,3 +79,16 @@ fn de_f64() { assert!((x - 12345.0).abs() < f64::EPSILON); }); } + +#[test] +fn de_map() { + use std::collections::HashMap; + + dedo("({a: 1, b: 2, c: 3})", |scope, v| { + let map: HashMap = serde_v8::from_v8(scope, v).unwrap(); + assert_eq!(map.get("a").cloned(), Some(1)); + assert_eq!(map.get("b").cloned(), Some(2)); + assert_eq!(map.get("c").cloned(), Some(3)); + assert_eq!(map.get("nada"), None); + }) +} From 084b22bc9d3aaa1069f20a89ee5ddb269645ac8e Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 15:40:41 +0100 Subject: [PATCH 12/24] serde_v8: support deserializing undefined to unit type Fixes de_unit_undefined test --- serde_v8/src/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index 2725c59b523f55..60daa561f061e8 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -167,7 +167,7 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> where V: Visitor<'de>, { - if self.input.is_null() { + if self.input.is_null_or_undefined() { visitor.visit_unit() } else { Err(Error::ExpectedNull) From 3a05f88c4ebdcc576cc9e992885ee900e8f45238 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 17:26:39 +0100 Subject: [PATCH 13/24] serde_v8: add tests/ser --- serde_v8/tests/ser.rs | 115 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 serde_v8/tests/ser.rs diff --git a/serde_v8/tests/ser.rs b/serde_v8/tests/ser.rs new file mode 100644 index 00000000000000..e4eae4129e4b03 --- /dev/null +++ b/serde_v8/tests/ser.rs @@ -0,0 +1,115 @@ +use rusty_v8 as v8; + +use serde::Serialize; + +use serde_v8::utils::{js_exec, v8_do}; + +#[derive(Debug, Serialize, PartialEq)] +struct MathOp { + pub a: u64, + pub b: u64, + pub operator: Option, +} + +// Utility JS code (obj equality, etc...) +const JS_UTILS: &str = r#" +// Shallow obj equality (don't use deep objs for now) +function objEqual(object1, object2) { + const keys1 = Object.keys(object1); + const keys2 = Object.keys(object2); + + if (keys1.length !== keys2.length) { + return false; + } + + for (let key of keys1) { + if (object1[key] !== object2[key]) { + return false; + } + } + + return true; +} + +function arrEqual(a, b) { + return Array.isArray(a) && + Array.isArray(b) && + a.length === b.length && + a.every((val, index) => val === b[index]); +} +"#; + +fn sercheck(val: T, code: &str) -> bool { + let mut equal = false; + + v8_do(|| { + // Setup isolate + let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); + let handle_scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(handle_scope); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + // Set value as "x" in global scope + let global = context.global(scope); + let v8_key = serde_v8::to_v8(scope, "x").unwrap(); + let v8_val = serde_v8::to_v8(scope, val).unwrap(); + global.set(scope, v8_key, v8_val); + + // Load util functions + js_exec(scope, JS_UTILS); + // Execute equality check in JS (e.g: x == ...) + let v = js_exec(scope, code); + // Cast to bool + equal = serde_v8::from_v8(scope, v).unwrap(); + }); + + equal +} + +macro_rules! sertest { + ($fn_name:ident, $rust:expr, $src:expr) => { + #[test] + fn $fn_name() { + assert!( + sercheck($rust, $src), + format!("Expected: {} where x={:?}", $src, $rust), + ); + } + }; +} + +sertest!(ser_option_some, Some(true), "x === true"); +sertest!(ser_option_null, None as Option, "x === null"); +sertest!(ser_unit_null, (), "x === null"); +sertest!(ser_bool, true, "x === true"); +sertest!(ser_u64, 32, "x === 32"); +sertest!(ser_f64, 12345.0, "x === 12345.0"); +sertest!(ser_string, "Hello".to_owned(), "x === 'Hello'"); +sertest!(ser_vec_u64, vec![1, 2, 3, 4, 5], "arrEqual(x, [1,2,3,4,5])"); +sertest!( + ser_vec_string, + vec!["hello".to_owned(), "world".to_owned(),], + "arrEqual(x, ['hello', 'world'])" +); +sertest!(ser_tuple, (123, true, ()), "arrEqual(x, [123, true, null])"); +sertest!( + ser_mathop, + MathOp { + a: 1, + b: 3, + operator: None + }, + "objEqual(x, {a: 1, b: 3, operator: null})" +); + +use std::collections::BTreeMap; + +sertest!( + ser_map, + { + let map: BTreeMap<&str, u32> = + vec![("a", 1), ("b", 2), ("c", 3)].drain(..).collect(); + map + }, + "objEqual(x, {a: 1, b: 2, c: 3})" +); From 27c24e0c1552835a68bc337fad5bd2df13278411 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 17:30:03 +0100 Subject: [PATCH 14/24] serde_v8: drop isolate use for BTreeMap, use full path --- serde_v8/tests/ser.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/serde_v8/tests/ser.rs b/serde_v8/tests/ser.rs index e4eae4129e4b03..cc87d4272f5e4f 100644 --- a/serde_v8/tests/ser.rs +++ b/serde_v8/tests/ser.rs @@ -102,12 +102,10 @@ sertest!( "objEqual(x, {a: 1, b: 3, operator: null})" ); -use std::collections::BTreeMap; - sertest!( ser_map, { - let map: BTreeMap<&str, u32> = + let map: std::collections::BTreeMap<&str, u32> = vec![("a", 1), ("b", 2), ("c", 3)].drain(..).collect(); map }, From 8ef301cb9273d02b13212679acbdcb4e800c6c19 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 20:21:06 +0100 Subject: [PATCH 15/24] serde_v8: implement deserialize_map --- serde_v8/src/de.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index 60daa561f061e8..d7f99237f68b0e 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -244,7 +244,28 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> self.deserialize_tuple(len, visitor) } - wip!(deserialize_map); + fn deserialize_map(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Assume object, then get_own_property_names + let obj = v8::Local::::try_from(self.input).unwrap(); + let prop_names = obj.get_own_property_names(self.scope); + let mut keys: Vec = match prop_names { + Some(names) => from_v8(self.scope, names.into()).unwrap(), + None => vec![], + }; + let keys: Vec> = + keys.drain(..).map(|x| x.into()).collect(); + + let map = MapAccess { + obj, + keys, + pos: 0, + scope: self.scope, + }; + visitor.visit_map(map) + } fn deserialize_struct( self, @@ -309,6 +330,65 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> } } +struct MapAccess<'a, 'b, 's> { + obj: v8::Local<'a, v8::Object>, + scope: &'b mut v8::HandleScope<'s>, + keys: Vec>, + pos: usize, +} + +impl<'de> de::MapAccess<'de> for MapAccess<'_, '_, '_> { + type Error = Error; + + fn next_key_seed>( + &mut self, + seed: K, + ) -> Result> { + Ok(match self.keys.get(self.pos) { + Some(key) => { + let mut deserializer = Deserializer::new(self.scope, *key, None); + Some(seed.deserialize(&mut deserializer)?) + } + None => None, + }) + } + + fn next_value_seed>( + &mut self, + seed: V, + ) -> Result { + if self.pos >= self.keys.len() { + return Err(Error::LengthMismatch); + } + let key = self.keys[self.pos]; + self.pos += 1; + let v8_val = self.obj.get(self.scope, key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + seed.deserialize(&mut deserializer) + } + + fn next_entry_seed< + K: de::DeserializeSeed<'de>, + V: de::DeserializeSeed<'de>, + >( + &mut self, + kseed: K, + vseed: V, + ) -> Result> { + if self.pos >= self.keys.len() { + return Ok(None); + } + let v8_key = self.keys[self.pos]; + self.pos += 1; + let mut kdeserializer = Deserializer::new(self.scope, v8_key, None); + Ok(Some((kseed.deserialize(&mut kdeserializer)?, { + let v8_val = self.obj.get(self.scope, v8_key).unwrap(); + let mut deserializer = Deserializer::new(self.scope, v8_val, None); + vseed.deserialize(&mut deserializer)? + }))) + } +} + struct ObjectAccess<'a, 'b, 's> { obj: v8::Local<'a, v8::Object>, scope: &'b mut v8::HandleScope<'s>, From 8ee0fe2de03497c62154f0bb170f540bfc844138 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 20:42:28 +0100 Subject: [PATCH 16/24] serde_v8: add json compatibility tests for de --- serde_v8/tests/de.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs index 9556f939c168a6..0f096bb990add4 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -92,3 +92,78 @@ fn de_map() { assert_eq!(map.get("nada"), None); }) } + +//// +// JSON tests: serde_json::Value compatibility +//// + +detest!( + de_json_null, + serde_json::Value, + "null", + serde_json::Value::Null +); +detest!( + de_json_bool, + serde_json::Value, + "true", + serde_json::Value::Bool(true) +); +detest!( + de_json_int, + serde_json::Value, + "123", + serde_json::Value::Number(serde_json::Number::from_f64(123.0).unwrap()) +); +detest!( + de_json_float, + serde_json::Value, + "3.14159", + serde_json::Value::Number(serde_json::Number::from_f64(3.14159).unwrap()) +); +detest!( + de_json_string, + serde_json::Value, + "'Hello'", + serde_json::Value::String("Hello".to_string()) +); +detest!( + de_json_vec_string, + serde_json::Value, + "['Hello', 'World']", + serde_json::Value::Array(vec![ + serde_json::Value::String("Hello".to_string()), + serde_json::Value::String("World".to_string()) + ]) +); +detest!( + de_json_tuple, + serde_json::Value, + "[true, 'World', 3.14159, null]", + serde_json::Value::Array(vec![ + serde_json::Value::Bool(true), + serde_json::Value::String("World".to_string()), + serde_json::Value::Number(serde_json::Number::from_f64(3.14159).unwrap()), + serde_json::Value::Null, + ]) +); +detest!( + de_json_object, + serde_json::Value, + "({a: 1, b: 'hello', c: true})", + serde_json::Value::Object( + vec![ + ( + "a".to_string(), + serde_json::Value::Number(serde_json::Number::from_f64(1.0).unwrap()), + ), + ( + "b".to_string(), + serde_json::Value::String("hello".to_string()), + ), + ("c".to_string(), serde_json::Value::Bool(true),), + ] + .drain(..) + .collect() + ) +); From 4948051edce118996f08b7191d56723bb4703b89 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 20:43:20 +0100 Subject: [PATCH 17/24] serde_v8: implement deserialize_str Passthrough to deserialize_string since we only return owned strings --- serde_v8/src/de.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index d7f99237f68b0e..46785a3fa37834 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -135,7 +135,13 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> } wip!(deserialize_char); - wip!(deserialize_str); + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_string(visitor) + } fn deserialize_string(self, visitor: V) -> Result where From c6d9159dfd31cb9d1fb6b7f8414123ed541d20b4 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 20:55:13 +0100 Subject: [PATCH 18/24] clippy --- serde_v8/tests/de.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/serde_v8/tests/de.rs b/serde_v8/tests/de.rs index 0f096bb990add4..d0721c0f84631f 100644 --- a/serde_v8/tests/de.rs +++ b/serde_v8/tests/de.rs @@ -118,8 +118,8 @@ detest!( detest!( de_json_float, serde_json::Value, - "3.14159", - serde_json::Value::Number(serde_json::Number::from_f64(3.14159).unwrap()) + "123.45", + serde_json::Value::Number(serde_json::Number::from_f64(123.45).unwrap()) ); detest!( de_json_string, @@ -139,11 +139,11 @@ detest!( detest!( de_json_tuple, serde_json::Value, - "[true, 'World', 3.14159, null]", + "[true, 'World', 123.45, null]", serde_json::Value::Array(vec![ serde_json::Value::Bool(true), serde_json::Value::String("World".to_string()), - serde_json::Value::Number(serde_json::Number::from_f64(3.14159).unwrap()), + serde_json::Value::Number(serde_json::Number::from_f64(123.45).unwrap()), serde_json::Value::Null, ]) ); From 02f12f243fc7f8bd1825b765d1e2de3d3c6c3c2a Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 24 Mar 2021 20:55:21 +0100 Subject: [PATCH 19/24] serde_v8: add json compatibility tests for ser --- serde_v8/tests/ser.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/serde_v8/tests/ser.rs b/serde_v8/tests/ser.rs index cc87d4272f5e4f..6ce0bcfbef6143 100644 --- a/serde_v8/tests/ser.rs +++ b/serde_v8/tests/ser.rs @@ -1,7 +1,7 @@ use rusty_v8 as v8; use serde::Serialize; - +use serde_json::json; use serde_v8::utils::{js_exec, v8_do}; #[derive(Debug, Serialize, PartialEq)] @@ -111,3 +111,33 @@ sertest!( }, "objEqual(x, {a: 1, b: 2, c: 3})" ); + +//// +// JSON tests: json!() compatibility +//// +sertest!(ser_json_bool, json!(true), "x === true"); +sertest!(ser_json_null, json!(null), "x === null"); +sertest!(ser_json_int, json!(123), "x === 123"); +sertest!(ser_json_f64, json!(123.45), "x === 123.45"); +sertest!(ser_json_string, json!("Hello World"), "x === 'Hello World'"); +sertest!(ser_json_obj_empty, json!({}), "objEqual(x, {})"); +sertest!( + ser_json_obj, + json!({"a": 1, "b": 2, "c": true}), + "objEqual(x, {a: 1, b: 2, c: true})" +); +sertest!( + ser_json_vec_int, + json!([1, 2, 3, 4, 5]), + "arrEqual(x, [1,2,3,4,5])" +); +sertest!( + ser_json_vec_string, + json!(["Goodbye", "Dinosaurs 👋☄️"]), + "arrEqual(x, ['Goodbye', 'Dinosaurs 👋☄️'])" +); +sertest!( + ser_json_tuple, + json!([true, 42, "nabla"]), + "arrEqual(x, [true, 42, 'nabla'])" +); From 30bdbcb28dd1b97f77acc9bb8d7a02058c195687 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 25 Mar 2021 13:05:11 +0100 Subject: [PATCH 20/24] serde_v8/tests: simplify objEqual utility func --- serde_v8/tests/ser.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/serde_v8/tests/ser.rs b/serde_v8/tests/ser.rs index 6ce0bcfbef6143..0c1b95d0cf20c1 100644 --- a/serde_v8/tests/ser.rs +++ b/serde_v8/tests/ser.rs @@ -14,21 +14,11 @@ struct MathOp { // Utility JS code (obj equality, etc...) const JS_UTILS: &str = r#" // Shallow obj equality (don't use deep objs for now) -function objEqual(object1, object2) { - const keys1 = Object.keys(object1); - const keys2 = Object.keys(object2); +function objEqual(a, b) { + const ka = Object.keys(a); + const kb = Object.keys(b); - if (keys1.length !== keys2.length) { - return false; - } - - for (let key of keys1) { - if (object1[key] !== object2[key]) { - return false; - } - } - - return true; + return ka.length === kb.length && ka.every(k => a[k] === b[k]); } function arrEqual(a, b) { From cbbb7a7b04b6d143a2bf92ac7400c45380f26a4e Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 25 Mar 2021 14:04:15 +0100 Subject: [PATCH 21/24] serde_v8/tests: further simplify obj/array equality funcs --- serde_v8/tests/ser.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/serde_v8/tests/ser.rs b/serde_v8/tests/ser.rs index 0c1b95d0cf20c1..1da8f6ff23abe1 100644 --- a/serde_v8/tests/ser.rs +++ b/serde_v8/tests/ser.rs @@ -17,15 +17,11 @@ const JS_UTILS: &str = r#" function objEqual(a, b) { const ka = Object.keys(a); const kb = Object.keys(b); - return ka.length === kb.length && ka.every(k => a[k] === b[k]); } function arrEqual(a, b) { - return Array.isArray(a) && - Array.isArray(b) && - a.length === b.length && - a.every((val, index) => val === b[index]); + return a.length === b.length && a.every((v, i) => v === b[i]); } "#; From 22ecb866c1ec87dd9de509e1bb8f445c53065bbe Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Fri, 26 Mar 2021 01:27:51 +0100 Subject: [PATCH 22/24] serde_v8: improve error messages --- serde_v8/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serde_v8/src/error.rs b/serde_v8/src/error.rs index f4230d86665b0a..effec4138fb729 100644 --- a/serde_v8/src/error.rs +++ b/serde_v8/src/error.rs @@ -35,7 +35,7 @@ impl Display for Error { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { match self { Error::Message(msg) => formatter.write_str(msg), - _ => formatter.write_str("serde_v8 error: TODO"), + err => formatter.write_str(format!("serde_v8 error: {:?}", err).as_ref()), } } } From a26f5ea24631504849d813c6a4b404c92b324664 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Fri, 26 Mar 2021 01:37:45 +0100 Subject: [PATCH 23/24] serde_v8: add comments explaining passthrough value --- serde_v8/src/magic.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/serde_v8/src/magic.rs b/serde_v8/src/magic.rs index ef697027bc3b97..8e6b2dfe55cd16 100644 --- a/serde_v8/src/magic.rs +++ b/serde_v8/src/magic.rs @@ -6,6 +6,13 @@ use std::marker::PhantomData; pub const FIELD: &str = "$__v8_magic_value"; pub const NAME: &str = "$__v8_magic_Value"; +/// serde_v8::Value allows passing through `v8::Value`s untouched +/// when encoding/decoding and allows mixing rust & v8 values in +/// structs, tuples... +/// The implementation mainly breaks down to: +/// 1. Transmuting between u64 <> serde_v8::Value +/// 2. Using special struct/field names to detect these values +/// 3. Then serde "boilerplate" pub struct Value<'s> { pub v8_value: v8::Local<'s, v8::Value>, } From 90709c319fecb09e7e5ee33a52da8b61184943e4 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Fri, 26 Mar 2021 02:18:13 +0100 Subject: [PATCH 24/24] serde_v8: move common key code to keys.rs --- serde_v8/src/de.rs | 27 +-------------------------- serde_v8/src/keys.rs | 32 ++++++++++++++++++++++++++++++++ serde_v8/src/lib.rs | 4 +++- serde_v8/src/ser.rs | 19 +------------------ 4 files changed, 37 insertions(+), 45 deletions(-) create mode 100644 serde_v8/src/keys.rs diff --git a/serde_v8/src/de.rs b/serde_v8/src/de.rs index 46785a3fa37834..22d66f7ce83dfd 100644 --- a/serde_v8/src/de.rs +++ b/serde_v8/src/de.rs @@ -2,10 +2,10 @@ use rusty_v8 as v8; use serde::de::{self, Visitor}; use serde::Deserialize; -use std::collections::HashMap; use std::convert::TryFrom; use crate::error::{Error, Result}; +use crate::keys::{v8_struct_key, KeyCache}; use crate::payload::ValueType; use crate::magic; @@ -15,7 +15,6 @@ pub struct Deserializer<'a, 'b, 's> { scope: &'b mut v8::HandleScope<'s>, _key_cache: Option<&'b mut KeyCache>, } -pub type KeyCache = HashMap<&'static str, v8::Global>; impl<'a, 'b, 's> Deserializer<'a, 'b, 's> { pub fn new( @@ -457,30 +456,6 @@ impl<'de, 'a, 'b, 's> de::MapAccess<'de> for ObjectAccess<'a, 'b, 's> { } } -// creates an optimized v8::String for a struct field -// TODO: experiment with external strings -// TODO: evaluate if own KeyCache is better than v8's dedupe -fn v8_struct_key<'s>( - scope: &mut v8::HandleScope<'s>, - field: &'static str, -) -> v8::Local<'s, v8::String> { - // Internalized v8 strings are significantly faster than "normal" v8 strings - // since v8 deduplicates re-used strings minimizing new allocations - // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 - v8::String::new_from_utf8( - scope, - field.as_ref(), - v8::NewStringType::Internalized, - ) - .unwrap() - - // TODO: consider external strings later - // right now non-deduped external strings (without KeyCache) - // are slower than the deduped internalized strings by ~2.5x - // since they're a new string in v8's eyes and needs to be hashed, etc... - // v8::String::new_external_onebyte_static(scope, field).unwrap() -} - struct SeqAccess<'a, 'b, 's> { obj: v8::Local<'a, v8::Object>, scope: &'b mut v8::HandleScope<'s>, diff --git a/serde_v8/src/keys.rs b/serde_v8/src/keys.rs new file mode 100644 index 00000000000000..3e17e1c52e49c8 --- /dev/null +++ b/serde_v8/src/keys.rs @@ -0,0 +1,32 @@ +use rusty_v8 as v8; + +use std::collections::HashMap; + +// KeyCache stores a pool struct keys mapped to v8, +// to minimize allocs and speed up decoding/encoding `v8::Object`s +// TODO: experiment with in from_v8/to_v8 +pub struct KeyCache(HashMap<&'static str, v8::Global>); + +// creates an optimized v8::String for a struct field +// TODO: experiment with external strings +// TODO: evaluate if own KeyCache is better than v8's dedupe +pub fn v8_struct_key<'s>( + scope: &mut v8::HandleScope<'s>, + field: &'static str, +) -> v8::Local<'s, v8::String> { + // Internalized v8 strings are significantly faster than "normal" v8 strings + // since v8 deduplicates re-used strings minimizing new allocations + // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 + v8::String::new_from_utf8( + scope, + field.as_ref(), + v8::NewStringType::Internalized, + ) + .unwrap() + + // TODO: consider external strings later + // right now non-deduped external strings (without KeyCache) + // are slower than the deduped internalized strings by ~2.5x + // since they're a new string in v8's eyes and needs to be hashed, etc... + // v8::String::new_external_onebyte_static(scope, field).unwrap() +} diff --git a/serde_v8/src/lib.rs b/serde_v8/src/lib.rs index 1484339bbbefcf..cd20d6d17ae7f5 100644 --- a/serde_v8/src/lib.rs +++ b/serde_v8/src/lib.rs @@ -1,11 +1,13 @@ mod de; mod error; +mod keys; mod magic; mod payload; mod ser; pub mod utils; -pub use de::{from_v8, from_v8_cached, Deserializer, KeyCache}; +pub use de::{from_v8, from_v8_cached, Deserializer}; pub use error::{Error, Result}; +pub use keys::KeyCache; pub use magic::Value; pub use ser::{to_v8, Serializer}; diff --git a/serde_v8/src/ser.rs b/serde_v8/src/ser.rs index 045af763902e98..3970ddaab5ff36 100644 --- a/serde_v8/src/ser.rs +++ b/serde_v8/src/ser.rs @@ -6,6 +6,7 @@ use std::cell::RefCell; use std::rc::Rc; use crate::error::{Error, Result}; +use crate::keys::v8_struct_key; use crate::magic; type JsValue<'s> = v8::Local<'s, v8::Value>; @@ -629,21 +630,3 @@ impl<'a, 'b> ser::Serializer for MagicTransmuter<'a, 'b> { unreachable!(); } } - -// creates an optimized v8::String for a struct field -// TODO: experiment with external strings -// TODO: evaluate if own KeyCache is better than v8's dedupe -fn v8_struct_key<'s>( - scope: &mut v8::HandleScope<'s>, - field: &'static str, -) -> v8::Local<'s, v8::String> { - // Internalized v8 strings are significantly faster than "normal" v8 strings - // since v8 deduplicates re-used strings minimizing new allocations - // see: https://github.com/v8/v8/blob/14ac92e02cc3db38131a57e75e2392529f405f2f/include/v8.h#L3165-L3171 - v8::String::new_from_utf8( - scope, - field.as_ref(), - v8::NewStringType::Internalized, - ) - .unwrap() -}