diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..282422f8 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,11 @@ +export class JSONRPCError extends Error { + public code: number; + public message: string; + public data?: any; + constructor(message: string, code: number, data?: any) { + super(); + this.code = code; + this.message = message; + this.data = data; + } +} diff --git a/src/index.ts b/src/index.ts index d1ea7e88..ce09bb9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ import Server, { IServerOptions } from "./server"; import { Router } from "./router"; +import { JSONRPCError } from "./error"; export { Server, IServerOptions, Router, + JSONRPCError, }; diff --git a/src/router.test.ts b/src/router.test.ts index 1fb4b54b..616df556 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -9,10 +9,11 @@ import { ExamplePairingObject, MethodObject, } from "@open-rpc/meta-schema"; +import { JSONRPCError } from "./error"; const jsf = require("json-schema-faker"); // tslint:disable-line const makeMethodMapping = (methods: MethodObject[]): IMethodMapping => { - return _.chain(methods) + const methodMapping = _.chain(methods) .keyBy("name") .mapValues((methodObject: MethodObject) => async (...args: any): Promise => { const foundExample = _.find( @@ -28,6 +29,9 @@ const makeMethodMapping = (methods: MethodObject[]): IMethodMapping => { } }) .value(); + methodMapping["test-error"] = async () => { throw new JSONRPCError("test error", 9998, { meta: "data" }); }; + methodMapping["unknown-error"] = async () => { throw new Error("unanticpated crash"); }; + return methodMapping; }; describe("router", () => { @@ -37,6 +41,11 @@ describe("router", () => { let parsedExample: OpenRPC; beforeAll(async () => { parsedExample = await parseOpenRPCDocument(JSON.stringify(example)); + // Mock error methods used to test routing calls + const testErrorMethod = { name: "test-error", params: [], result: { name: "test-error-result", schema: {} } }; + const unknownErrorMethod = Object.assign({}, testErrorMethod, { name: "unknown-error" }); + parsedExample.methods.push(testErrorMethod); + parsedExample.methods.push(unknownErrorMethod); }); it("is constructed with an OpenRPC document and a method mapping", () => { @@ -68,6 +77,20 @@ describe("router", () => { expect(result.error.code).toBe(-32602); }); + it("returns JSONRPCError data when thrown", async () => { + const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); + const result = await router.call("test-error", []); + expect(result.error.code).toBe(9998); + expect(result.error.message).toBe("test error"); + }); + + it("returns Unknown Error data when thrown", async () => { + const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); + const result = await router.call("unknown-error", []); + expect(result.error.code).toBe(6969); + expect(result.error.message).toBe("unknown error"); + }); + it("implements service discovery", async () => { const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); const result = await router.call("rpc.discover", []); diff --git a/src/router.ts b/src/router.ts index 6f3f1dc7..be40e501 100644 --- a/src/router.ts +++ b/src/router.ts @@ -7,6 +7,8 @@ import { OpenRPC, } from "@open-rpc/meta-schema"; import { MethodCallValidator, MethodNotFoundError, ParameterValidationError } from "@open-rpc/schema-utils-js"; +import { JSONRPCError } from "./error"; + const jsf = require("json-schema-faker"); // tslint:disable-line export interface IMethodMapping { @@ -61,7 +63,10 @@ export class Router { try { return await this.methods[methodName](...params); } catch (e) { - return { code: 6969, message: "unknown error" }; + if (e instanceof JSONRPCError) { + return {error: { code: e.code, message: e.message, data: e.data }}; + } + return { error: { code: 6969, message: "unknown error" } }; } }