use deno_core::{
serde_v8::GlobalValue,
v8::{self, HandleScope},
};
use serde::Deserialize;
macro_rules! impl_v8 {
($name:ident$(<$generic:ident>)?, $checker:ident $(,)?) => {
impl $(<$generic>)? $name $(<$generic>)? where
$( $generic: serde::de::DeserializeOwned, )? {
#[allow(dead_code)]
pub(crate) fn into_inner(self) -> V8Value<$checker> {
self.0
}
#[must_use]
pub fn into_v8(self) -> v8::Global<v8::Value> {
self.0 .0
}
#[must_use]
pub fn as_v8(&self) -> &v8::Global<v8::Value> {
&self.0 .0
}
pub fn try_from_v8<'a, H>(
scope: &mut v8::HandleScope<'a>,
value: v8::Global<H>,
) -> Result<Self, crate::Error>
where
v8::Local<'a, v8::Value>: From<v8::Local<'a, H>>,
{
let local: v8::Local<v8::Value> = v8::Local::new(scope, value).into();
v8::Global::new(scope, local).try_into()
}
#[must_use]
pub unsafe fn from_v8_unchecked(value: v8::Global<v8::Value>) -> Self {
let inner = V8Value::<$checker>(value, std::marker::PhantomData);
Self(inner $(, std::marker::PhantomData::<$generic>)?)
}
}
impl<'de$(, $generic)?> serde::Deserialize<'de> for $name $(<$generic>)?
$(where $generic: serde::de::DeserializeOwned,)?
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let inner = V8Value::<$checker>::deserialize(deserializer)?;
Ok(Self(inner $(, std::marker::PhantomData::<$generic>)?))
}
}
#[allow(clippy::from_over_into)]
impl $(<$generic>)? Into<v8::Global<v8::Value>> for $name $(<$generic>)? $(where $generic: serde::de::DeserializeOwned)? {
fn into(self) -> v8::Global<v8::Value> {
self.0 .0
}
}
impl $(<$generic>)? TryFrom<v8::Global<v8::Value>> for $name $(<$generic>)? $(where $generic: serde::de::DeserializeOwned)? {
type Error = crate::Error;
fn try_from(value: v8::Global<v8::Value>) -> Result<Self, Self::Error> {
<$checker as $crate::js_value::V8TypeChecker>::validate(value.clone())?;
let inner = V8Value::<$checker>(value, std::marker::PhantomData);
Ok(Self(inner $(, std::marker::PhantomData::<$generic>)?))
}
}
};
}
macro_rules! impl_checker {
($name:ident, $v8_name:ident, $checker_fn:ident, |$err_ty:ident| $err:block) => {
#[doc = "Implementations of `V8TypeChecker`"]
#[doc = concat!("Guards for `v8::", stringify!($v8_name), "` values")]
#[derive(Eq, Hash, PartialEq, Debug, Clone, Deserialize)]
pub(crate) struct $name;
impl $crate::js_value::V8TypeChecker for $name {
type Output = v8::$v8_name;
fn validate(value: v8::Global<v8::Value>) -> Result<(), crate::Error> {
let raw: &v8::Value = unsafe { v8::Handle::get_unchecked(&value) };
if raw.$checker_fn() {
Ok(())
} else {
let $err_ty = raw.type_repr().to_string();
Err($err)
}
}
}
};
($name:ident, $v8_name:ident) => {
#[doc = "Implementation of `V8TypeChecker`"]
#[doc = concat!("Guards for `v8::", stringify!($v8_name), "` values")]
#[derive(Eq, Hash, PartialEq, Debug, Clone, Deserialize)]
pub(crate) struct $name;
impl V8TypeChecker for $name {
type Output = v8::$v8_name;
fn validate(_: v8::Global<v8::Value>) -> Result<(), crate::Error> {
Ok(())
}
}
};
}
pub(crate) trait V8TypeChecker {
type Output;
fn validate(value: v8::Global<v8::Value>) -> Result<(), crate::Error>;
}
impl_checker!(DefaultTypeChecker, Value);
#[derive(Eq, Hash, PartialEq, Debug, Clone)]
pub(crate) struct V8Value<V8TypeChecker>(
v8::Global<v8::Value>,
std::marker::PhantomData<V8TypeChecker>,
);
impl<T: V8TypeChecker> V8Value<T> {
pub(crate) fn as_local<'a>(&self, scope: &mut HandleScope<'a>) -> v8::Local<'a, T::Output>
where
v8::Local<'a, T::Output>: TryFrom<v8::Local<'a, v8::Value>>,
{
let local = v8::Local::new(scope, &self.0);
v8::Local::<'a, T::Output>::try_from(local)
.ok()
.expect("Failed to convert V8Value: Invalid V8TypeChecker!")
}
pub(crate) fn as_global<'a>(&self, scope: &mut HandleScope<'a>) -> v8::Global<T::Output>
where
v8::Local<'a, T::Output>: TryFrom<v8::Local<'a, v8::Value>>,
{
let local = self.as_local(scope);
v8::Global::new(scope, local)
}
}
impl<'de, T: V8TypeChecker> serde::Deserialize<'de> for V8Value<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = GlobalValue::deserialize(deserializer)?;
T::validate(value.v8_value.clone()).map_err(serde::de::Error::custom)?;
Ok(Self(value.v8_value, std::marker::PhantomData))
}
}
#[derive(Eq, Hash, PartialEq, Debug, Clone)]
pub struct Value(V8Value<DefaultTypeChecker>);
impl_v8!(Value, DefaultTypeChecker);
impl Value {
pub fn try_into<T>(self, runtime: &mut crate::Runtime) -> Result<T, crate::Error>
where
T: serde::de::DeserializeOwned,
{
let mut scope = runtime.deno_runtime().handle_scope();
let local = self.0.as_local(&mut scope);
Ok(deno_core::serde_v8::from_v8(&mut scope, local)?)
}
#[must_use]
pub fn from_v8(value: v8::Global<v8::Value>) -> Self {
Self(V8Value(value, std::marker::PhantomData))
}
}
mod function;
pub use function::*;
mod promise;
pub use promise::*;
mod string;
pub use string::*;
mod map;
pub use map::*;
#[cfg(test)]
mod test {
use super::*;
use crate::{Module, Runtime, RuntimeOptions};
#[test]
fn test_value() {
let module = Module::new(
"test.js",
"
export const f = 42;
export const g = () => 42;
",
);
let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap();
let handle = runtime.load_module(&module).unwrap();
let f: Value = runtime.get_value(Some(&handle), "f").unwrap();
let value: usize = f.try_into(&mut runtime).unwrap();
assert_eq!(value, 42);
let g: Value = runtime.get_value(Some(&handle), "g").unwrap();
let global = g.into_v8();
let _f = Function::try_from_v8(&mut runtime.deno_runtime().handle_scope(), global.clone())
.unwrap();
let f = unsafe { Function::from_v8_unchecked(global) };
let _f = f
.into_inner()
.as_local(&mut runtime.deno_runtime().handle_scope());
}
}