Skip to content

Commit

Permalink
feat(expressjs): add req metaResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
pismenskiy authored Mar 19, 2020
1 parent a42891e commit 7fddc73
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 184 deletions.
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"postupdate": "yarn && yarn build && yarn test"
},
"dependencies": {
"@qiwi/substrate": "^1.18.14",
"@qiwi/substrate": "^1.18.16",
"@types/express": "^4.17.3",
"@types/lodash": "^4.14.149",
"lodash": "^4.17.15",
Expand Down
19 changes: 10 additions & 9 deletions packages/expressjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,26 @@
"postupdate": "yarn && yarn build && yarn test"
},
"dependencies": {
"@qiwi/substrate": "^1.18.14",
"@qiwi/json-rpc-common": "^1.0.2",
"@qiwi/substrate": "^1.18.16",
"@types/express": "^4.17.3",
"@types/lodash": "^4.14.149",
"jsonrpc-lite": "^2.1.0",
"reflect-metadata": "^0.1.13",
"class-transformer": "^0.2.3",
"class-validator": "^0.11.0",
"jsonrpc-lite": "^2.1.0",
"lodash": "^4.17.15",
"reflect-metadata": "^0.1.13",
"rxjs": "^6.5.4",
"tslib": "^1.11.1",
"lodash": "^4.17.15"
"tslib": "^1.11.1"
},
"devDependencies": {
"@qiwi/semantic-release-gh-pages-plugin": "^1.15.9",
"@qiwi/substrate-types": "^1.30.0",
"@semantic-release/changelog": "^5.0.0",
"@semantic-release/git": "^9.0.0",
"@semantic-release/github": "^7.0.4",
"@semantic-release/npm": "^7.0.4",
"@swissquote/crafty-preset-jest": "^1.10.0",
"@types/jest": "^25.1.4",
"@types/jest-json-schema": "^2.1.1",
"coveralls": "^3.0.9",
Expand All @@ -68,6 +70,8 @@
"jest": "^25.1.0",
"microbundle": "^0.12.0-next.3",
"replace-in-file": "^5.0.2",
"reqresnext": "^1.6.3",
"rimraf": "^3.0.2",
"semantic-release": "^17.0.4",
"terser": "^4.6.6",
"ts-jest": "^25.2.1",
Expand All @@ -76,10 +80,7 @@
"typedoc": "^0.16.11",
"typedoc-plugin-external-module-name": "^3.0.0",
"typescript": "^3.8.3",
"typescript-eslint-parser": "^22.0.0",
"rimraf": "^3.0.2",
"@swissquote/crafty-preset-jest": "^1.10.0",
"reqresnext": "^1.6.3"
"typescript-eslint-parser": "^22.0.0"
},
"repository": {
"type": "git",
Expand Down
61 changes: 44 additions & 17 deletions packages/expressjs/src/main/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import {
TRpcMeta,
Extender,
parseJsonRpcObject,
IParsedObjectRequest,
RequestObject,
IParsedObject,
error,
success,
JsonRpcError,
OK,
} from '@qiwi/json-rpc-common'

import {IMetaTypedValue} from '@qiwi/substrate'
export * from '@qiwi/json-rpc-common'

export const enum JsonRpcDecoratorType {
Expand All @@ -27,26 +26,28 @@ export const enum JsonRpcDecoratorType {
PARAMS = 'params',
}

type IJsonRpcMetaTypedValue = IMetaTypedValue<IParsedObject, 'jsonRpc', {}>

export function JsonRpcMiddleware(): ClassDecorator {
return <TFunction extends Function>(target: TFunction) => {

const extend: Extender = (base) => {
class Extended extends base {

protected middleware({body}: Request, res: Response): any {
const jsonRpc = parseJsonRpcObject(body)
if (Array.isArray(jsonRpc)) {
protected middleware(req: Request, res: Response): any {
const boxedJsonRpc = (this.constructor as any).parseRequest(req)
if (!boxedJsonRpc) {
// TODO
return
}

if (jsonRpc.type === 'request') {
const {params, handler} = (this.constructor as any).resolveHandler(this, jsonRpc)
const {payload: {id, method}} = jsonRpc
if (boxedJsonRpc.value.type === 'request') {
const {value: {payload: {id, method}}} = boxedJsonRpc
const {params, handler} = (this.constructor as any).resolveHandler(this, boxedJsonRpc)

// @ts-ignore
if (!handler) {
res.status(OK).send(error(id, JsonRpcError.methodNotFound(method)))

return
}

Expand All @@ -60,6 +61,21 @@ export function JsonRpcMiddleware(): ClassDecorator {

}

static parseRequest(req: Request): IJsonRpcMetaTypedValue | undefined {
const jsonRpc = parseJsonRpcObject(req.body)

if (Array.isArray(jsonRpc)) {
// TODO
return
}

return {
meta: {},
value: jsonRpc,
type: 'jsonRpc',
}
}

static handleResult(result: any): any {
if (result instanceof JsonRpcError) {
return result
Expand All @@ -72,9 +88,16 @@ export function JsonRpcMiddleware(): ClassDecorator {
return result
}

static resolveHandler(instance: Extended, jsonRpc: IParsedObjectRequest): {handler: Function, params: any[]} | {[key: string]: any} {
static resolveHandler(instance: Extended, boxedJsonRpc: IJsonRpcMetaTypedValue): {handler: Function, params: any[]} | {[key: string]: any} {
if (Array.isArray(boxedJsonRpc.value)) {
throw new Error('unexpected error')
}

const _method = jsonRpc.payload.method
if (boxedJsonRpc.value.type !== 'request') {
throw new Error('unexpected error')
}

const _method = boxedJsonRpc.value.payload.method

const meta = Reflect.getMetadata(JSON_RPC_METADATA, this) || {}
const methodMeta: TRpcMethodEntry | undefined = (Object as any).values(meta)
Expand All @@ -88,7 +111,7 @@ export function JsonRpcMiddleware(): ClassDecorator {
const handler = this.prototype[propKey]
const paramTypes = Reflect.getMetadata('design:paramtypes', instance, propKey)
const params = (methodMeta.params || []).map((param: TRpcMethodParam, index: number) => {
return this.resolveParam(jsonRpc.payload, paramTypes[index], param)
return this.resolveParam(boxedJsonRpc, paramTypes[index], param)
})

return {
Expand All @@ -97,15 +120,19 @@ export function JsonRpcMiddleware(): ClassDecorator {
}
}

static resolveParam(payload: RequestObject, Param: any, {type, value}: TRpcMethodParam) {
static resolveParam(boxedJsonRpc: IJsonRpcMetaTypedValue, Param: any, {type, value}: TRpcMethodParam) {
let data

if (type === JsonRpcDecoratorType.ID) {
data = payload.id
if (boxedJsonRpc.value.type === 'request') {
data = boxedJsonRpc.value.payload.id
}
}
else {
data = payload.params
data = value ? get(data, value) : data
if (boxedJsonRpc.value.type === 'request') {
data = boxedJsonRpc.value.payload.params
data = value ? get(data, value) : data
}
}

return typeof Param === 'function'
Expand Down
148 changes: 120 additions & 28 deletions packages/expressjs/src/test/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('expressjs-json-rpc', () => {
const controller = new SomeJsonRpcController()
const mware = controller.middleware.bind(controller)

it('properly handles requrest', () => {
it('properly handles request', () => {
const {req, res} = reqresnext({
body: {
jsonrpc: '2.0',
Expand All @@ -62,11 +62,13 @@ describe('expressjs-json-rpc', () => {
mware(req, res)

expect(res.statusCode).toBe(OK)
expect(res.body).toBe(JSON.stringify({
jsonrpc: '2.0',
id: '123',
result: {foo: 'quxr', id: '123', a: 'a', b: 2},
}))
expect(res.body).toBe(
JSON.stringify({
jsonrpc: '2.0',
id: '123',
result: {foo: 'quxr', id: '123', a: 'a', b: 2},
}),
)
})

it('finds proper method', () => {
Expand All @@ -85,11 +87,13 @@ describe('expressjs-json-rpc', () => {
mware(req, res)

expect(res.statusCode).toBe(OK)
expect(res.body).toBe(JSON.stringify({
jsonrpc: '2.0',
id: '123',
result: {foo: 'bar', id: '123'},
}))
expect(res.body).toBe(
JSON.stringify({
jsonrpc: '2.0',
id: '123',
result: {foo: 'bar', id: '123'},
}),
)
})

it('returns error if method does not exist', () => {
Expand All @@ -105,15 +109,17 @@ describe('expressjs-json-rpc', () => {
mware(req, res)

expect(res.statusCode).toBe(OK)
expect(res.body).toBe(JSON.stringify({
jsonrpc: '2.0',
id: '111',
error: {
message: 'Method not found',
code: -32601,
data: 'foobar',
},
}))
expect(res.body).toBe(
JSON.stringify({
jsonrpc: '2.0',
id: '111',
error: {
message: 'Method not found',
code: -32601,
data: 'foobar',
},
}),
)
})

it('returns error if method returns JsonRpcError', () => {
Expand All @@ -129,14 +135,100 @@ describe('expressjs-json-rpc', () => {
mware(req, res)

expect(res.statusCode).toBe(OK)
expect(res.body).toBe(JSON.stringify({
jsonrpc: '2.0',
id: '111',
error: {
message: 'Some error',
code: -100000,
},
}))
expect(res.body).toBe(
JSON.stringify({
jsonrpc: '2.0',
id: '111',
error: {
message: 'Some error',
code: -100000,
},
}),
)
})

describe('static', () => {
describe('parseRequest', () => {
it('parseRequest return IJsonRpcMetaTypedValue', () => {
// @ts-ignore
const res = SomeJsonRpcController.parseRequest({
headers: {},
body: {
id: 1,
jsonrpc: '2.0',
method: 'test',
params: {
fields: {},
},
},
})
expect(res).toMatchObject({
meta: {},
type: 'jsonRpc',
value: {
type: 'request',
payload: {
jsonrpc: '2.0',
id: 1,
method: 'test',
params: {
fields: {},
},
},
},
})
})
})

describe('resolveHandler', () => {
it('work correctly', () => {
// @ts-ignore
const boxedRpc = SomeJsonRpcController.parseRequest({
headers: {},
body: {
id: 1,
jsonrpc: '2.0',
method: 'test2',
params: {
fields: {},
},
},
})
// @ts-ignore
expect(SomeJsonRpcController.resolveHandler(
new SomeJsonRpcController(),
boxedRpc,
),
).toMatchObject({
params: [{0: '1'}, {fields: {}}],
})
})
})

describe('resolveParam', () => {
it('work correctly', () => {
// @ts-ignore
const boxedRpc = SomeJsonRpcController.parseRequest({
headers: {},
body: {
id: 1,
jsonrpc: '2.0',
method: 'test2',
params: {
a: 10,
b: 'foo',
},
},
})
// @ts-ignore
expect(SomeJsonRpcController.resolveParam(boxedRpc, Abc, {
index: 1,
type: 'params',
value: undefined,
}),
).toMatchObject({a: 10, b: 'foo'})
})
})
})
})
})
2 changes: 1 addition & 1 deletion packages/nestjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"dependencies": {
"@nestjs/common": "^6.11.11",
"@nestjs/core": "^6.11.11",
"@qiwi/substrate": "^1.18.14",
"@qiwi/substrate": "^1.18.16",
"@qiwi/json-rpc-common": "^1.0.2",
"expressjs-json-rpc": "^1.0.3",
"@types/express": "^4.17.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/open-rpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"dependencies": {
"@nestjs/common": "^6.11.11",
"@nestjs/core": "^6.11.11",
"@qiwi/substrate": "^1.18.14",
"@qiwi/substrate": "^1.18.16",
"@types/express": "^4.17.3",
"@types/lodash": "^4.14.149",
"jsonrpc-lite": "^2.1.0",
Expand Down
Loading

0 comments on commit 7fddc73

Please sign in to comment.