summaryrefslogtreecommitdiff
path: root/core/ops.rs
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2020-09-10 09:57:45 -0400
committerGitHub <noreply@github.com>2020-09-10 09:57:45 -0400
commit7c2e7c660804afca823d60e6496aa853f75db16c (patch)
treeb7746b181c1564c6b1abd2e906662f9e6b008417 /core/ops.rs
parent6f70e6e72ba2d5c1de7495adac37c1e4f4e86b24 (diff)
Use gotham-like state for ops (#7385)
Provides a concrete state type that can be dynamically added. This is necessary for op crates. * renames BasicState to OpState * async ops take `Rc<RefCell<OpState>>` * sync ops take `&mut OpState` * removes `OpRegistry`, `OpRouter` traits * `get_error_class_fn` moved to OpState * ResourceTable moved to OpState
Diffstat (limited to 'core/ops.rs')
-rw-r--r--core/ops.rs256
1 files changed, 164 insertions, 92 deletions
diff --git a/core/ops.rs b/core/ops.rs
index 838596dc0..7af4949a1 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -1,13 +1,13 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use crate::gotham_state::GothamState;
use crate::BufVec;
use crate::ErrBox;
use crate::ZeroCopyBuf;
use futures::Future;
-use futures::FutureExt;
use indexmap::IndexMap;
-use serde_json::json;
use serde_json::Value;
+use std::cell::RefCell;
use std::collections::HashMap;
use std::iter::once;
use std::ops::Deref;
@@ -16,7 +16,7 @@ use std::pin::Pin;
use std::rc::Rc;
pub type OpAsyncFuture = Pin<Box<dyn Future<Output = Box<[u8]>>>>;
-pub type OpFn<S> = dyn Fn(Rc<S>, BufVec) -> Op + 'static;
+pub type OpFn = dyn Fn(Rc<RefCell<OpState>>, BufVec) -> Op + 'static;
pub type OpId = usize;
pub enum Op {
@@ -28,119 +28,191 @@ pub enum Op {
NotFound,
}
-pub trait OpRouter {
- fn route_op(self: Rc<Self>, op_id: OpId, bufs: BufVec) -> Op;
+pub struct OpState {
+ pub resource_table: crate::ResourceTable,
+ pub op_table: OpTable,
+ pub get_error_class_fn: crate::runtime::GetErrorClassFn,
+ gotham_state: GothamState,
}
-pub trait OpRegistry: OpRouter + 'static {
- fn get_op_catalog(self: Rc<Self>) -> HashMap<String, OpId>;
-
- fn register_op<F>(&self, name: &str, op_fn: F) -> OpId
- where
- F: Fn(Rc<Self>, BufVec) -> Op + 'static;
-
- fn register_op_json_sync<F>(self: &Rc<Self>, name: &str, op_fn: F) -> OpId
- where
- F: Fn(&Self, Value, &mut [ZeroCopyBuf]) -> Result<Value, ErrBox> + 'static,
- {
- let base_op_fn = move |state: Rc<Self>, mut bufs: BufVec| -> Op {
- let result = serde_json::from_slice(&bufs[0])
- .map_err(ErrBox::from)
- .and_then(|args| op_fn(&state, args, &mut bufs[1..]));
- let buf = state.json_serialize_op_result(None, result);
- Op::Sync(buf)
- };
-
- self.register_op(name, base_op_fn)
+impl Default for OpState {
+ // TODO(ry) Only deno_core should be able to construct an OpState. But I don't
+ // know how to make default private. Maybe rename to
+ // pub(crate) fn new() -> OpState
+ fn default() -> OpState {
+ OpState {
+ resource_table: crate::ResourceTable::default(),
+ op_table: OpTable::default(),
+ get_error_class_fn: &|_| "Error",
+ gotham_state: GothamState::default(),
+ }
}
+}
- fn register_op_json_async<F, R>(self: &Rc<Self>, name: &str, op_fn: F) -> OpId
- where
- F: Fn(Rc<Self>, Value, BufVec) -> R + 'static,
- R: Future<Output = Result<Value, ErrBox>> + 'static,
- {
- let try_dispatch_op = move |state: Rc<Self>,
- bufs: BufVec|
- -> Result<Op, ErrBox> {
- let args: Value = serde_json::from_slice(&bufs[0])?;
- let promise_id = args
- .get("promiseId")
- .and_then(Value::as_u64)
- .ok_or_else(|| ErrBox::type_error("missing or invalid `promiseId`"))?;
- let bufs = bufs[1..].into();
- let fut = op_fn(state.clone(), args, bufs).map(move |result| {
- state.json_serialize_op_result(Some(promise_id), result)
- });
- Ok(Op::Async(Box::pin(fut)))
- };
-
- let base_op_fn = move |state: Rc<Self>, bufs: BufVec| -> Op {
- match try_dispatch_op(state.clone(), bufs) {
- Ok(op) => op,
- Err(err) => Op::Sync(state.json_serialize_op_result(None, Err(err))),
- }
- };
-
- self.register_op(name, base_op_fn)
- }
+impl Deref for OpState {
+ type Target = GothamState;
- fn json_serialize_op_result(
- &self,
- promise_id: Option<u64>,
- result: Result<Value, ErrBox>,
- ) -> Box<[u8]> {
- let value = match result {
- Ok(v) => json!({ "ok": v, "promiseId": promise_id }),
- Err(err) => json!({
- "promiseId": promise_id ,
- "err": {
- "className": self.get_error_class_name(&err),
- "message": err.to_string(),
- }
- }),
- };
- serde_json::to_vec(&value).unwrap().into_boxed_slice()
+ fn deref(&self) -> &Self::Target {
+ &self.gotham_state
}
+}
- fn get_error_class_name(&self, _err: &ErrBox) -> &'static str {
- "Error"
+impl DerefMut for OpState {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.gotham_state
}
}
/// Collection for storing registered ops. The special 'get_op_catalog'
/// op with OpId `0` is automatically added when the OpTable is created.
-pub struct OpTable<S>(IndexMap<String, Rc<OpFn<S>>>);
+pub struct OpTable(IndexMap<String, Rc<OpFn>>);
-impl<S: OpRegistry> OpTable<S> {
- pub fn get_op_catalog(&self) -> HashMap<String, OpId> {
- self.keys().cloned().zip(0..).collect()
+impl OpTable {
+ pub fn register_op<F>(&mut self, name: &str, op_fn: F) -> OpId
+ where
+ F: Fn(Rc<RefCell<OpState>>, BufVec) -> Op + 'static,
+ {
+ let (op_id, prev) = self.0.insert_full(name.to_owned(), Rc::new(op_fn));
+ assert!(prev.is_none());
+ op_id
}
- fn op_get_op_catalog(state: Rc<S>, _bufs: BufVec) -> Op {
- let ops = state.get_op_catalog();
- let buf = serde_json::to_vec(&ops).map(Into::into).unwrap();
- Op::Sync(buf)
+ pub fn route_op(
+ op_id: OpId,
+ state: Rc<RefCell<OpState>>,
+ bufs: BufVec,
+ ) -> Op {
+ if op_id == 0 {
+ let ops: HashMap<String, OpId> =
+ state.borrow().op_table.0.keys().cloned().zip(0..).collect();
+ let buf = serde_json::to_vec(&ops).map(Into::into).unwrap();
+ Op::Sync(buf)
+ } else {
+ let op_fn = state
+ .borrow()
+ .op_table
+ .0
+ .get_index(op_id)
+ .map(|(_, op_fn)| op_fn.clone());
+ match op_fn {
+ Some(f) => (f)(state, bufs),
+ None => Op::NotFound,
+ }
+ }
}
}
-impl<S: OpRegistry> Default for OpTable<S> {
+impl Default for OpTable {
fn default() -> Self {
- Self(
- once(("ops".to_owned(), Rc::new(Self::op_get_op_catalog) as _)).collect(),
- )
+ fn dummy(_state: Rc<RefCell<OpState>>, _bufs: BufVec) -> Op {
+ unreachable!()
+ }
+ Self(once(("ops".to_owned(), Rc::new(dummy) as _)).collect())
}
}
-impl<S> Deref for OpTable<S> {
- type Target = IndexMap<String, Rc<OpFn<S>>>;
+#[test]
+fn op_table() {
+ let state = Rc::new(RefCell::new(OpState::default()));
- fn deref(&self) -> &Self::Target {
- &self.0
+ let foo_id;
+ let bar_id;
+ {
+ let op_table = &mut state.borrow_mut().op_table;
+ foo_id = op_table.register_op("foo", |_, _| Op::Sync(b"oof!"[..].into()));
+ assert_eq!(foo_id, 1);
+ bar_id = op_table.register_op("bar", |_, _| Op::Sync(b"rab!"[..].into()));
+ assert_eq!(bar_id, 2);
}
+
+ let foo_res = OpTable::route_op(foo_id, state.clone(), Default::default());
+ assert!(matches!(foo_res, Op::Sync(buf) if &*buf == b"oof!"));
+ let bar_res = OpTable::route_op(bar_id, state.clone(), Default::default());
+ assert!(matches!(bar_res, Op::Sync(buf) if &*buf == b"rab!"));
+
+ let catalog_res = OpTable::route_op(0, state, Default::default());
+ let mut catalog_entries = match catalog_res {
+ Op::Sync(buf) => serde_json::from_slice::<HashMap<String, OpId>>(&buf)
+ .map(|map| map.into_iter().collect::<Vec<_>>())
+ .unwrap(),
+ _ => panic!("unexpected `Op` variant"),
+ };
+ catalog_entries.sort_by(|(_, id1), (_, id2)| id1.partial_cmp(id2).unwrap());
+ assert_eq!(
+ catalog_entries,
+ vec![
+ ("ops".to_owned(), 0),
+ ("foo".to_owned(), 1),
+ ("bar".to_owned(), 2)
+ ]
+ )
}
-impl<S> DerefMut for OpTable<S> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
- }
+pub fn json_op_sync<F>(op_fn: F) -> Box<OpFn>
+where
+ F: Fn(&mut OpState, Value, &mut [ZeroCopyBuf]) -> Result<Value, ErrBox>
+ + 'static,
+{
+ Box::new(move |state: Rc<RefCell<OpState>>, mut bufs: BufVec| -> Op {
+ let result = serde_json::from_slice(&bufs[0])
+ .map_err(crate::ErrBox::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)
+ })
+}
+
+pub fn json_op_async<F, R>(op_fn: F) -> Box<OpFn>
+where
+ F: Fn(Rc<RefCell<OpState>>, Value, BufVec) -> R + 'static,
+ R: Future<Output = Result<Value, ErrBox>> + 'static,
+{
+ let try_dispatch_op =
+ move |state: Rc<RefCell<OpState>>, bufs: BufVec| -> Result<Op, ErrBox> {
+ let args: Value = serde_json::from_slice(&bufs[0])?;
+ let promise_id = args
+ .get("promiseId")
+ .and_then(Value::as_u64)
+ .ok_or_else(|| ErrBox::type_error("missing or invalid `promiseId`"))?;
+ 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(promise_id),
+ result,
+ state.borrow().get_error_class_fn,
+ )
+ });
+ Ok(Op::Async(Box::pin(fut)))
+ };
+
+ Box::new(move |state: Rc<RefCell<OpState>>, bufs: BufVec| -> Op {
+ match try_dispatch_op(state.clone(), bufs) {
+ Ok(op) => op,
+ Err(err) => Op::Sync(json_serialize_op_result(
+ None,
+ Err(err),
+ state.borrow().get_error_class_fn,
+ )),
+ }
+ })
+}
+
+fn json_serialize_op_result(
+ promise_id: Option<u64>,
+ result: Result<serde_json::Value, crate::ErrBox>,
+ get_error_class_fn: crate::runtime::GetErrorClassFn,
+) -> Box<[u8]> {
+ let value = match result {
+ Ok(v) => serde_json::json!({ "ok": v, "promiseId": promise_id }),
+ Err(err) => serde_json::json!({
+ "promiseId": promise_id ,
+ "err": {
+ "className": (get_error_class_fn)(&err),
+ "message": err.to_string(),
+ }
+ }),
+ };
+ serde_json::to_vec(&value).unwrap().into_boxed_slice()
}