diff --git a/package-lock.json b/package-lock.json index 3697ba94..56c98eba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -398,11 +398,11 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, "@octokit/endpoint": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.1.7.tgz", - "integrity": "sha512-MfsXHx9z9EPxLYSf7PYuzWvVZTotx+/QTFk7UMp4Fv83k3QrvmovEjP0pl141g+Uq/w9CcDuuXhsiq4X3oxVsA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.2.0.tgz", + "integrity": "sha512-g4r1MKr8GJ8qubJQp3HP3JrxDY+ZeVqjYBTgtu1lPEDLhfQDY6rOhyZOoHKOw+gaIF6aAcmuvPPNZUro2OwmOg==", "requires": { - "deepmerge": "3.2.1", + "deepmerge": "3.3.0", "is-plain-object": "^3.0.0", "universal-user-agent": "^2.1.0", "url-template": "^2.0.8" @@ -493,12 +493,12 @@ "integrity": "sha512-kNBtphcH7c4cr80v2Ea64EzBYJEgiryopOVbj14qOXmxhVt8r9dTKNNfGSnQxjWE0fIb7a6+U6l0qEs1OM0o3w==" }, "@open-rpc/schema-utils-js": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@open-rpc/schema-utils-js/-/schema-utils-js-1.10.3.tgz", - "integrity": "sha512-QkZRxyoYxUPjDNRdODDgb302I/qZkkGFfi+lmrLxvaoL9PsyNOysipNx7cMyxA/XNT+oysZUVpjek0qhKBpbhw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@open-rpc/schema-utils-js/-/schema-utils-js-1.11.0.tgz", + "integrity": "sha512-ELsuy44v3WVvJVqWw3InH1wldlFtoW5mWZV4Z9mujHJZ9/ppbF9awpot1XfEqMqoUeEQ7DdK9fvGgxvgz2NFtw==", "requires": { - "@open-rpc/meta-schema": "^1.3.2", - "@qiwi/semantic-release-gh-pages-plugin": "^1.9.1", + "@open-rpc/meta-schema": "^1.4.3", + "@qiwi/semantic-release-gh-pages-plugin": "^1.10.0", "@semantic-release/changelog": "^3.0.4", "@semantic-release/commit-analyzer": "^6.2.0", "@semantic-release/git": "^7.0.12", @@ -515,9 +515,9 @@ }, "dependencies": { "json-schema-ref-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-7.0.1.tgz", - "integrity": "sha512-dyKV6n/x9oyRoXrPYckbPu4P3ldcWz7ecI7zMR6AG3fNAQqdn79YFnJ/wwINXsQLgtc0LnYTkulUiTKbWZ6TTg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-7.1.0.tgz", + "integrity": "sha512-eP9+39HimQUpmqEUHRpV+oh8hiVMRU2tD6H+8uDc0raCQxX6jARN4nSJe5OpAtPt7eObuIUIsW7AwvGBzCHavQ==", "requires": { "call-me-maybe": "^1.0.1", "js-yaml": "^3.13.1", @@ -532,9 +532,9 @@ } }, "@qiwi/semantic-release-gh-pages-plugin": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@qiwi/semantic-release-gh-pages-plugin/-/semantic-release-gh-pages-plugin-1.9.1.tgz", - "integrity": "sha512-H73KDmNfpZebIf3tWotiE428dO8cR9+LHb6hs9wOqaV2648k8NMgjVtkxks/VXsuwB71vxxwCy2SqC6p9by60w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@qiwi/semantic-release-gh-pages-plugin/-/semantic-release-gh-pages-plugin-1.10.0.tgz", + "integrity": "sha512-olzBWicupCZ0/n7vfO0anWLeaLkXckZs4SmmHyForHaI0tF3pQ6emjXy8LJOli66PKoTcmB8JsOwTHb8/+itpA==", "requires": { "aggregate-error": "^3.0.0", "dot": "^1.1.2", @@ -542,7 +542,7 @@ "lodash": "^4.17.11", "read-pkg": "^5.1.1", "sync-request": "^6.1.0", - "tslib": "^1.9.3" + "tslib": "^1.10.0" }, "dependencies": { "read-pkg": { @@ -2102,9 +2102,9 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deepmerge": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.1.tgz", - "integrity": "sha512-+hbDSzTqEW0fWgnlKksg7XAOtT+ddZS5lHZJ6f6MdixRs9wQy+50fm1uUCVb1IkvjLUYX/SfFO021ZNwriURTw==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" }, "define-properties": { "version": "1.1.3", @@ -3592,9 +3592,9 @@ }, "dependencies": { "@types/node": { - "version": "10.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.9.tgz", - "integrity": "sha512-NelG/dSahlXYtSoVPErrp06tYFrvzj8XLWmKA+X8x0W//4MqbUyZu++giUG/v0bjAT6/Qxa8IjodrfdACyb0Fg==" + "version": "10.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.10.tgz", + "integrity": "sha512-V8wj+w2YMNvGuhgl/MA5fmTxgjmVHVoasfIaxMMZJV6Y8Kk+Ydpi1z2whoShDCJ2BuNVoqH/h1hrygnBxkrw/Q==" } } }, @@ -9248,9 +9248,9 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, "semver": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", - "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", + "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==" }, "string-width": { "version": "3.1.0", diff --git a/package.json b/package.json index f2bb8a03..e9e3df18 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "license": "Apache-2.0", "dependencies": { "@open-rpc/meta-schema": "^1.4.3", - "@open-rpc/schema-utils-js": "^1.7.0", + "@open-rpc/schema-utils-js": "^1.11.0", "body-parser": "^1.18.3", "commander": "^2.19.0", "connect": "^3.6.6", diff --git a/src/router.test.ts b/src/router.test.ts index 1eae722f..1fb4b54b 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -1,7 +1,7 @@ import { Router, IMethodMapping } from "./router"; import examples from "@open-rpc/examples"; import _ from "lodash"; -import { parseOpenRPCDocument } from "@open-rpc/schema-utils-js"; +import { parseOpenRPCDocument, MethodNotFoundError } from "@open-rpc/schema-utils-js"; import { OpenRPC, ContentDescriptorObject, @@ -56,6 +56,24 @@ describe("router", () => { expect(result).toBe(4); }); + it("returns not found error when using incorrect method", async () => { + const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); + const result = await router.call("foobar", [2, 2]); + expect(result.error.code).toBe(-32601); + }); + + it("returns param validation error when passing incorrect params", async () => { + const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); + const result = await router.call("addition", ["123", "321"]); + expect(result.error.code).toBe(-32602); + }); + + it("implements service discovery", async () => { + const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); + const result = await router.call("rpc.discover", []); + expect(result).toEqual(parsedExample); + }); + it("Simple math call validates params", async () => { const router = new Router(parsedExample, makeMethodMapping(parsedExample.methods)); expect(await router.call("addition", ["2", 2])).toEqual({ @@ -75,7 +93,7 @@ describe("router", () => { it("works in mock mode with unknown params", async () => { const router = new Router(parsedExample, { mockMode: true }); - const result = await router.call("addition", [6, 2]); + const result = await router.call("addition", [6, 2]); expect(typeof result).toBe("number"); }); } diff --git a/src/router.ts b/src/router.ts index 2625960c..6f3f1dc7 100644 --- a/src/router.ts +++ b/src/router.ts @@ -6,7 +6,7 @@ import { ContentDescriptorObject, OpenRPC, } from "@open-rpc/meta-schema"; -import { MethodCallValidator } from "@open-rpc/schema-utils-js"; +import { MethodCallValidator, MethodNotFoundError, ParameterValidationError } from "@open-rpc/schema-utils-js"; const jsf = require("json-schema-faker"); // tslint:disable-line export interface IMethodMapping { @@ -20,6 +20,7 @@ export interface IMockModeSettings { export type TMethodHandler = (...args: any) => Promise; export class Router { + public static methodNotFoundHandler(methodName: string) { return { error: { @@ -29,7 +30,6 @@ export class Router { }, }; } - private methods: IMethodMapping; private methodCallValidator: MethodCallValidator; @@ -42,21 +42,22 @@ export class Router { } else { this.methods = methodMapping as IMethodMapping; } + this.methods["rpc.discover"] = this.serviceDiscoveryHandler.bind(this); this.methodCallValidator = new MethodCallValidator(this.openrpcDocument); } public async call(methodName: string, params: any[]) { const validationErrors = this.methodCallValidator.validate(methodName, params); + + if (validationErrors instanceof MethodNotFoundError) { + return Router.methodNotFoundHandler(methodName); + } + if (validationErrors.length > 0) { - return { - error: { - code: -32602, - data: validationErrors, - message: "Invalid params", - }, - }; + return this.invalidParamsHandler(validationErrors); } + try { return await this.methods[methodName](...params); } catch (e) { @@ -68,6 +69,10 @@ export class Router { return this.methods[methodName] !== undefined; } + private serviceDiscoveryHandler(): Promise { + return Promise.resolve(this.openrpcDocument); + } + private buildMockMethodMapping(methods: MethodObject[]): IMethodMapping { return _.chain(methods) .keyBy("name") @@ -87,4 +92,14 @@ export class Router { .value(); } + private invalidParamsHandler(errs: ParameterValidationError[]) { + return { + error: { + code: -32602, + data: errs, + message: "Invalid params", + }, + }; + } + } diff --git a/src/transports/server-transport.ts b/src/transports/server-transport.ts index 26a77d25..2e20f926 100644 --- a/src/transports/server-transport.ts +++ b/src/transports/server-transport.ts @@ -15,7 +15,7 @@ export default abstract class ServerTransport { protected async routerHandler(id: string | number, methodName: string, params: any[]) { if (this.routers.length === 0) { console.warn("transport method called without a router configured."); // tslint:disable-line - return Router.methodNotFoundHandler(methodName); + return new Error("No router configured"); } const routerForMethod = _.find(