1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use crate::error::AnyError;
use crate::serialize_op_result;
use crate::Op;
use crate::OpFn;
use crate::OpState;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;

/// A helper function that returns a sync NOP OpFn
///
/// It's mainly intended for embedders who want to disable ops, see ./examples/disable_ops.rs
pub fn void_op_sync() -> Box<OpFn> {
  // TODO(@AaronO): use this simpler implementation after changing serde_v8 to allow all values
  // to deserialize to the unit type instead of failing with `ExpectedNull`
  // op_sync(|_, _: (), _: ()| Ok(()))
  Box::new(move |state, _| -> Op {
    let op_result = serialize_op_result(Ok(()), state);
    Op::Sync(op_result)
  })
}

/// A helper function that returns an async NOP OpFn
///
/// It's mainly intended for embedders who want to disable ops, see ./examples/disable_ops.rs
pub fn void_op_async() -> Box<OpFn> {
  // TODO(@AaronO): use this simpler implementation after changing serde_v8 to allow all values
  // to deserialize to the unit type instead of failing with `ExpectedNull`
  // op_async(|_, _: (), _: ()| futures::future::ok(()))
  Box::new(move |state, payload| -> Op {
    let pid = payload.promise_id;
    let op_result = serialize_op_result(Ok(()), state);
    Op::Async(Box::pin(futures::future::ready((pid, op_result))))
  })
}

/// 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::op_sync(Self::hello_op));
/// runtime.sync_ops_cache();
/// ```
///
/// ...it can be invoked from JS using the provided name, for example:
/// ```js
/// let result = Deno.core.opSync("hello", args);
/// ```
///
/// `runtime.sync_ops_cache()` must be called after registering new ops
/// A more complete example is available in the examples directory.
pub fn op_sync<F, A, B, R>(op_fn: F) -> Box<OpFn>
where
  F: Fn(&mut OpState, A, B) -> Result<R, AnyError> + 'static,
  A: DeserializeOwned,
  B: DeserializeOwned,
  R: Serialize + 'static,
{
  Box::new(move |state, payload| -> Op {
    let result = payload
      .deserialize()
      .and_then(|(a, b)| op_fn(&mut state.borrow_mut(), a, b));
    Op::Sync(serialize_op_result(result, state))
  })
}

/// Creates an op that passes data asynchronously using JSON.
///
/// When this op is dispatched, the runtime doesn't exit while processing it.
/// Use op_async_unref instead if you want to make the runtime exit while processing it.
///
/// The provided function `op_fn` has the following parameters:
/// * `Rc<RefCell<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.
/// * `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::op_async(Self::hello_op));
/// runtime.sync_ops_cache();
/// ```
///
/// ...it can be invoked from JS using the provided name, for example:
/// ```js
/// let future = Deno.core.opAsync("hello", args);
/// ```
///
/// `runtime.sync_ops_cache()` must be called after registering new ops
/// A more complete example is available in the examples directory.
pub fn op_async<F, A, B, R, RV>(op_fn: F) -> Box<OpFn>
where
  F: Fn(Rc<RefCell<OpState>>, A, B) -> R + 'static,
  A: DeserializeOwned,
  B: DeserializeOwned,
  R: Future<Output = Result<RV, AnyError>> + 'static,
  RV: Serialize + 'static,
{
  Box::new(move |state, payload| -> Op {
    let pid = payload.promise_id;
    // Deserialize args, sync error on failure
    let args = match payload.deserialize() {
      Ok(args) => args,
      Err(err) => {
        return Op::Sync(serialize_op_result(Err::<(), AnyError>(err), state))
      }
    };
    let (a, b) = args;

    use crate::futures::FutureExt;
    let fut = op_fn(state.clone(), a, b)
      .map(move |result| (pid, serialize_op_result(result, state)));
    Op::Async(Box::pin(fut))
  })
}

/// Creates an op that passes data asynchronously using JSON.
///
/// When this op is dispatched, the runtime still can exit while processing it.
///
/// The other usages are the same as `op_async`.
pub fn op_async_unref<F, A, B, R, RV>(op_fn: F) -> Box<OpFn>
where
  F: Fn(Rc<RefCell<OpState>>, A, B) -> R + 'static,
  A: DeserializeOwned,
  B: DeserializeOwned,
  R: Future<Output = Result<RV, AnyError>> + 'static,
  RV: Serialize + 'static,
{
  Box::new(move |state, payload| -> Op {
    let pid = payload.promise_id;
    // Deserialize args, sync error on failure
    let args = match payload.deserialize() {
      Ok(args) => args,
      Err(err) => {
        return Op::Sync(serialize_op_result(Err::<(), AnyError>(err), state))
      }
    };
    let (a, b) = args;

    use crate::futures::FutureExt;
    let fut = op_fn(state.clone(), a, b)
      .map(move |result| (pid, serialize_op_result(result, state)));
    Op::AsyncUnref(Box::pin(fut))
  })
}

#[cfg(test)]
mod tests {
  use super::*;

  #[tokio::test]
  async fn op_async_stack_trace() {
    let mut runtime = crate::JsRuntime::new(Default::default());

    async fn op_throw(
      _state: Rc<RefCell<OpState>>,
      msg: Option<String>,
      _: (),
    ) -> Result<(), AnyError> {
      assert_eq!(msg.unwrap(), "hello");
      Err(crate::error::generic_error("foo"))
    }

    runtime.register_op("op_throw", op_async(op_throw));
    runtime.sync_ops_cache();
    runtime
      .execute_script(
        "<init>",
        r#"
    async function f1() {
      await Deno.core.opAsync('op_throw', 'hello');
    }

    async function f2() {
      await f1();
    }

    f2();
    "#,
      )
      .unwrap();
    let e = runtime.run_event_loop(false).await.unwrap_err().to_string();
    println!("{}", e);
    assert!(e.contains("Error: foo"));
    assert!(e.contains("at async f1 (<init>:"));
    assert!(e.contains("at async f2 (<init>:"));
  }
}