From 1251c893212d57303ecdfa8d953d1e487cb7ec7d Mon Sep 17 00:00:00 2001 From: Inteon <42113979+inteon@users.noreply.github.com> Date: Sat, 20 Mar 2021 17:51:08 +0100 Subject: refactor: Move bin ops to deno_core and unify logic with json ops (#9457) This commit moves implementation of bin ops to "deno_core" crates as well as unifying logic between bin ops and json ops to reuse as much code as possible (both in Rust and JavaScript). --- core/ops_json.rs | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 core/ops_json.rs (limited to 'core/ops_json.rs') diff --git a/core/ops_json.rs b/core/ops_json.rs new file mode 100644 index 000000000..0ef91ed33 --- /dev/null +++ b/core/ops_json.rs @@ -0,0 +1,134 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::error::type_error; +use crate::error::AnyError; +use crate::BufVec; +use crate::Op; +use crate::OpFn; +use crate::OpState; +use crate::ZeroCopyBuf; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::cell::RefCell; +use std::convert::TryInto; +use std::future::Future; +use std::rc::Rc; + +fn json_serialize_op_result( + request_id: Option, + result: Result, + get_error_class_fn: crate::runtime::GetErrorClassFn, +) -> Box<[u8]> { + let value = match result { + Ok(v) => serde_json::json!({ "ok": v, "requestId": request_id }), + Err(err) => serde_json::json!({ + "requestId": request_id, + "err": { + "className": (get_error_class_fn)(&err), + "message": err.to_string(), + } + }), + }; + serde_json::to_vec(&value).unwrap().into_boxed_slice() +} + +/// Creates an op that passes data synchronously using JSON. +/// +/// The provided function `op_fn` has the following parameters: +/// * `&mut OpState`: the op state, can be used to read/write resources in the runtime from an op. +/// * `V`: the deserializable value that is passed to the Rust function. +/// * `&mut [ZeroCopyBuf]`: raw bytes passed along, usually not needed if the JSON value is used. +/// +/// `op_fn` returns a serializable value, which is directly returned to JavaScript. +/// +/// When registering an op like this... +/// ```ignore +/// let mut runtime = JsRuntime::new(...); +/// runtime.register_op("hello", deno_core::json_op_sync(Self::hello_op)); +/// ``` +/// +/// ...it can be invoked from JS using the provided name, for example: +/// ```js +/// Deno.core.ops(); +/// let result = Deno.core.jsonOpSync("function_name", args); +/// ``` +/// +/// The `Deno.core.ops()` statement is needed once before any op calls, for initialization. +/// A more complete example is available in the examples directory. +pub fn json_op_sync(op_fn: F) -> Box +where + F: Fn(&mut OpState, V, &mut [ZeroCopyBuf]) -> Result + 'static, + V: DeserializeOwned, + R: Serialize, +{ + Box::new(move |state: Rc>, mut bufs: BufVec| -> Op { + let result = serde_json::from_slice(&bufs[0]) + .map_err(AnyError::from) + .and_then(|args| op_fn(&mut state.borrow_mut(), args, &mut bufs[1..])); + let buf = + json_serialize_op_result(None, result, state.borrow().get_error_class_fn); + Op::Sync(buf) + }) +} + +/// Creates an op that passes data asynchronously using JSON. +/// +/// The provided function `op_fn` has the following parameters: +/// * `Rc`: the op state, can be used to read/write resources in the runtime from an op. +/// * `V`: the deserializable value that is passed to the Rust function. +/// * `BufVec`: raw bytes passed along, usually not needed if the JSON value is used. +/// +/// `op_fn` returns a future, whose output is a serializable value. This value will be asynchronously +/// returned to JavaScript. +/// +/// When registering an op like this... +/// ```ignore +/// let mut runtime = JsRuntime::new(...); +/// runtime.register_op("hello", deno_core::json_op_async(Self::hello_op)); +/// ``` +/// +/// ...it can be invoked from JS using the provided name, for example: +/// ```js +/// Deno.core.ops(); +/// let future = Deno.core.jsonOpAsync("function_name", args); +/// ``` +/// +/// The `Deno.core.ops()` statement is needed once before any op calls, for initialization. +/// A more complete example is available in the examples directory. +pub fn json_op_async(op_fn: F) -> Box +where + F: Fn(Rc>, V, BufVec) -> R + 'static, + V: DeserializeOwned, + R: Future> + 'static, + RV: Serialize, +{ + let try_dispatch_op = + move |state: Rc>, bufs: BufVec| -> Result { + let request_id = bufs[0] + .get(0..8) + .map(|b| u64::from_le_bytes(b.try_into().unwrap())) + .ok_or_else(|| type_error("missing or invalid `requestId`"))?; + let args = serde_json::from_slice(&bufs[0][8..])?; + let bufs = bufs[1..].into(); + use crate::futures::FutureExt; + let fut = op_fn(state.clone(), args, bufs).map(move |result| { + json_serialize_op_result( + Some(request_id), + result, + state.borrow().get_error_class_fn, + ) + }); + Ok(Op::Async(Box::pin(fut))) + }; + + Box::new(move |state: Rc>, bufs: BufVec| -> Op { + match try_dispatch_op(state.clone(), bufs) { + Ok(op) => op, + Err(err) => Op::Sync(json_serialize_op_result( + None, + Err::<(), AnyError>(err), + state.borrow().get_error_class_fn, + )), + } + }) +} -- cgit v1.2.3