Skip to content

Commit

Permalink
feat: 'dispatch_json' in core
Browse files Browse the repository at this point in the history
  • Loading branch information
afinch7 committed May 15, 2020
1 parent 053c568 commit 1f1a882
Show file tree
Hide file tree
Showing 17 changed files with 1,080 additions and 50 deletions.
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/ops/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl<'a> plugin_api::Interface for PluginInterface<'a> {
fn register_op(
&mut self,
name: &str,
dispatch_op_fn: plugin_api::DispatchOpFn,
dispatch_op_fn: Box<dyn plugin_api::DispatchOpFn>,
) -> OpId {
let plugin_lib = self.plugin_lib.clone();
self.isolate.op_registry.register(
Expand Down
12 changes: 12 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,24 @@ log = "0.4.8"
rusty_v8 = "0.4.2"
serde_json = "1.0.52"
url = "2.1.1"
# dispatch json stuff
serde = { version = "1.0.104", features = ["derive"] }
serde_derive = "1.0.104"
tokio = { version = "0.2.13", optional = true }

[[example]]
name = "deno_core_http_bench"
path = "examples/http_bench.rs"

[[example]]
name = "deno_core_json"
path = "examples/json.rs"

# These dependendencies are only used for deno_core_http_bench.
[dev-dependencies]
derive_deref = "1.1.0"
tokio = { version = "0.2.20", features = ["rt-core", "tcp"] }

[features]
default = []
dispatch_json = [ "default", "tokio" ]
50 changes: 50 additions & 0 deletions core/dispatch_json.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
export declare type ErrorFactory = (kind: number, msg: string) => Error;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export declare type Ok = any;

interface Deferred<T> extends Promise<T> {
resolve: (value?: T | PromiseLike<T>) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (reason?: any) => void;
}

declare enum InternalErrorKinds {
JsonIoError = 1,
JsonSyntaxError = 2,
JsonDataError = 3,
JsonEofError = 4,
}

interface JsonError {
kind: number;
message: string;
}

interface JsonResponse {
ok?: Ok;
err?: JsonError;
promiseId?: number;
}

/** Json based dispatch wrapper for core ops.
*
* Error kind mapping is controlled by errorFactory. Async handler is automatically
* set during construction.
*
* const opId = Deno.ops()["json_op"];
* const jsonOp = new DispatchJsonOp(opId, (kind, msg) => return new CustomError(kind, msg));
* const response = jsonOp.dispatchSync({ data });
* console.log(response.items[3].name);
*/
export declare class DispatchJsonOp {
protected readonly promiseTable: Map<number, Deferred<JsonResponse>>;
protected _nextPromiseId: number;
constructor(opId: number, errorFactory: ErrorFactory);
protected nextPromiseId(): number;
protected unwrapResponse(res: JsonResponse): Ok;
protected handleAsync(resUi8: Uint8Array): void;
dispatchSync(args?: object, zeroCopy?: Uint8Array): Ok;
dispatchAsync(args?: object, zeroCopy?: Uint8Array): Promise<Ok>;
}
131 changes: 131 additions & 0 deletions core/dispatch_json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// These utilities are borrowed from std. We don't really
// have a better way to include them here yet.
class AssertionError extends Error {
constructor(message) {
super(message);
this.name = "AssertionError";
}
}
/** Make an assertion, if not `true`, then throw. */
function assert(expr, msg = "") {
if (!expr) {
throw new AssertionError(msg);
}
}
/** Creates a Promise with the `reject` and `resolve` functions
* placed as methods on the promise object itself. It allows you to do:
*
* const p = deferred<number>();
* // ...
* p.resolve(42);
*/
function deferred() {
let methods;
const promise = new Promise((resolve, reject) => {
methods = { resolve, reject };
});
return Object.assign(promise, methods);
}
// Actual dispatch_json code begins here.
// Warning! The values in this enum are duplicated in dispatch_json.rs
// Update carefully!
let InternalErrorKinds;
(function (InternalErrorKinds) {
InternalErrorKinds[(InternalErrorKinds["JsonIoError"] = 1)] = "JsonIoError";
InternalErrorKinds[(InternalErrorKinds["JsonSyntaxError"] = 2)] =
"JsonSyntaxError";
InternalErrorKinds[(InternalErrorKinds["JsonDataError"] = 3)] =
"JsonDataError";
InternalErrorKinds[(InternalErrorKinds["JsonEofError"] = 4)] = "JsonEofError";
})(InternalErrorKinds || (InternalErrorKinds = {}));
export class InternalDispatchJsonError extends Error {
constructor(kind, msg) {
super(msg);
this.name = InternalErrorKinds[kind];
}
}
const core = Deno["core"];
function decode(ui8) {
const s = core.decode(ui8);
return JSON.parse(s);
}
function encode(args) {
const s = JSON.stringify(args);
return core.encode(s);
}
/** Json based dispatch wrapper for core ops.
*
* Error kind mapping is controlled by errorFactory. Async handler is automatically
* set during construction.
*
* const opId = Deno.ops()["json_op"];
* const jsonOp = new DispatchJsonOp(opId, (kind, msg) => return new CustomError(kind, msg));
* const response = jsonOp.dispatchSync({ data });
* console.log(response.items[3].name);
*/
export class DispatchJsonOp {
constructor(opId, errorFactory) {
this.opId = opId;
this.errorFactory = errorFactory;
this.promiseTable = new Map();
this._nextPromiseId = 1;
}
nextPromiseId() {
return this._nextPromiseId++;
}
unwrapResponse(res) {
if (res.err != null) {
if (res.err.kind < 0) {
throw new InternalDispatchJsonError(
res.err.kind * -1, // Transform kind back to positive
res.err.message
);
} else if (res.err.kind > 0) {
throw this.errorFactory(res.err.kind, res.err.message);
} else {
throw new Error(res.err.message);
}
}
assert(res.ok != null);
return res.ok;
}
handleAsync(resUi8) {
const res = decode(resUi8);
assert(res.promiseId != null);
const promise = this.promiseTable.get(res.promiseId);
assert(promise != null);
this.promiseTable.delete(res.promiseId);
promise.resolve(res);
}
dispatch(args = {}, zeroCopy) {
const argsUi8 = encode(args);
return core.dispatch(this.opId, argsUi8, zeroCopy);
}
// Dispatch this op with a Sync call
dispatchSync(args = {}, zeroCopy) {
const resUi8 = this.dispatch(args, zeroCopy);
assert(resUi8 != null);
const res = decode(resUi8);
assert(res.promiseId == null);
return this.unwrapResponse(res);
}
// Dispatch this op with a Async call
async dispatchAsync(args = {}, zeroCopy) {
const promiseId = this.nextPromiseId();
args = Object.assign(args, { promiseId });
const promise = deferred();
const argsUi8 = encode(args);
const buf = this.dispatch(args, zeroCopy);
if (buf) {
// Sync result.
const res = decode(buf);
promise.resolve(res);
} else {
// Async result.
this.promiseTable.set(promiseId, promise);
}
const res = await promise;
return this.unwrapResponse(res);
}
}
Loading

0 comments on commit 1f1a882

Please sign in to comment.