// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use super::erased_future::TypeErased; use super::future_arena::FutureContextMapper; use crate::GetErrorClassFn; use crate::OpId; use crate::PromiseId; use anyhow::Error; use serde::Serialize; const MAX_RESULT_SIZE: usize = 32; pub type MappedResult<'s, C> = Result< >::Result, >::Result, >; pub type UnmappedResult<'s, C> = Result< >::Result, >::MappingError, >; pub trait OpMappingContextLifetime<'s> { type Context: 's; type Result: 's; type MappingError: 's; fn map_error( context: &mut Self::Context, err: Error, get_error_class_fn: GetErrorClassFn, ) -> UnmappedResult<'s, Self>; fn map_mapping_error( context: &mut Self::Context, err: Self::MappingError, ) -> Self::Result; } /// A type that allows for arbitrary mapping systems for op output with lifetime /// control. We add this extra layer of generics because we want to test our drivers /// without requiring V8. pub trait OpMappingContext: for<'s> OpMappingContextLifetime<'s> + 'static { type MappingFn: Copy; fn erase_mapping_fn(f: Self::MappingFn) -> *const fn(); fn unerase_mapping_fn<'s, R: 'static>( f: *const fn(), scope: &mut >::Context, r: R, ) -> UnmappedResult<'s, Self>; } #[derive(Default)] pub struct V8OpMappingContext {} pub type V8RetValMapper = for<'r> fn( &mut v8::HandleScope<'r>, R, ) -> Result, serde_v8::Error>; impl<'s> OpMappingContextLifetime<'s> for V8OpMappingContext { type Context = v8::HandleScope<'s>; type Result = v8::Local<'s, v8::Value>; type MappingError = serde_v8::Error; #[inline(always)] fn map_error( scope: &mut v8::HandleScope<'s>, err: Error, get_error_class_fn: GetErrorClassFn, ) -> UnmappedResult<'s, Self> { serde_v8::to_v8(scope, OpError::new(get_error_class_fn, err)) } fn map_mapping_error( scope: &mut v8::HandleScope<'s>, err: Self::MappingError, ) -> v8::Local<'s, v8::Value> { serde_v8::to_v8(scope, OpError::new(&|_| "TypeError", err.into())).unwrap() } } impl OpMappingContext for V8OpMappingContext { type MappingFn = V8RetValMapper; #[inline(always)] fn erase_mapping_fn(f: Self::MappingFn) -> *const fn() { f as _ } #[inline(always)] fn unerase_mapping_fn<'s, R: 'static>( f: *const fn(), scope: &mut >::Context, r: R, ) -> Result, serde_v8::Error> { let f: Self::MappingFn = unsafe { std::mem::transmute(f) }; f(scope, r) } } /// [`PendingOp`] holds the metadata and result (success or failure) of a submitted /// and completed op. pub struct PendingOp(pub PendingOpInfo, pub OpResult); impl PendingOp { #[inline(always)] pub fn new + 'static>( info: PendingOpInfo, rv_map: C::MappingFn, result: Result, ) -> Self { match result { Ok(r) => PendingOp(info, OpResult::new_value(r, rv_map)), Err(err) => PendingOp(info, OpResult::Err(err.into())), } } #[inline(always)] pub fn ok( info: PendingOpInfo, rv_map: C::MappingFn, r: R, ) -> Self { PendingOp(info, OpResult::new_value(r, rv_map)) } } #[derive(Clone, Copy)] pub struct PendingOpInfo(pub PromiseId, pub OpId); pub struct PendingOpMappingInfo< C: OpMappingContext, R: 'static, const FALLIBLE: bool, >(pub PendingOpInfo, pub C::MappingFn); impl Copy for PendingOpMappingInfo { } impl Clone for PendingOpMappingInfo { #[inline(always)] fn clone(&self) -> Self { *self } } impl + 'static> FutureContextMapper, PendingOpInfo, Result> for PendingOpMappingInfo { fn context(&self) -> PendingOpInfo { self.0 } fn map(&self, r: Result) -> PendingOp { PendingOp::new(self.0, self.1, r) } } impl FutureContextMapper, PendingOpInfo, R> for PendingOpMappingInfo { fn context(&self) -> PendingOpInfo { self.0 } fn map(&self, r: R) -> PendingOp { PendingOp::ok(self.0, self.1, r) } } type MapRawFn = for<'a> fn( _lifetime: &'a (), scope: &mut >::Context, rv_map: *const fn(), value: TypeErased, ) -> UnmappedResult<'a, C>; #[allow(clippy::type_complexity)] pub struct OpValue { value: TypeErased, rv_map: *const fn(), map_fn: MapRawFn, } impl OpValue { pub fn new(rv_map: C::MappingFn, v: R) -> Self { Self { value: TypeErased::new(v), rv_map: C::erase_mapping_fn(rv_map), map_fn: |_, scope, rv_map, erased| unsafe { let r = erased.take(); C::unerase_mapping_fn::(rv_map, scope, r) }, } } } pub trait ValueLargeFn { fn unwrap<'a>( self: Box, ctx: &mut >::Context, ) -> UnmappedResult<'a, C>; } struct ValueLarge { v: R, rv_map: C::MappingFn, } impl ValueLargeFn for ValueLarge { fn unwrap<'a>( self: Box, ctx: &mut >::Context, ) -> UnmappedResult<'a, C> { C::unerase_mapping_fn(C::erase_mapping_fn(self.rv_map), ctx, self.v) } } pub enum OpResult { /// Errors. Err(Error), /// For small ops, we include them in an erased type container. Value(OpValue), /// For ops that return "large" results (> MAX_RESULT_SIZE bytes) we just box a function /// that can turn it into a v8 value. ValueLarge(Box>), } impl OpResult { pub fn new_value(v: R, rv_map: C::MappingFn) -> Self { if std::mem::size_of::() > MAX_RESULT_SIZE { OpResult::ValueLarge(Box::new(ValueLarge { v, rv_map })) } else { OpResult::Value(OpValue::new(rv_map, v)) } } pub fn unwrap<'a>( self, context: &mut >::Context, get_error_class_fn: GetErrorClassFn, ) -> MappedResult<'a, C> { let (success, res) = match self { Self::Err(err) => (false, C::map_error(context, err, get_error_class_fn)), Self::Value(f) => (true, (f.map_fn)(&(), context, f.rv_map, f.value)), Self::ValueLarge(f) => (true, f.unwrap(context)), }; match (success, res) { (true, Ok(x)) => Ok(x), (false, Ok(x)) => Err(x), (_, Err(err)) => Err( >::map_mapping_error(context, err), ), } } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct OpError { #[serde(rename = "$err_class_name")] class_name: &'static str, message: String, code: Option<&'static str>, } impl OpError { pub fn new(get_class: GetErrorClassFn, err: Error) -> Self { Self { class_name: (get_class)(&err), message: format!("{err:#}"), code: crate::error_codes::get_error_code(&err), } } }