diff --git a/packages/p3/src/main/ts/sinap/decortors/index.ts b/packages/p3/src/main/ts/sinap/decortors/index.ts index a5f73a1..ba9f8f6 100644 --- a/packages/p3/src/main/ts/sinap/decortors/index.ts +++ b/packages/p3/src/main/ts/sinap/decortors/index.ts @@ -1,5 +1,6 @@ +import {IProto} from '@qiwi/decorator-utils/target/es5/interface' import {constructDecorator, METHOD, PARAM} from '@qiwi/decorator-utils' -import {JsonRpcMethod} from 'nestjs-json-rpc' +import {JSON_RPC_METADATA, JsonRpcMethod} from 'nestjs-json-rpc' import {JsonRpcData, ParamMetadataKeys} from 'expressjs-json-rpc' export type TSinapSuggest = { @@ -8,14 +9,89 @@ export type TSinapSuggest = { locale: string } +export class SinapSuggestRequest implements TSinapSuggest { + + query: string + fields?: Record + locale: string + + constructor(request: TSinapSuggest) { + this.fields = request.fields + this.locale = request.locale + this.query = request.query + } + +} + export type TSinapContext = { fields?: Record locale: string } +export class SinapContextRequest implements TSinapContext { + + fields?: Record + locale: string + + constructor(request: TSinapContext) { + this.fields = request.fields + this.locale = request.locale + } + +} + +export class SinapSuggestResponse extends Array {} + +export class SinapContextResponse { + + [key: string]: any + +} + +const returnTypeMap = { + SinapSuggestResponse: 'SinapSuggest', + SinapContextResponse: 'SinapContext', +} + +const paramTypeMap = { + SinapSuggestRequest: 'SinapSuggest', + SinapContextRequest: 'SinapContext', +} + +const decoratorCompatibilityCheck = ( + proto: IProto, + propName: string | undefined, + method: string, + ctor: Function, +) => { + const returnType = Reflect.getMetadata('design:returntype', proto, propName + '')?.name + // @ts-ignore + if (returnType && returnTypeMap[returnType] !== method) { + throw new Error(`Cannot return ${returnType} in ${method}`) + } + + const sinapParam = Reflect.getMetadata('design:paramtypes', proto, propName + '') + const meta = Reflect.getMetadata(JSON_RPC_METADATA, ctor) + // @ts-ignore + meta?.[propName]?.params.forEach(({index, type}) => { + if (type === 'params') { + // @ts-ignore + if (paramTypeMap[sinapParam[index].name] === undefined) { + throw new Error(`Cannot use ${sinapParam[index].name} class with this parameter decorator`) + } + + // @ts-ignore + if (paramTypeMap[sinapParam[index].name] !== method) { + throw new Error(`Class ${sinapParam[index].name} not compatible with ${method} method`) + } + } + }) +} + export const SinapSuggest = (arg?: any) => { - return constructDecorator(({targetType, proto, propName, paramIndex}) => { + return constructDecorator(({targetType, proto, propName, paramIndex, ctor}) => { if (targetType === METHOD) { + decoratorCompatibilityCheck(proto, propName, 'SinapSuggest', ctor) JsonRpcMethod(`suggest.${arg}`)(proto, propName!) } @@ -27,8 +103,9 @@ export const SinapSuggest = (arg?: any) => { } export const SinapContext = (arg?: any) => { - return constructDecorator(({targetType, proto, propName, paramIndex}) => { + return constructDecorator(({targetType, proto, propName, paramIndex, ctor}) => { if (targetType === METHOD) { + decoratorCompatibilityCheck(proto, propName, 'SinapContext', ctor) JsonRpcMethod(`context.${arg}`)(proto, propName!) } diff --git a/packages/p3/src/test/ts/index.ts b/packages/p3/src/test/ts/index.ts index 7ea604e..7000b37 100644 --- a/packages/p3/src/test/ts/index.ts +++ b/packages/p3/src/test/ts/index.ts @@ -3,12 +3,12 @@ import {HttpStatus} from '@nestjs/common' import {NestApplication} from '@nestjs/core' import request from 'supertest' import {RpcId} from 'nestjs-json-rpc' -// import {METHOD} from '@qiwi/decorator-utils' + import { P3Provider, Auth, ClientAuth, - TSinapSuggest, + SinapSuggestRequest, SinapSuggest, SinapContext, Client, @@ -17,6 +17,8 @@ import { TSecurity, ClientType, SecurityLevel, + SinapContextResponse, + SinapSuggestResponse, } from '../../main/ts' describe('P3', () => { @@ -27,13 +29,14 @@ describe('P3', () => { @SinapSuggest('test2') bar( @RpcId() id: string, - @SinapSuggest() params: TSinapSuggest, + @SinapSuggest() params: SinapSuggestRequest, @Auth() auth: string, @ClientAuth() clientAuth: string, @Client() client: TClient, @Security() security: TSecurity, - ) { - return { + ): SinapSuggestResponse { + // @ts-ignore + return [{ foo: 'bar', id, params, @@ -41,7 +44,7 @@ describe('P3', () => { clientAuth, client, security, - } + }] } } @@ -87,7 +90,7 @@ describe('P3', () => { .expect({ jsonrpc: '2.0', id: '123', - result: { + result: [{ foo: 'bar', id: '123', params: {fields: {a: '123', foo: 'bar'}, locale: 'baz', query: 'qwe'}, @@ -95,7 +98,7 @@ describe('P3', () => { clientAuth: 'Client-Authorization test3344', client: {clientId: 'SINAP-CLIENT', clientType: 'SINAP'}, security: {level: 0}, - }, + }], }) }) }) @@ -106,12 +109,12 @@ describe('P3', () => { @Security(SecurityLevel.SECURE) @SinapContext('test2') - bar() { + bar(): SinapContextResponse { return {foo: 'bar'} } @SinapContext('test1') - baz() { + baz(): SinapContextResponse { return {foo: 'bar'} }