mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 07:44:48 -05:00
f5840bdcd3
Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
568 lines
14 KiB
Rust
568 lines
14 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
use serde::Deserialize;
|
|
use serde::Deserializer;
|
|
|
|
use serde_v8::utils::js_exec;
|
|
use serde_v8::utils::v8_do;
|
|
use serde_v8::ByteString;
|
|
use serde_v8::Error;
|
|
use serde_v8::U16String;
|
|
use serde_v8::ZeroCopyBuf;
|
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
|
struct MathOp {
|
|
pub a: u64,
|
|
pub b: u64,
|
|
pub operator: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Deserialize)]
|
|
enum EnumUnit {
|
|
A,
|
|
B,
|
|
C,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Deserialize)]
|
|
enum EnumPayloads {
|
|
UInt(u64),
|
|
Int(i64),
|
|
Float(f64),
|
|
Point { x: i64, y: i64 },
|
|
Tuple(bool, i64, ()),
|
|
}
|
|
|
|
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! decheck {
|
|
($fn_name:ident, $t:ty, $src:expr, $x:ident, $check:expr) => {
|
|
#[test]
|
|
fn $fn_name() {
|
|
#[allow(clippy::bool_assert_comparison)]
|
|
dedo($src, |scope, v| {
|
|
let rt = serde_v8::from_v8(scope, v);
|
|
assert!(rt.is_ok(), "from_v8(\"{}\"): {:?}", $src, rt.err());
|
|
let $x: $t = rt.unwrap();
|
|
$check
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! detest {
|
|
($fn_name:ident, $t:ty, $src:expr, $rust:expr) => {
|
|
decheck!($fn_name, $t, $src, t, assert_eq!(t, $rust));
|
|
};
|
|
}
|
|
|
|
macro_rules! defail {
|
|
($fn_name:ident, $t:ty, $src:expr, $failcase:expr) => {
|
|
#[test]
|
|
fn $fn_name() {
|
|
#[allow(clippy::bool_assert_comparison)]
|
|
dedo($src, |scope, v| {
|
|
let rt: serde_v8::Result<$t> = serde_v8::from_v8(scope, v);
|
|
let rtstr = format!("{:?}", rt);
|
|
let failed_as_expected = $failcase(rt);
|
|
assert!(
|
|
failed_as_expected,
|
|
"expected failure on deserialize(\"{}\"), got: {}",
|
|
$src, rtstr
|
|
);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
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_char, char, "'é'", 'é');
|
|
detest!(de_u64, u64, "32", 32);
|
|
detest!(de_string, String, "'Hello'", "Hello".to_owned());
|
|
detest!(de_vec_empty, Vec<u64>, "[]", vec![0; 0]);
|
|
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, ())
|
|
);
|
|
defail!(
|
|
de_tuple_wrong_len_short,
|
|
(u64, bool, ()),
|
|
"[123, true]",
|
|
|e| e == Err(Error::LengthMismatch)
|
|
);
|
|
defail!(
|
|
de_tuple_wrong_len_long,
|
|
(u64, bool, ()),
|
|
"[123, true, null, 'extra']",
|
|
|e| e == Err(Error::LengthMismatch)
|
|
);
|
|
detest!(
|
|
de_mathop,
|
|
MathOp,
|
|
"({a: 1, b: 3, c: 'ignored'})",
|
|
MathOp {
|
|
a: 1,
|
|
b: 3,
|
|
operator: None
|
|
}
|
|
);
|
|
|
|
// Unit enums
|
|
detest!(de_enum_unit_a, EnumUnit, "'A'", EnumUnit::A);
|
|
detest!(de_enum_unit_so_a, EnumUnit, "new String('A')", EnumUnit::A);
|
|
detest!(de_enum_unit_b, EnumUnit, "'B'", EnumUnit::B);
|
|
detest!(de_enum_unit_so_b, EnumUnit, "new String('B')", EnumUnit::B);
|
|
detest!(de_enum_unit_c, EnumUnit, "'C'", EnumUnit::C);
|
|
detest!(de_enum_unit_so_c, EnumUnit, "new String('C')", EnumUnit::C);
|
|
|
|
// Enums with payloads (tuples & struct)
|
|
detest!(
|
|
de_enum_payload_int,
|
|
EnumPayloads,
|
|
"({ Int: -123 })",
|
|
EnumPayloads::Int(-123)
|
|
);
|
|
detest!(
|
|
de_enum_payload_uint,
|
|
EnumPayloads,
|
|
"({ UInt: 123 })",
|
|
EnumPayloads::UInt(123)
|
|
);
|
|
detest!(
|
|
de_enum_payload_float,
|
|
EnumPayloads,
|
|
"({ Float: 1.23 })",
|
|
EnumPayloads::Float(1.23)
|
|
);
|
|
detest!(
|
|
de_enum_payload_point,
|
|
EnumPayloads,
|
|
"({ Point: { x: 1, y: 2 } })",
|
|
EnumPayloads::Point { x: 1, y: 2 }
|
|
);
|
|
detest!(
|
|
de_enum_payload_tuple,
|
|
EnumPayloads,
|
|
"({ Tuple: [true, 123, null ] })",
|
|
EnumPayloads::Tuple(true, 123, ())
|
|
);
|
|
|
|
#[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);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn de_obj_with_numeric_keys() {
|
|
dedo(
|
|
r#"({
|
|
lines: {
|
|
100: {
|
|
unit: "m"
|
|
},
|
|
200: {
|
|
unit: "cm"
|
|
}
|
|
}
|
|
})"#,
|
|
|scope, v| {
|
|
let json: serde_json::Value = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(
|
|
json.to_string(),
|
|
r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"#
|
|
);
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn de_string_or_buffer() {
|
|
dedo("'hello'", |scope, v| {
|
|
let sob: serde_v8::StringOrBuffer = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(sob.as_ref(), &[0x68, 0x65, 0x6C, 0x6C, 0x6F]);
|
|
});
|
|
|
|
dedo("new Uint8Array([97])", |scope, v| {
|
|
let sob: serde_v8::StringOrBuffer = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(sob.as_ref(), &[97]);
|
|
});
|
|
|
|
dedo("new Uint8Array([128])", |scope, v| {
|
|
let sob: serde_v8::StringOrBuffer = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(sob.as_ref(), &[128]);
|
|
});
|
|
|
|
dedo(
|
|
"(Uint8Array.from([0x68, 0x65, 0x6C, 0x6C, 0x6F]))",
|
|
|scope, v| {
|
|
let sob: serde_v8::StringOrBuffer = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(sob.as_ref(), &[0x68, 0x65, 0x6C, 0x6C, 0x6F]);
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn de_buffers() {
|
|
// ArrayBufferView
|
|
dedo("new Uint8Array([97])", |scope, v| {
|
|
let buf: ZeroCopyBuf = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(&*buf, &[97]);
|
|
});
|
|
|
|
// ArrayBuffer
|
|
dedo("(new Uint8Array([97])).buffer", |scope, v| {
|
|
let buf: ZeroCopyBuf = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(&*buf, &[97]);
|
|
});
|
|
|
|
dedo(
|
|
"(Uint8Array.from([0x68, 0x65, 0x6C, 0x6C, 0x6F]))",
|
|
|scope, v| {
|
|
let buf: ZeroCopyBuf = serde_v8::from_v8(scope, v).unwrap();
|
|
assert_eq!(&*buf, &[0x68, 0x65, 0x6C, 0x6C, 0x6F]);
|
|
},
|
|
);
|
|
}
|
|
|
|
// Structs
|
|
#[derive(Debug, PartialEq, Deserialize)]
|
|
struct StructUnit;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
struct StructPayload {
|
|
a: u64,
|
|
b: u64,
|
|
}
|
|
|
|
struct StructVisitor;
|
|
|
|
impl<'de> serde::de::Visitor<'de> for StructVisitor {
|
|
type Value = StructPayload;
|
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
formatter.write_str("struct StructPayload")
|
|
}
|
|
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
|
where
|
|
A: serde::de::MapAccess<'de>,
|
|
{
|
|
let mut payload = StructPayload { a: 0, b: 0 };
|
|
while let Some(key) = map.next_key::<String>()? {
|
|
match key.as_ref() {
|
|
"a" => payload.a = map.next_value()?,
|
|
"b" => payload.b = map.next_value()?,
|
|
f => panic!("Unknown field {f}"),
|
|
}
|
|
}
|
|
Ok(payload)
|
|
}
|
|
}
|
|
|
|
detest!(de_unit_struct, StructUnit, "'StructUnit'", StructUnit);
|
|
|
|
#[test]
|
|
fn de_struct() {
|
|
dedo("({ a: 1, b: 2 })", |scope, v| {
|
|
let mut de = serde_v8::Deserializer::new(scope, v, None);
|
|
let payload = de
|
|
.deserialize_struct("StructPayload", &[], StructVisitor)
|
|
.unwrap();
|
|
assert_eq!(payload, StructPayload { a: 1, b: 2 })
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn de_struct_hint() {
|
|
dedo("({ a: 1, b: 2 })", |scope, v| {
|
|
let mut de = serde_v8::Deserializer::new(scope, v, None);
|
|
let payload = de
|
|
.deserialize_struct("StructPayload", &["a", "b"], StructVisitor)
|
|
.unwrap();
|
|
assert_eq!(payload, StructPayload { a: 1, b: 2 })
|
|
})
|
|
}
|
|
|
|
////
|
|
// 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(123))
|
|
);
|
|
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::json!({
|
|
"a": 1,
|
|
"b": "hello",
|
|
"c": true,
|
|
})
|
|
);
|
|
detest!(
|
|
de_json_object_from_map,
|
|
serde_json::Value,
|
|
"(new Map([['a', 1], ['b', 'hello'], ['c', true]]))",
|
|
serde_json::json!({
|
|
"a": 1,
|
|
"b": "hello",
|
|
"c": true,
|
|
})
|
|
);
|
|
// TODO: this is not optimal, ideally we'd get an array of [1,2,3] instead.
|
|
// Fixing that will require exposing Set::AsArray in the v8 bindings.
|
|
detest!(
|
|
de_json_object_from_set,
|
|
serde_json::Value,
|
|
"(new Set([1, 2, 3]))",
|
|
serde_json::json!({})
|
|
);
|
|
|
|
defail!(defail_struct, MathOp, "123", |e| e
|
|
== Err(Error::ExpectedObject));
|
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
pub struct SomeThing {
|
|
pub a: String,
|
|
#[serde(default)]
|
|
pub b: String,
|
|
}
|
|
detest!(
|
|
de_struct_defaults,
|
|
SomeThing,
|
|
"({ a: 'hello' })",
|
|
SomeThing {
|
|
a: "hello".into(),
|
|
b: "".into()
|
|
}
|
|
);
|
|
|
|
detest!(de_bstr, ByteString, "'hello'", "hello".into());
|
|
defail!(defail_bstr, ByteString, "'👋bye'", |e| e
|
|
== Err(Error::ExpectedLatin1));
|
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
pub struct StructWithBytes {
|
|
#[serde(with = "serde_bytes")]
|
|
a: Vec<u8>,
|
|
#[serde(with = "serde_bytes")]
|
|
b: Vec<u8>,
|
|
#[serde(with = "serde_bytes")]
|
|
c: Vec<u8>,
|
|
}
|
|
detest!(
|
|
de_struct_with_bytes,
|
|
StructWithBytes,
|
|
"({ a: new Uint8Array([1, 2]), b: (new Uint8Array([3 , 4])).buffer, c: (new Uint32Array([0])).buffer})",
|
|
StructWithBytes {
|
|
a: vec![1, 2],
|
|
b: vec![3, 4],
|
|
c: vec![0, 0, 0, 0],
|
|
}
|
|
);
|
|
detest!(
|
|
de_u16str,
|
|
U16String,
|
|
"'hello'",
|
|
"hello".encode_utf16().collect::<Vec<_>>().into()
|
|
);
|
|
detest!(
|
|
de_u16str_non_latin1,
|
|
U16String,
|
|
"'👋bye'",
|
|
"👋bye".encode_utf16().collect::<Vec<_>>().into()
|
|
);
|
|
|
|
// NaN
|
|
detest!(de_nan_u8, u8, "NaN", 0);
|
|
detest!(de_nan_u16, u16, "NaN", 0);
|
|
detest!(de_nan_u32, u32, "NaN", 0);
|
|
detest!(de_nan_u64, u64, "NaN", 0);
|
|
detest!(de_nan_i8, i8, "NaN", 0);
|
|
detest!(de_nan_i16, i16, "NaN", 0);
|
|
detest!(de_nan_i32, i32, "NaN", 0);
|
|
detest!(de_nan_i64, i64, "NaN", 0);
|
|
decheck!(de_nan_f32, f32, "NaN", t, assert!(t.is_nan()));
|
|
decheck!(de_nan_f64, f64, "NaN", t, assert!(t.is_nan()));
|
|
|
|
// Infinity
|
|
detest!(de_inf_u8, u8, "Infinity", u8::MAX);
|
|
detest!(de_inf_u16, u16, "Infinity", u16::MAX);
|
|
detest!(de_inf_u32, u32, "Infinity", u32::MAX);
|
|
detest!(de_inf_u64, u64, "Infinity", u64::MAX);
|
|
detest!(de_inf_i8, i8, "Infinity", i8::MAX);
|
|
detest!(de_inf_i16, i16, "Infinity", i16::MAX);
|
|
detest!(de_inf_i32, i32, "Infinity", i32::MAX);
|
|
detest!(de_inf_i64, i64, "Infinity", i64::MAX);
|
|
detest!(de_inf_f32, f32, "Infinity", f32::INFINITY);
|
|
detest!(de_inf_f64, f64, "Infinity", f64::INFINITY);
|
|
|
|
// -Infinity
|
|
detest!(de_neg_inf_u8, u8, "-Infinity", u8::MIN);
|
|
detest!(de_neg_inf_u16, u16, "-Infinity", u16::MIN);
|
|
detest!(de_neg_inf_u32, u32, "-Infinity", u32::MIN);
|
|
detest!(de_neg_inf_u64, u64, "-Infinity", u64::MIN);
|
|
detest!(de_neg_inf_i8, i8, "-Infinity", i8::MIN);
|
|
detest!(de_neg_inf_i16, i16, "-Infinity", i16::MIN);
|
|
detest!(de_neg_inf_i32, i32, "-Infinity", i32::MIN);
|
|
detest!(de_neg_inf_i64, i64, "-Infinity", i64::MIN);
|
|
detest!(de_neg_inf_f32, f32, "-Infinity", f32::NEG_INFINITY);
|
|
detest!(de_neg_inf_f64, f64, "-Infinity", f64::NEG_INFINITY);
|
|
|
|
// valueOf Number
|
|
detest!(de_valof_u8, u8, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_u16, u16, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_u32, u32, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_u64, u64, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_i8, i8, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_i16, i16, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_i32, i32, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_i64, i64, "({ valueOf: () => 123 })", 123);
|
|
detest!(de_valof_f32, f32, "({ valueOf: () => 123 })", 123.0);
|
|
detest!(de_valof_f64, f64, "({ valueOf: () => 123 })", 123.0);
|
|
|
|
// valueOf BigInt
|
|
detest!(de_valof_bigint_u8, u8, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_u16, u16, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_u32, u32, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_u64, u64, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_i8, i8, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_i16, i16, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_i32, i32, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_i64, i64, "({ valueOf: () => 123n })", 123);
|
|
detest!(de_valof_bigint_f32, f32, "({ valueOf: () => 123n })", 123.0);
|
|
detest!(de_valof_bigint_f64, f64, "({ valueOf: () => 123n })", 123.0);
|
|
|
|
// bool
|
|
detest!(de_num_true, u8, "true", 1);
|
|
detest!(de_num_false, u8, "false", 0);
|
|
|
|
// BigInt to f32/f64 max/min
|
|
detest!(
|
|
de_bigint_f64_max,
|
|
f64,
|
|
"BigInt(1.7976931348623157e+308)",
|
|
f64::MAX
|
|
);
|
|
detest!(
|
|
de_bigint_f64_min,
|
|
f64,
|
|
"BigInt(-1.7976931348623157e+308)",
|
|
f64::MIN
|
|
);
|
|
detest!(de_bigint_f32_max, f32, "BigInt(3.40282347e38)", f32::MAX);
|
|
detest!(de_bigint_f32_min, f32, "BigInt(-3.40282347e38)", f32::MIN);
|
|
// BigInt to f32/f64 saturating to inf
|
|
detest!(
|
|
de_bigint_f64_inf,
|
|
f64,
|
|
"(BigInt(1.7976931348623157e+308)*BigInt(100))",
|
|
f64::INFINITY
|
|
);
|
|
detest!(
|
|
de_bigint_f64_neg_inf,
|
|
f64,
|
|
"(BigInt(-1.7976931348623157e+308)*BigInt(100))",
|
|
f64::NEG_INFINITY
|
|
);
|
|
|
|
detest!(
|
|
de_bigint_f32_inf,
|
|
f32,
|
|
"BigInt(1.7976931348623157e+308)",
|
|
f32::INFINITY
|
|
);
|
|
detest!(
|
|
de_bigint_f32_neg_inf,
|
|
f32,
|
|
"BigInt(-1.7976931348623157e+308)",
|
|
f32::NEG_INFINITY
|
|
);
|