|
| 1 | +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. |
| 2 | + |
| 3 | +use crate::error::StdAnyError; |
| 4 | +use crate::runtime::ops; |
| 5 | +use std::convert::Infallible; |
| 6 | + |
| 7 | +/// A conversion from a rust value to a v8 value. |
| 8 | +/// |
| 9 | +/// When passing data from Rust into JS, either |
| 10 | +/// via an op or by calling a JS function directly, |
| 11 | +/// you need to serialize the data into a native |
| 12 | +/// V8 value. When using the [`op2`][deno_core::op2] macro, the return |
| 13 | +/// value is converted to a `v8::Local<Value>` automatically, |
| 14 | +/// and the strategy for conversion is controlled by attributes |
| 15 | +/// like `#[smi]`, `#[number]`, `#[string]`. For types with support |
| 16 | +/// built-in to the op2 macro, like primitives, strings, and buffers, |
| 17 | +/// these attributes are sufficient and you don't need to worry about this trait. |
| 18 | +/// |
| 19 | +/// If, however, you want to return a custom type from an op, or |
| 20 | +/// simply want more control over the conversion process, |
| 21 | +/// you can implement the `ToV8` trait. This allows you the |
| 22 | +/// choose the best serialization strategy for your specific use case. |
| 23 | +/// You can then use the `#[to_v8]` attribute to indicate |
| 24 | +/// that the `#[op2]` macro should call your implementation for the conversion. |
| 25 | +/// |
| 26 | +/// # Example |
| 27 | +/// |
| 28 | +/// ```ignore |
| 29 | +/// use deno_core::ToV8; |
| 30 | +/// use deno_core::convert::Smi; |
| 31 | +/// use deno_core::op2; |
| 32 | +/// |
| 33 | +/// struct Foo(i32); |
| 34 | +/// |
| 35 | +/// impl<'a> ToV8<'a> for Foo { |
| 36 | +/// // This conversion can never fail, so we use `Infallible` as the error type. |
| 37 | +/// // Any error type that implements `std::error::Error` can be used here. |
| 38 | +/// type Error = std::convert::Infallible; |
| 39 | +/// |
| 40 | +/// fn to_v8(self, scope: &mut v8::HandleScope<'a>) -> Result<v8::Local<'a, v8::Value>, Self::Error> { |
| 41 | +/// // For performance, pass this value as a `v8::Integer` (i.e. a `smi`). |
| 42 | +/// // The `Smi` wrapper type implements this conversion for you. |
| 43 | +/// Smi(self.0).to_v8(scope) |
| 44 | +/// } |
| 45 | +/// } |
| 46 | +/// |
| 47 | +/// // using the `#[to_v8]` attribute tells the `op2` macro to call this implementation. |
| 48 | +/// #[op2] |
| 49 | +/// #[to_v8] |
| 50 | +/// fn op_foo() -> Foo { |
| 51 | +/// Foo(42) |
| 52 | +/// } |
| 53 | +/// ``` |
| 54 | +/// |
| 55 | +/// # Performance Notes |
| 56 | +/// ## Structs |
| 57 | +/// The natural representation of a struct in JS is an object with fields |
| 58 | +/// corresponding the struct. This, however, is a performance footgun and |
| 59 | +/// you should avoid creating and passing objects to V8 whenever possible. |
| 60 | +/// In general, if you need to pass a compound type to JS, it is more performant to serialize |
| 61 | +/// to a tuple (a `v8::Array`) rather than an object. |
| 62 | +/// Object keys are V8 strings, and strings are expensive to pass to V8 |
| 63 | +/// and they have to be managed by the V8 garbage collector. |
| 64 | +/// Tuples, on the other hand, are keyed by `smi`s, which are immediates |
| 65 | +/// and don't require allocation or garbage collection. |
| 66 | +pub trait ToV8<'a> { |
| 67 | + type Error: std::error::Error + Send + Sync + 'static; |
| 68 | + |
| 69 | + /// Converts the value to a V8 value. |
| 70 | + fn to_v8( |
| 71 | + self, |
| 72 | + scope: &mut v8::HandleScope<'a>, |
| 73 | + ) -> Result<v8::Local<'a, v8::Value>, Self::Error>; |
| 74 | +} |
| 75 | + |
| 76 | +/// A conversion from a v8 value to a rust value. |
| 77 | +/// |
| 78 | +/// When writing a op, or otherwise writing a function in Rust called |
| 79 | +/// from JS, arguments passed from JS are represented as [`v8::Local<v8::Value>>`][deno_core::v8::Value]. |
| 80 | +/// To convert these values into custom Rust types, you can implement the [`FromV8`] trait. |
| 81 | +/// |
| 82 | +/// Once you've implemented this trait, you can use the `#[from_v8]` attribute |
| 83 | +/// to tell the [`op2`][deno_core::op2] macro to use your implementation to convert the argument |
| 84 | +/// to the desired type. |
| 85 | +/// |
| 86 | +/// # Example |
| 87 | +/// |
| 88 | +/// ```ignore |
| 89 | +/// use deno_core::FromV8; |
| 90 | +/// use deno_core::convert::Smi; |
| 91 | +/// use deno_core::op2; |
| 92 | +/// |
| 93 | +/// struct Foo(i32); |
| 94 | +/// |
| 95 | +/// impl<'a> FromV8<'a> for Foo { |
| 96 | +/// // This conversion can fail, so we use `deno_core::error::StdAnyError` as the error type. |
| 97 | +/// // Any error type that implements `std::error::Error` can be used here. |
| 98 | +/// type Error = deno_core::error::StdAnyError; |
| 99 | +/// |
| 100 | +/// fn from_v8(scope: &mut v8::HandleScope<'a>, value: v8::Local<'a, v8::Value>) -> Result<Self, Self::Error> { |
| 101 | +/// /// We expect this value to be a `v8::Integer`, so we use the [`Smi`][deno_core::convert::Smi] wrapper type to convert it. |
| 102 | +/// Smi::from_v8(scope, value).map(|Smi(v)| Foo(v)) |
| 103 | +/// } |
| 104 | +/// } |
| 105 | +/// |
| 106 | +/// // using the `#[from_v8]` attribute tells the `op2` macro to call this implementation. |
| 107 | +/// #[op2] |
| 108 | +/// fn op_foo(#[from_v8] foo: Foo) { |
| 109 | +/// let Foo(_) = foo; |
| 110 | +/// } |
| 111 | +/// ``` |
| 112 | +pub trait FromV8<'a>: Sized { |
| 113 | + type Error: std::error::Error + Send + Sync + 'static; |
| 114 | + |
| 115 | + /// Converts a V8 value to a Rust value. |
| 116 | + fn from_v8( |
| 117 | + scope: &mut v8::HandleScope<'a>, |
| 118 | + value: v8::Local<'a, v8::Value>, |
| 119 | + ) -> Result<Self, Self::Error>; |
| 120 | +} |
| 121 | + |
| 122 | +// impls |
| 123 | + |
| 124 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 125 | +/// Marks a numeric type as being serialized as a v8 `smi` in a `v8::Integer`. |
| 126 | +#[repr(transparent)] |
| 127 | +pub struct Smi<T: SmallInt>(pub T); |
| 128 | + |
| 129 | +/// A trait for types that can represent a JS `smi`. |
| 130 | +pub trait SmallInt { |
| 131 | + const NAME: &'static str; |
| 132 | + |
| 133 | + #[allow(clippy::wrong_self_convention)] |
| 134 | + fn as_i32(self) -> i32; |
| 135 | + fn from_i32(value: i32) -> Self; |
| 136 | +} |
| 137 | + |
| 138 | +macro_rules! impl_smallint { |
| 139 | + (for $($t:ty),*) => { |
| 140 | + $( |
| 141 | + impl SmallInt for $t { |
| 142 | + const NAME: &'static str = stringify!($t); |
| 143 | + #[allow(clippy::wrong_self_convention)] |
| 144 | + #[inline(always)] |
| 145 | + fn as_i32(self) -> i32 { |
| 146 | + self as _ |
| 147 | + } |
| 148 | + |
| 149 | + #[inline(always)] |
| 150 | + fn from_i32(value: i32) -> Self { |
| 151 | + value as _ |
| 152 | + } |
| 153 | + } |
| 154 | + )* |
| 155 | + }; |
| 156 | +} |
| 157 | + |
| 158 | +impl_smallint!(for u8, u16, u32, u64, usize, i8, i16, i32, i64, isize); |
| 159 | + |
| 160 | +impl<'a, T: SmallInt> ToV8<'a> for Smi<T> { |
| 161 | + type Error = Infallible; |
| 162 | + |
| 163 | + #[inline] |
| 164 | + fn to_v8( |
| 165 | + self, |
| 166 | + scope: &mut v8::HandleScope<'a>, |
| 167 | + ) -> Result<v8::Local<'a, v8::Value>, Self::Error> { |
| 168 | + Ok(v8::Integer::new(scope, self.0.as_i32()).into()) |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +impl<'a, T: SmallInt> FromV8<'a> for Smi<T> { |
| 173 | + type Error = StdAnyError; |
| 174 | + |
| 175 | + #[inline] |
| 176 | + fn from_v8( |
| 177 | + _scope: &mut v8::HandleScope<'a>, |
| 178 | + value: v8::Local<'a, v8::Value>, |
| 179 | + ) -> Result<Self, Self::Error> { |
| 180 | + let v = crate::runtime::ops::to_i32_option(&value).ok_or_else(|| { |
| 181 | + crate::error::type_error(format!("Expected {}", T::NAME)) |
| 182 | + })?; |
| 183 | + Ok(Smi(T::from_i32(v))) |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 188 | +/// Marks a numeric type as being serialized as a v8 `number` in a `v8::Number`. |
| 189 | +#[repr(transparent)] |
| 190 | +pub struct Number<T: Numeric>(pub T); |
| 191 | + |
| 192 | +/// A trait for types that can represent a JS `number`. |
| 193 | +pub trait Numeric: Sized { |
| 194 | + const NAME: &'static str; |
| 195 | + #[allow(clippy::wrong_self_convention)] |
| 196 | + fn as_f64(self) -> f64; |
| 197 | + fn from_value(value: &v8::Value) -> Option<Self>; |
| 198 | +} |
| 199 | + |
| 200 | +macro_rules! impl_numeric { |
| 201 | + ($($t:ty : $from: path ),*) => { |
| 202 | + $( |
| 203 | + impl Numeric for $t { |
| 204 | + const NAME: &'static str = stringify!($t); |
| 205 | + #[inline(always)] |
| 206 | + fn from_value(value: &v8::Value) -> Option<Self> { |
| 207 | + $from(value).map(|v| v as _) |
| 208 | + } |
| 209 | + |
| 210 | + #[allow(clippy::wrong_self_convention)] |
| 211 | + #[inline(always)] |
| 212 | + fn as_f64(self) -> f64 { |
| 213 | + self as _ |
| 214 | + } |
| 215 | + } |
| 216 | + )* |
| 217 | + }; |
| 218 | +} |
| 219 | + |
| 220 | +impl_numeric!( |
| 221 | + f32 : ops::to_f32_option, |
| 222 | + f64 : ops::to_f64_option, |
| 223 | + u32 : ops::to_u32_option, |
| 224 | + u64 : ops::to_u64_option, |
| 225 | + usize : ops::to_u64_option, |
| 226 | + i32 : ops::to_i32_option, |
| 227 | + i64 : ops::to_i64_option, |
| 228 | + isize : ops::to_i64_option |
| 229 | +); |
| 230 | + |
| 231 | +impl<'a, T: Numeric> ToV8<'a> for Number<T> { |
| 232 | + type Error = Infallible; |
| 233 | + #[inline] |
| 234 | + fn to_v8( |
| 235 | + self, |
| 236 | + scope: &mut v8::HandleScope<'a>, |
| 237 | + ) -> Result<v8::Local<'a, v8::Value>, Self::Error> { |
| 238 | + Ok(v8::Number::new(scope, self.0.as_f64()).into()) |
| 239 | + } |
| 240 | +} |
| 241 | + |
| 242 | +impl<'a, T: Numeric> FromV8<'a> for Number<T> { |
| 243 | + type Error = StdAnyError; |
| 244 | + #[inline] |
| 245 | + fn from_v8( |
| 246 | + _scope: &mut v8::HandleScope<'a>, |
| 247 | + value: v8::Local<'a, v8::Value>, |
| 248 | + ) -> Result<Self, Self::Error> { |
| 249 | + T::from_value(&value).map(Number).ok_or_else(|| { |
| 250 | + crate::error::type_error(format!("Expected {}", T::NAME)).into() |
| 251 | + }) |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +impl<'a> ToV8<'a> for bool { |
| 256 | + type Error = Infallible; |
| 257 | + #[inline] |
| 258 | + fn to_v8( |
| 259 | + self, |
| 260 | + scope: &mut v8::HandleScope<'a>, |
| 261 | + ) -> Result<v8::Local<'a, v8::Value>, Self::Error> { |
| 262 | + Ok(v8::Boolean::new(scope, self).into()) |
| 263 | + } |
| 264 | +} |
| 265 | + |
| 266 | +impl<'a> FromV8<'a> for bool { |
| 267 | + type Error = Infallible; |
| 268 | + #[inline] |
| 269 | + fn from_v8( |
| 270 | + _scope: &mut v8::HandleScope<'a>, |
| 271 | + value: v8::Local<'a, v8::Value>, |
| 272 | + ) -> Result<Self, Self::Error> { |
| 273 | + Ok(value.is_true()) |
| 274 | + } |
| 275 | +} |
0 commit comments