Repository: denoland/deno_core
Pull request #796 (2024-06-26 17:20:06)

Add a v8::Global variant of magic::Value
Allows the direct deserialization of globals across serde boundaries, which will be exceptionally useful in downstream projects

As discussed with @bartlomieju

rscarson: > LGTM aside from the doc comment.
> 
> It would also be good to add a simple test to `serde_v8/tests/magic.rs`, if you don't mind.

I would need to have the crate export `GlobalValue`
`Value` used to be exported which was changed recently

I would be happy to re-add Value and GlobalValue to serde_v8's exports
Would certainly make it easier to use

And then the existing `Value` can be tested too, which it currently isn't afaik
rscarson: > LGTM aside from the doc comment.
> 
> It would also be good to add a simple test to `serde_v8/tests/magic.rs`, if you don't mind.

Done!

Patch:
diff --git a/serde_v8/de.rs b/serde_v8/de.rs
index 0c36206..49b4407 100644
--- a/serde_v8/de.rs
+++ b/serde_v8/de.rs
@@ -342,6 +342,10 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de>
       BigInt::MAGIC_NAME => {
         visit_magic(visitor, BigInt::from_v8(self.scope, self.input)?)
       }
+      magic::GlobalValue::MAGIC_NAME => visit_magic(
+        visitor,
+        magic::GlobalValue::from_v8(self.scope, self.input)?,
+      ),
       magic::Value::MAGIC_NAME => {
         visit_magic(visitor, magic::Value::from_v8(self.scope, self.input)?)
       }
diff --git a/serde_v8/lib.rs b/serde_v8/lib.rs
index ea2b715..ae752d8 100644
--- a/serde_v8/lib.rs
+++ b/serde_v8/lib.rs
@@ -24,5 +24,7 @@ pub use magic::u16string::U16String;
 pub use magic::v8slice::V8Slice;
 pub use magic::v8slice::V8Sliceable;
 pub use magic::ExternalPointer;
+pub use magic::GlobalValue;
+pub use magic::Value;
 pub use ser::to_v8;
 pub use ser::Serializer;
diff --git a/serde_v8/magic/global_value.rs b/serde_v8/magic/global_value.rs
new file mode 100644
index 0000000..a29294c
--- /dev/null
+++ b/serde_v8/magic/global_value.rs
@@ -0,0 +1,47 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use crate::magic::transl8::impl_magic;
+use crate::magic::transl8::FromV8;
+use crate::magic::transl8::ToV8;
+
+/// A wrapper around `v8::Global<v8::Value>` to allow for passing globals transparently through a serde boundary.
+///
+/// Serializing a `GlobalValue` creates a `v8::Local` from the global value, and deserializing creates a `v8::Global` from the local value.
+pub struct GlobalValue {
+  pub v8_value: v8::Global<v8::Value>,
+}
+impl_magic!(GlobalValue);
+
+impl<T> From<v8::Global<T>> for GlobalValue
+where
+  v8::Global<T>: Into<v8::Global<v8::Value>>,
+{
+  fn from(v: v8::Global<T>) -> Self {
+    Self { v8_value: v.into() }
+  }
+}
+
+impl From<GlobalValue> for v8::Global<v8::Value> {
+  fn from(value: GlobalValue) -> Self {
+    value.v8_value
+  }
+}
+
+impl ToV8 for GlobalValue {
+  fn to_v8<'a>(
+    &self,
+    scope: &mut v8::HandleScope<'a>,
+  ) -> Result<v8::Local<'a, v8::Value>, crate::Error> {
+    Ok(v8::Local::new(scope, self.v8_value.clone()))
+  }
+}
+
+impl FromV8 for GlobalValue {
+  fn from_v8(
+    scope: &mut v8::HandleScope,
+    value: v8::Local<v8::Value>,
+  ) -> Result<Self, crate::Error> {
+    Ok(Self {
+      v8_value: v8::Global::new(scope, value),
+    })
+  }
+}
diff --git a/serde_v8/magic/mod.rs b/serde_v8/magic/mod.rs
index 993ca66..a42a20b 100644
--- a/serde_v8/magic/mod.rs
+++ b/serde_v8/magic/mod.rs
@@ -5,10 +5,12 @@ pub mod buffer;
 pub mod bytestring;
 pub mod detached_buffer;
 mod external_pointer;
+mod global_value;
 pub mod string_or_buffer;
 pub mod transl8;
 pub mod u16string;
 pub mod v8slice;
 mod value;
 pub use external_pointer::ExternalPointer;
+pub use global_value::GlobalValue;
 pub use value::Value;
diff --git a/serde_v8/ser.rs b/serde_v8/ser.rs
index 8bba73c..dc07216 100644
--- a/serde_v8/ser.rs
+++ b/serde_v8/ser.rs
@@ -275,6 +275,7 @@ impl<'a, 'b, 'c, T: MagicType + ToV8> ser::SerializeStruct
 pub enum StructSerializers<'a, 'b, 'c> {
   ExternalPointer(MagicalSerializer<'a, 'b, 'c, magic::ExternalPointer>),
   Magic(MagicalSerializer<'a, 'b, 'c, magic::Value<'a>>),
+  GlobalMagic(MagicalSerializer<'a, 'b, 'c, magic::GlobalValue>),
   RustToV8Buf(MagicalSerializer<'a, 'b, 'c, ToJsBuffer>),
   MagicAnyValue(MagicalSerializer<'a, 'b, 'c, AnyValue>),
   MagicDetached(MagicalSerializer<'a, 'b, 'c, DetachedBuffer>),
@@ -296,6 +297,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> {
     match self {
       StructSerializers::ExternalPointer(s) => s.serialize_field(key, value),
       StructSerializers::Magic(s) => s.serialize_field(key, value),
+      StructSerializers::GlobalMagic(s) => s.serialize_field(key, value),
       StructSerializers::RustToV8Buf(s) => s.serialize_field(key, value),
       StructSerializers::MagicAnyValue(s) => s.serialize_field(key, value),
       StructSerializers::MagicDetached(s) => s.serialize_field(key, value),
@@ -310,6 +312,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> {
     match self {
       StructSerializers::ExternalPointer(s) => s.end(),
       StructSerializers::Magic(s) => s.end(),
+      StructSerializers::GlobalMagic(s) => s.end(),
       StructSerializers::RustToV8Buf(s) => s.end(),
       StructSerializers::MagicAnyValue(s) => s.end(),
       StructSerializers::MagicDetached(s) => s.end(),
@@ -604,6 +607,10 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> {
         let m = MagicalSerializer::<magic::Value<'a>>::new(self.scope);
         Ok(StructSerializers::Magic(m))
       }
+      magic::GlobalValue::MAGIC_NAME => {
+        let m = MagicalSerializer::<magic::GlobalValue>::new(self.scope);
+        Ok(StructSerializers::GlobalMagic(m))
+      }
       _ => {
         // Regular structs
         let o = ObjectSerializer::new(self.scope, len);


Test patch:
diff --git a/serde_v8/tests/magic.rs b/serde_v8/tests/magic.rs
index 1e11734..8977860 100644
--- a/serde_v8/tests/magic.rs
+++ b/serde_v8/tests/magic.rs
@@ -163,3 +163,52 @@ fn magic_byte_string() {
     assert!(eq.is_true());
   })
 }
+
+#[test]
+fn magic_value() {
+  use serde_v8_utilities::{js_exec, v8_do};
+
+  struct TestLocal<'a>(v8::Local<'a, v8::Value>);
+  impl<'de> serde::Deserialize<'de> for TestLocal<'_> {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+    where
+      D: serde::Deserializer<'de>,
+    {
+      let value = serde_v8::Value::deserialize(deserializer)?;
+      let value = value.v8_value;
+      Ok(Self(value))
+    }
+  }
+
+  struct TestGlobal(v8::Global<v8::Value>);
+  impl<'de> serde::Deserialize<'de> for TestGlobal {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+    where
+      D: serde::Deserializer<'de>,
+    {
+      let value = serde_v8::GlobalValue::deserialize(deserializer)?;
+      let value = value.v8_value;
+      Ok(Self(value))
+    }
+  }
+
+  v8_do(|| {
+    // Init 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);
+
+    let v8_string = js_exec(scope, "'test'");
+    let test: TestGlobal = serde_v8::from_v8(scope, v8_string).unwrap();
+    let local = v8::Local::new(scope, test.0);
+    let test = local.to_rust_string_lossy(scope);
+    assert_eq!(test.as_str(), "test");
+
+    let v8_string = js_exec(scope, "'test'");
+    let test: TestLocal = serde_v8::from_v8(scope, v8_string).unwrap();
+    let local = test.0;
+    let test = local.to_rust_string_lossy(scope);
+    assert_eq!(test.as_str(), "test");
+  })
+}