Introduce serde_v8 (#9722)

This commit is contained in:
Aaron O'Mullan 2021-03-26 03:36:46 +01:00 committed by GitHub
parent e7954413e1
commit 3d2e05dc7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1887 additions and 102 deletions

10
Cargo.lock generated
View File

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

View File

@ -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
opt-level = 3

View File

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

View File

@ -17,6 +17,9 @@ use std::option::Option;
use url::Url;
use v8::MapFnTo;
use serde::Serialize;
use serde_v8::to_v8;
lazy_static! {
pub static ref EXTERNAL_REFERENCES: v8::ExternalReferences =
v8::ExternalReferences::new(&[
@ -477,16 +480,17 @@ fn eval_context(
let url = v8::Local::<v8::String>::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<serde_v8::Value<'s>>, Option<ErrInfo<'s>>);
#[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,
@ -499,39 +503,15 @@ 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 output = Output(
None,
Some(ErrInfo {
thrown: exception.into(),
is_native_error: exception.is_native_error(),
is_compile_error: true,
}),
);
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());
rv.set(to_v8(tc_scope, output).unwrap());
return;
}
@ -540,48 +520,20 @@ 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 output = Output(
None,
Some(ErrInfo {
thrown: exception.into(),
is_native_error: exception.is_native_error(),
is_compile_error: false,
}),
);
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());
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(
@ -850,30 +802,24 @@ fn get_promise_details(
}
};
let promise_details = v8::Array::new(scope, 2);
#[derive(Serialize)]
struct PromiseDetails<'s>(u32, Option<serde_v8::Value<'s>>);
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(),
);
}
}
}
@ -912,14 +858,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<str>) {

16
serde_v8/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "serde_v8"
version = "0.0.0"
authors = ["Aaron O'Mullan <aaron.omullan@gmail.com>"]
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"

55
serde_v8/README.md Normal file
View File

@ -0,0 +1,55 @@
# serde_v8
Serde support for encoding/decoding (rusty_)v8 values.
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
- [ ] 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

View File

@ -0,0 +1,57 @@
use rusty_v8 as v8;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct MathOp {
pub a: u64,
pub b: u64,
pub operator: Option<String>,
}
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<u64> = serde_v8::from_v8(scope, v).unwrap();
println!("arr = {:?}", arr);
let v = exec(scope, "['hello', 'world']");
let hi: Vec<String> = serde_v8::from_v8(scope, v).unwrap();
println!("hi = {:?}", hi);
let v: v8::Local<v8::Value> = 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();
}

484
serde_v8/src/de.rs Normal file
View File

@ -0,0 +1,484 @@
use rusty_v8 as v8;
use serde::de::{self, Visitor};
use serde::Deserialize;
use std::convert::TryFrom;
use crate::error::{Error, Result};
use crate::keys::{v8_struct_key, KeyCache};
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>,
}
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<T>
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<T>
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<V>(self, _v: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
unimplemented!()
}
};
}
macro_rules! deserialize_signed {
($dmethod:ident, $vmethod:ident, $t:tt) => {
fn $dmethod<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_f32(self.input.number_value(&mut self.scope).unwrap() as f32)
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_f64(self.input.number_value(&mut self.scope).unwrap())
}
wip!(deserialize_char);
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_string(visitor)
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
if self.input.is_null_or_undefined() {
visitor.visit_none()
} else {
visitor.visit_some(self)
}
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
if self.input.is_null_or_undefined() {
visitor.visit_unit()
} else {
Err(Error::ExpectedNull)
}
}
fn deserialize_unit_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value>
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<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let arr = v8::Local::<v8::Array>::try_from(self.input).unwrap();
let len = arr.length();
let obj = v8::Local::<v8::Object>::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<V>(self, len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// TODO: error on length mismatch
let obj = v8::Local::<v8::Object>::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<V>(
self,
_name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_tuple(len, visitor)
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
// Assume object, then get_own_property_names
let obj = v8::Local::<v8::Object>::try_from(self.input).unwrap();
let prop_names = obj.get_own_property_names(self.scope);
let mut keys: Vec<magic::Value> = match prop_names {
Some(names) => from_v8(self.scope, names.into()).unwrap(),
None => vec![],
};
let keys: Vec<v8::Local<v8::Value>> =
keys.drain(..).map(|x| x.into()).collect();
let map = MapAccess {
obj,
keys,
pos: 0,
scope: self.scope,
};
visitor.visit_map(map)
}
fn deserialize_struct<V>(
self,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
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::<v8::Object>::try_from(self.input).unwrap();
let map = ObjectAccess {
fields,
obj,
pos: 0,
scope: self.scope,
_cache: None,
};
visitor.visit_map(map)
}
fn deserialize_enum<V>(
self,
_name: &str,
_variants: &'static [&'static str],
_visitor: V,
) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_none()
}
}
struct MapAccess<'a, 'b, 's> {
obj: v8::Local<'a, v8::Object>,
scope: &'b mut v8::HandleScope<'s>,
keys: Vec<v8::Local<'a, v8::Value>>,
pos: usize,
}
impl<'de> de::MapAccess<'de> for MapAccess<'_, '_, '_> {
type Error = Error;
fn next_key_seed<K: de::DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>> {
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<V: de::DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value> {
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<Option<(K::Value, V::Value)>> {
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>,
fields: &'static [&'static str],
pos: usize,
_cache: Option<&'b mut KeyCache>,
}
fn str_deserializer(s: &str) -> de::value::StrDeserializer<Error> {
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<K: de::DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>> {
Ok(match self.fields.get(self.pos) {
Some(&field) => Some(seed.deserialize(str_deserializer(field))?),
None => None,
})
}
fn next_value_seed<V: de::DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value> {
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<
K: de::DeserializeSeed<'de>,
V: de::DeserializeSeed<'de>,
>(
&mut self,
kseed: K,
vseed: V,
) -> Result<Option<(K::Value, V::Value)>> {
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)?
})))
}
}
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<T: de::DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>> {
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)
}
}
}

43
serde_v8/src/error.rs Normal file
View File

@ -0,0 +1,43 @@
use std::fmt::{self, Display};
use serde::{de, ser};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Message(String),
ExpectedBoolean,
ExpectedInteger,
ExpectedString,
ExpectedNull,
ExpectedArray,
ExpectedMap,
ExpectedEnum,
LengthMismatch,
}
impl ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
Error::Message(msg.to_string())
}
}
impl de::Error for Error {
fn custom<T: Display>(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),
err => formatter.write_str(format!("serde_v8 error: {:?}", err).as_ref()),
}
}
}
impl std::error::Error for Error {}

32
serde_v8/src/keys.rs Normal file
View File

@ -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<v8::String>>);
// 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()
}

13
serde_v8/src/lib.rs Normal file
View File

@ -0,0 +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};
pub use error::{Error, Result};
pub use keys::KeyCache;
pub use magic::Value;
pub use ser::{to_v8, Serializer};

78
serde_v8/src/magic.rs Normal file
View File

@ -0,0 +1,78 @@
use rusty_v8 as v8;
use std::fmt;
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>,
}
impl<'s> From<v8::Local<'s, v8::Value>> for Value<'s> {
fn from(v8_value: v8::Local<'s, v8::Value>) -> Self {
Self { v8_value }
}
}
impl<'s> From<Value<'s>> for v8::Local<'s, v8::Value> {
fn from(v: Value<'s>) -> Self {
v.v8_value
}
}
impl serde::Serialize for Value<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Value<'s>, 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<E>(self, v: u64) -> Result<Self::Value, E>
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)
}
}

33
serde_v8/src/payload.rs Normal file
View File

@ -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<v8::Value>) -> 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")
}
}

632
serde_v8/src/ser.rs Normal file
View File

@ -0,0 +1,632 @@
use rusty_v8 as v8;
use serde::ser;
use serde::ser::{Impossible, Serialize};
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>;
type JsResult<'s> = Result<JsValue<'s>>;
type ScopePtr<'a, 'b> = Rc<RefCell<v8::EscapableHandleScope<'a, 'b>>>;
pub fn to_v8<'a, 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<Ok = JsValue<'a>, Error = Error>,
{
type Ok = JsValue<'a>;
type Error = Error;
fn serialize_field<T: ?Sized + Serialize>(
&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<Ok = JsValue<'a>, Error = Error>,
{
type Ok = JsValue<'a>;
type Error = Error;
fn serialize_field<T: ?Sized + Serialize>(
&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<JsValue<'a>>,
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<T: ?Sized + Serialize>(
&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<T: ?Sized + Serialize>(
&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<T: ?Sized + Serialize>(
&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<T: ?Sized + Serialize>(
&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<v8::Local<'a, v8::Value>>,
}
impl<'a, 'b> ser::SerializeStruct for MagicSerializer<'a, 'b> {
type Ok = JsValue<'a>;
type Error = Error;
fn serialize_field<T: ?Sized + Serialize>(
&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<T: ?Sized + Serialize>(
&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(),
}
}
}
// 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<JsValue<'a>>,
}
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<T: ?Sized + Serialize>(&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<T: ?Sized + Serialize>(
&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>,
}
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 = MapSerializer<'a, 'b>;
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<T: ?Sized + Serialize>(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<T: ?Sized + Serialize>(
self,
_name: &'static str,
value: &T,
) -> JsResult<'a> {
value.serialize(self)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
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<usize>) -> Result<Self::SerializeSeq> {
Ok(ArraySerializer::new(self.scope))
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.serialize_tuple(len)
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant> {
Ok(VariantSerializer::new(
self.scope.clone(),
variant,
self.serialize_tuple_struct(variant, len)?,
))
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
// 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.
fn serialize_struct(
self,
name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct> {
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<Self::SerializeStructVariant> {
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<v8::Local<'a, v8::Value>, Error>;
type SerializeTuple = Impossible<v8::Local<'a, v8::Value>, Error>;
type SerializeTupleStruct = Impossible<v8::Local<'a, v8::Value>, Error>;
type SerializeTupleVariant = Impossible<v8::Local<'a, v8::Value>, Error>;
type SerializeMap = Impossible<v8::Local<'a, v8::Value>, Error>;
type SerializeStruct = Impossible<v8::Local<'a, v8::Value>, Error>;
type SerializeStructVariant = Impossible<v8::Local<'a, v8::Value>, 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<T: ?Sized + Serialize>(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<T: ?Sized + Serialize>(
self,
_name: &'static str,
_value: &T,
) -> JsResult<'a> {
unreachable!();
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_value: &T,
) -> JsResult<'a> {
unreachable!();
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
unreachable!();
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
unreachable!();
}
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct> {
unreachable!();
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
unreachable!();
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
unreachable!();
}
/// Serialises Rust typed structs into plain JS objects.
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct> {
unreachable!();
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
unreachable!();
}
}

33
serde_v8/src/utils.rs Normal file
View File

@ -0,0 +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 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();
}

169
serde_v8/tests/de.rs Normal file
View File

@ -0,0 +1,169 @@
use rusty_v8 as v8;
use serde::Deserialize;
use serde_v8::utils::{js_exec, v8_do};
#[derive(Debug, Deserialize, PartialEq)]
struct MathOp {
pub a: u64,
pub b: u64,
pub operator: Option<String>,
}
fn dedo(
code: &str,
f: impl FnOnce(&mut v8::HandleScope, v8::Local<v8::Value>),
) {
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);
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<bool>, "true", Some(true));
detest!(de_option_null, Option<bool>, "null", None);
detest!(de_option_undefined, Option<bool>, "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<u64>, "[1,2,3,4,5]", vec![1, 2, 3, 4, 5]);
detest!(
de_vec_str,
Vec<String>,
"['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
}
);
#[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);
});
}
#[test]
fn de_map() {
use std::collections::HashMap;
dedo("({a: 1, b: 2, c: 3})", |scope, v| {
let map: HashMap<String, u64> = 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);
})
}
////
// 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,
"123.45",
serde_json::Value::Number(serde_json::Number::from_f64(123.45).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', 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(123.45).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()
)
);

57
serde_v8/tests/magic.rs Normal file
View File

@ -0,0 +1,57 @@
use rusty_v8 as v8;
use serde::{Deserialize, Serialize};
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<String>,
}
#[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<v8::Value> = mop.c.into();
let vs = v8::Local::<v8::String>::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();
}

129
serde_v8/tests/ser.rs Normal file
View File

@ -0,0 +1,129 @@
use rusty_v8 as v8;
use serde::Serialize;
use serde_json::json;
use serde_v8::utils::{js_exec, v8_do};
#[derive(Debug, Serialize, PartialEq)]
struct MathOp {
pub a: u64,
pub b: u64,
pub operator: Option<String>,
}
// Utility JS code (obj equality, etc...)
const JS_UTILS: &str = r#"
// Shallow obj equality (don't use deep objs for now)
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 a.length === b.length && a.every((v, i) => v === b[i]);
}
"#;
fn sercheck<T: Serialize>(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<bool>, "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})"
);
sertest!(
ser_map,
{
let map: std::collections::BTreeMap<&str, u32> =
vec![("a", 1), ("b", 2), ("c", 3)].drain(..).collect();
map
},
"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'])"
);