Add ability to wrap method calls in jsonrpc

This commit is contained in:
Jerko Steiner 2019-08-30 13:19:13 +07:00
parent 4ea534293b
commit 9cc5a7affb
3 changed files with 83 additions and 4 deletions

View File

@ -1,10 +1,11 @@
import bodyParser from 'body-parser'
import express from 'express'
import request from 'supertest'
import {IRequest} from './jsonrpc'
import {createClient} from './supertest'
import {ensure} from './ensure'
import {jsonrpc} from './express'
import {noopLogger} from './test-utils'
import {ensure} from './ensure'
describe('jsonrpc', () => {
@ -240,6 +241,54 @@ describe('jsonrpc', () => {
.get(`/myService?jsonrpc=2.0&id=1&method=add&params=${params}`)
.expect(405)
})
describe('wrapCall', () => {
let requests: IRequest[] = []
let results: any[] = []
function create() {
requests = []
results = []
userId = 1000
const app = express()
const myService = new Service(5)
// console.log('service', myService, Object.
app.use(bodyParser.json())
app.use('/',
jsonrpc(
req => ({userId}),
noopLogger,
async (path, req, makeRequest) => {
requests.push(req)
const result = await makeRequest()
results.push(result)
return result
},
)
.addService('/myService', myService)
.router(),
)
return app
}
it('should wrap POST rpc method call', async () => {
const req = {
jsonrpc: '2.0',
id: 1,
method: 'add',
params: [1, 2],
}
const response = await request(create())
.post('/myService')
.send(req)
expect(response.body.result).toEqual(3)
expect(requests).toEqual([ req ])
expect(results).toEqual([ response.body ])
})
})
})
})

View File

@ -8,6 +8,7 @@ import {NextFunction, Request, Response, Router} from 'express'
import {createError, isJSONRPCError, IJSONRPCError, IError} from './error'
import {
createRpcService, ERROR_SERVER, ERROR_INVALID_PARAMS, ERROR_METHOD_NOT_FOUND,
IRequest,
} from './jsonrpc'
export type TGetContext<Context> = (req: Request) => Context
@ -21,9 +22,19 @@ export interface IJSONRPCReturnType {
router(): Router
}
async function wrap<A, R>(
path: string, request: A, fn: () => Promise<R>): Promise<R> {
const result = await fn()
return result
}
export function jsonrpc<Context>(
getContext: TGetContext<Context>,
logger: ILogger,
wrapCall: <A extends IRequest, R>(
path: string,
request: A,
fn: (request?: A) => Promise<R>) => Promise<R> = wrap,
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
): IJSONRPCReturnType {
@ -94,13 +105,15 @@ export function jsonrpc<Context>(
method: req.query.method,
params: JSON.parse(req.query.params),
}
rpcService.invoke(request, getContext(req))
wrapCall(path, request,
(body = request) => rpcService.invoke(body, getContext(req)))
.then(response => handleResponse(response, res))
.catch(next)
})
router.post(path, (req, res, next) => {
rpcService.invoke(req.body, getContext(req))
wrapCall(path, req.body,
(body = req.body) => rpcService.invoke(body, getContext(req)))
.then(response => handleResponse(response, res))
.catch(next)
})

View File

@ -45,6 +45,21 @@ export function pick<T, K extends FunctionPropertyNames<T>>(t: T, keys: K[])
}, {} as Pick<T, K>)
}
export function getAllMethods<T>(obj: T): Array<FunctionPropertyNames<T>> {
const props = new Set<string>()
do {
const l = Object.getOwnPropertyNames(obj)
.filter((p, i, arr) => {
return typeof (obj as any)[p] === 'function' &&
p !== 'constructor'
})
.forEach(p => props.add(p))
obj = Object.getPrototypeOf(obj)
} while (Object.getPrototypeOf(obj))
return Array.from(props) as unknown as Array<FunctionPropertyNames<T>>
}
export interface IRequest<M extends string = any, A = any[]> {
jsonrpc: '2.0'
id: TId | null
@ -104,7 +119,9 @@ export const createRpcService = <T, M extends FunctionPropertyNames<T>>(
service: T,
methods?: M[],
) => {
const rpcService = methods ? pick(service, methods) : service
const rpcService = methods ? pick(service, methods) :
pick(service, getAllMethods(service))
return {
async invoke<Context>(
req: IRequest<M, ArgumentTypes<T[M]>>,