diff --git a/packages/microservices/client/client-grpc.ts b/packages/microservices/client/client-grpc.ts index 3110ca0d453..373ab68e15e 100644 --- a/packages/microservices/client/client-grpc.ts +++ b/packages/microservices/client/client-grpc.ts @@ -10,6 +10,7 @@ import { ClientGrpc, GrpcOptions } from '../interfaces'; import { ClientProxy } from './client-proxy'; import { GRPC_CANCELLED } from './constants'; import { ChannelOptions } from '../external/grpc-options.interface'; +import { getGrpcPackageDefinition } from '../helpers'; let grpcPackage: any = {}; let grpcProtoLoaderPackage: any = {}; @@ -286,16 +287,11 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc { public loadProto(): any { try { - const file = this.getOptionsProp(this.options, 'protoPath'); - const loader = this.getOptionsProp(this.options, 'loader'); - - const packageDefinition = - this.getOptionsProp(this.options, 'packageDefinition') || - grpcProtoLoaderPackage.loadSync(file, loader); - - const packageObject = - grpcPackage.loadPackageDefinition(packageDefinition); - return packageObject; + const packageDefinition = getGrpcPackageDefinition( + this.options, + grpcProtoLoaderPackage, + ); + return grpcPackage.loadPackageDefinition(packageDefinition); } catch (err) { const invalidProtoError = new InvalidProtoDefinitionException(err.path); const message = diff --git a/packages/microservices/errors/invalid-grpc-package-definition-missing-package-definition.exception.ts b/packages/microservices/errors/invalid-grpc-package-definition-missing-package-definition.exception.ts new file mode 100644 index 00000000000..40ce6854c07 --- /dev/null +++ b/packages/microservices/errors/invalid-grpc-package-definition-missing-package-definition.exception.ts @@ -0,0 +1,7 @@ +import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception'; + +export class InvalidGrpcPackageDefinitionMissingPacakgeDefinitionException extends RuntimeException { + constructor() { + super('protoPath or packageDefinition must be defined'); + } +} diff --git a/packages/microservices/errors/invalid-grpc-package-definition-mutex.exception.ts b/packages/microservices/errors/invalid-grpc-package-definition-mutex.exception.ts new file mode 100644 index 00000000000..8720b688acb --- /dev/null +++ b/packages/microservices/errors/invalid-grpc-package-definition-mutex.exception.ts @@ -0,0 +1,9 @@ +import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception'; + +export class InvalidGrpcPackageDefinitionMutexException extends RuntimeException { + constructor() { + super( + 'Both protoPath and packageDefinition were provided, but only one can be defined', + ); + } +} diff --git a/packages/microservices/helpers/grpc-helpers.ts b/packages/microservices/helpers/grpc-helpers.ts new file mode 100644 index 00000000000..b1d6460a415 --- /dev/null +++ b/packages/microservices/helpers/grpc-helpers.ts @@ -0,0 +1,24 @@ +import { GrpcOptions } from '../interfaces'; +import { InvalidGrpcPackageDefinitionMutexException } from '../errors/invalid-grpc-package-definition-mutex.exception'; +import { InvalidGrpcPackageDefinitionMissingPacakgeDefinitionException } from '../errors/invalid-grpc-package-definition-missing-package-definition.exception'; + +export function getGrpcPackageDefinition( + options: GrpcOptions['options'], + grpcProtoLoaderPackage: any, +) { + const file = options['protoPath']; + const packageDefinition = options['packageDefinition']; + + if ([file, packageDefinition].every(x => x != undefined)) { + throw new InvalidGrpcPackageDefinitionMutexException(); + } + + if ([file, packageDefinition].every(x => x == undefined)) { + throw new InvalidGrpcPackageDefinitionMissingPacakgeDefinitionException(); + } + + return ( + packageDefinition || + grpcProtoLoaderPackage.loadSync(file, options['loader']) + ); +} diff --git a/packages/microservices/helpers/index.ts b/packages/microservices/helpers/index.ts index ff6118b517b..e395ce7dc81 100644 --- a/packages/microservices/helpers/index.ts +++ b/packages/microservices/helpers/index.ts @@ -3,3 +3,4 @@ export * from './kafka-logger'; export * from './kafka-parser'; export * from './kafka-reply-partition-assigner'; export * from './tcp-socket'; +export * from './grpc-helpers'; diff --git a/packages/microservices/interfaces/microservice-configuration.interface.ts b/packages/microservices/interfaces/microservice-configuration.interface.ts index 11e5b414ef4..b16459a983c 100644 --- a/packages/microservices/interfaces/microservice-configuration.interface.ts +++ b/packages/microservices/interfaces/microservice-configuration.interface.ts @@ -50,7 +50,7 @@ export interface GrpcOptions { }; channelOptions?: ChannelOptions; credentials?: any; - protoPath: string | string[]; + protoPath?: string | string[]; package: string | string[]; protoLoader?: string; packageDefinition?: any; diff --git a/packages/microservices/server/server-grpc.ts b/packages/microservices/server/server-grpc.ts index e001907a062..fa7e579d73e 100644 --- a/packages/microservices/server/server-grpc.ts +++ b/packages/microservices/server/server-grpc.ts @@ -18,6 +18,7 @@ import { ChannelOptions } from '../external/grpc-options.interface'; import { CustomTransportStrategy, MessageHandler } from '../interfaces'; import { GrpcOptions } from '../interfaces/microservice-configuration.interface'; import { Server } from './server'; +import { getGrpcPackageDefinition } from '../helpers'; let grpcPackage: any = {}; let grpcProtoLoaderPackage: any = {}; @@ -380,20 +381,18 @@ export class ServerGrpc extends Server implements CustomTransportStrategy { public loadProto(): any { try { - const file = this.getOptionsProp(this.options, 'protoPath'); - const loader = this.getOptionsProp(this.options, 'loader'); - - const packageDefinition = grpcProtoLoaderPackage.loadSync(file, loader); - const packageObject = - grpcPackage.loadPackageDefinition(packageDefinition); - return packageObject; + const packageDefinition = getGrpcPackageDefinition( + this.options, + grpcProtoLoaderPackage, + ); + return grpcPackage.loadPackageDefinition(packageDefinition); } catch (err) { const invalidProtoError = new InvalidProtoDefinitionException(err.path); const message = err && err.message ? err.message : invalidProtoError.message; this.logger.error(message, invalidProtoError.stack); - throw err; + throw invalidProtoError; } } diff --git a/packages/microservices/test/client/client-grpc.spec.ts b/packages/microservices/test/client/client-grpc.spec.ts index 6d38e594167..9e5375dbd32 100644 --- a/packages/microservices/test/client/client-grpc.spec.ts +++ b/packages/microservices/test/client/client-grpc.spec.ts @@ -3,10 +3,11 @@ import { expect } from 'chai'; import { join } from 'path'; import { Observable, Subject } from 'rxjs'; import * as sinon from 'sinon'; -import { ClientGrpcProxy } from '../../client/client-grpc'; +import { ClientGrpcProxy } from '../../client'; import { InvalidGrpcPackageException } from '../../errors/invalid-grpc-package.exception'; import { InvalidGrpcServiceException } from '../../errors/invalid-grpc-service.exception'; import { InvalidProtoDefinitionException } from '../../errors/invalid-proto-definition.exception'; +import * as grpcHelpers from '../../helpers/grpc-helpers'; class NoopLogger extends Logger { log(message: any, context?: string): void {} @@ -386,13 +387,18 @@ describe('ClientGrpcProxy', () => { describe('loadProto', () => { describe('when proto is invalid', () => { it('should throw InvalidProtoDefinitionException', () => { - sinon.stub(client, 'getOptionsProp' as any).callsFake(() => { + const getPackageDefinitionStub = sinon.stub( + grpcHelpers, + 'getGrpcPackageDefinition' as any, + ); + getPackageDefinitionStub.callsFake(() => { throw new Error(); }); (client as any).logger = new NoopLogger(); expect(() => client.loadProto()).to.throws( InvalidProtoDefinitionException, ); + getPackageDefinitionStub.restore(); }); }); }); diff --git a/packages/microservices/test/helpers/grpc-helpers.spec.ts b/packages/microservices/test/helpers/grpc-helpers.spec.ts new file mode 100644 index 00000000000..4348ecb5922 --- /dev/null +++ b/packages/microservices/test/helpers/grpc-helpers.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; +import { getGrpcPackageDefinition } from '../../helpers/grpc-helpers'; +import { InvalidGrpcPackageDefinitionMutexException } from '../../errors/invalid-grpc-package-definition-mutex.exception'; +import { InvalidGrpcPackageDefinitionMissingPacakgeDefinitionException } from '../../errors/invalid-grpc-package-definition-missing-package-definition.exception'; + +const grpcProtoLoaderPackage = { loadSync: (a, b) => 'withLoader' }; + +describe('getGrpcPackageDefinition', () => { + it('missing both protoPath and packageDefinition', () => { + expect(() => + getGrpcPackageDefinition( + { + package: 'somePackage', + }, + grpcProtoLoaderPackage, + ), + ).to.throw(InvalidGrpcPackageDefinitionMissingPacakgeDefinitionException); + }); + + it('got both protoPath and packageDefinition', () => { + expect(() => + getGrpcPackageDefinition( + { + package: 'somePackage', + protoPath: 'some/path', + packageDefinition: {}, + }, + grpcProtoLoaderPackage, + ), + ).to.throw(InvalidGrpcPackageDefinitionMutexException); + }); + + it('success with protoPath', () => { + expect(() => + getGrpcPackageDefinition( + { + package: 'somePackage', + protoPath: 'some/path', + }, + grpcProtoLoaderPackage, + ), + ).to.not.throw(Error); + }); + + it('success with packageDef', () => { + expect(() => + getGrpcPackageDefinition( + { + package: 'somePackage', + packageDefinition: {}, + }, + grpcProtoLoaderPackage, + ), + ).to.not.throw(Error); + }); +}); diff --git a/packages/microservices/test/server/server-grpc.spec.ts b/packages/microservices/test/server/server-grpc.spec.ts index 8f9f8dd6fbd..073d7499161 100644 --- a/packages/microservices/test/server/server-grpc.spec.ts +++ b/packages/microservices/test/server/server-grpc.spec.ts @@ -6,7 +6,9 @@ import * as sinon from 'sinon'; import { CANCEL_EVENT } from '../../constants'; import { InvalidGrpcPackageException } from '../../errors/invalid-grpc-package.exception'; import { GrpcMethodStreamingType } from '../../index'; -import { ServerGrpc } from '../../server/server-grpc'; +import { ServerGrpc } from '../../server'; +import { InvalidProtoDefinitionException } from '../../errors/invalid-proto-definition.exception'; +import * as grpcHelpers from '../../helpers/grpc-helpers'; class NoopLogger extends Logger { log(message: any, context?: string): void {} @@ -532,6 +534,25 @@ describe('ServerGrpc', () => { }); }); + describe('loadProto', () => { + describe('when proto is invalid', () => { + it('should throw InvalidProtoDefinitionException', () => { + const getPackageDefinitionStub = sinon.stub( + grpcHelpers, + 'getGrpcPackageDefinition' as any, + ); + getPackageDefinitionStub.callsFake(() => { + throw new Error(); + }); + (server as any).logger = new NoopLogger(); + expect(() => server.loadProto()).to.throws( + InvalidProtoDefinitionException, + ); + getPackageDefinitionStub.restore(); + }); + }); + }); + describe('close', () => { it('should call "forceShutdown"', () => { const grpcClient = { forceShutdown: sinon.spy() };