1+ import { isObject , isUndefined } from '@nestjs/common/utils/shared.utils' ;
12import { fromEvent } from 'rxjs' ;
23import { takeUntil } from 'rxjs/operators' ;
3- import { CANCEL_EVENT , GRPC_DEFAULT_PROTO_LOADER , GRPC_DEFAULT_URL } from '../constants' ;
4+ import {
5+ CANCEL_EVENT ,
6+ GRPC_DEFAULT_PROTO_LOADER ,
7+ GRPC_DEFAULT_URL ,
8+ } from '../constants' ;
49import { InvalidGrpcPackageException } from '../exceptions/errors/invalid-grpc-package.exception' ;
510import { InvalidProtoDefinitionException } from '../exceptions/errors/invalid-proto-definition.exception' ;
611import { CustomTransportStrategy } from '../interfaces' ;
@@ -23,13 +28,11 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
2328 this . getOptionsProp < GrpcOptions > ( options , 'url' ) || GRPC_DEFAULT_URL ;
2429
2530 const protoLoader =
26- this . getOptionsProp < GrpcOptions > ( options , 'protoLoader' ) || GRPC_DEFAULT_PROTO_LOADER ;
31+ this . getOptionsProp < GrpcOptions > ( options , 'protoLoader' ) ||
32+ GRPC_DEFAULT_PROTO_LOADER ;
2733
2834 grpcPackage = this . loadPackage ( 'grpc' , ServerGrpc . name ) ;
29- grpcProtoLoaderPackage = this . loadPackage (
30- protoLoader ,
31- ServerGrpc . name ,
32- ) ;
35+ grpcProtoLoaderPackage = this . loadPackage ( protoLoader , ServerGrpc . name ) ;
3336 }
3437
3538 public async listen ( callback : ( ) => void ) {
@@ -55,16 +58,30 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
5558 this . logger . error ( invalidPackageError . message , invalidPackageError . stack ) ;
5659 throw invalidPackageError ;
5760 }
58- for ( const name of this . getServiceNames ( grpcPkg ) ) {
61+
62+ // Take all of the services defined in grpcPkg and assign them to
63+ // method handlers defined in Controllers
64+ for ( const definition of this . getServiceNames ( grpcPkg ) ) {
5965 this . grpcClient . addService (
60- grpcPkg [ name ] . service ,
61- await this . createService ( grpcPkg [ name ] , name ) ,
66+ // First parameter requires exact service definition from proto
67+ definition . service . service ,
68+ // Here full proto definition required along with namespaced pattern name
69+ await this . createService ( definition . service , definition . name ) ,
6270 ) ;
6371 }
6472 }
6573
66- public getServiceNames ( grpcPkg : any ) {
67- return Object . keys ( grpcPkg ) . filter ( name => grpcPkg [ name ] . service ) ;
74+ /**
75+ * Will return all of the services along with their fully namespaced
76+ * names as an array of objects.
77+ * This method initiates recursive scan of grpcPkg object
78+ */
79+ public getServiceNames ( grpcPkg : any ) : { name : string ; service : any } [ ] {
80+ // Define accumulator to collect all of the services available to load
81+ const services : { name : string ; service : any } [ ] = [ ] ;
82+ // Initiate recursive services collector starting with empty name
83+ this . collectDeepServices ( '' , grpcPkg , services ) ;
84+ return services ;
6885 }
6986
7087 public async createService ( grpcService : any , name : string ) {
@@ -177,4 +194,58 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
177194 throw invalidProtoError ;
178195 }
179196 }
197+
198+ /**
199+ * Recursively fetch all of the service methods available on loaded
200+ * protobuf descriptor object, and collect those as an objects with
201+ * dot-syntax full-path names.
202+ *
203+ * Example:
204+ * for proto package Bundle.FirstService with service Events { rpc...
205+ * will be resolved to object of (while loaded for Bundle package):
206+ * {
207+ * name: "FirstService.Events",
208+ * service: {Object}
209+ * }
210+ */
211+ private collectDeepServices (
212+ name : string ,
213+ grpcDefinition : any ,
214+ accumulator : { name : string ; service : any } [ ] ,
215+ ) {
216+ if ( ! isObject ( grpcDefinition ) ) {
217+ return ;
218+ }
219+ const keysToTraverse = Object . keys ( grpcDefinition ) ;
220+ // Traverse definitions or namespace extensions
221+ for ( const key of keysToTraverse ) {
222+ const nameExtended = this . parseDeepServiceName ( name , key ) ;
223+ const deepDefinition = grpcDefinition [ key ] ;
224+
225+ const isServiceDefined = ! isUndefined ( deepDefinition . service ) ;
226+ const isServiceBoolean = isServiceDefined
227+ ? deepDefinition . service !== false
228+ : false ;
229+
230+ if ( isServiceDefined && isServiceBoolean ) {
231+ accumulator . push ( {
232+ name : nameExtended ,
233+ service : deepDefinition ,
234+ } ) ;
235+ }
236+ // Continue recursion until objects end or service definition found
237+ else {
238+ this . collectDeepServices ( nameExtended , deepDefinition , accumulator ) ;
239+ }
240+ }
241+ }
242+
243+ private parseDeepServiceName ( name : string , key : string ) : string {
244+ // If depth is zero then just return key
245+ if ( name . length === 0 ) {
246+ return key ;
247+ }
248+ // Otherwise add next through dot syntax
249+ return name + '.' + key ;
250+ }
180251}
0 commit comments