Skip to content

Commit 6204fe1

Browse files
Merge branch 'anton-alation-feature/grpc-method-with-namespaces' into 5.6.0
2 parents 1b872d6 + 3cb7311 commit 6204fe1

2 files changed

Lines changed: 154 additions & 15 deletions

File tree

packages/microservices/server/server-grpc.ts

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils';
12
import { fromEvent } from 'rxjs';
23
import { 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';
49
import { InvalidGrpcPackageException } from '../exceptions/errors/invalid-grpc-package.exception';
510
import { InvalidProtoDefinitionException } from '../exceptions/errors/invalid-proto-definition.exception';
611
import { 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
}

packages/microservices/test/server/server-grpc.spec.ts

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,16 @@ describe('ServerGrpc', () => {
5151
});
5252
describe('when package exist', () => {
5353
it('should call "addService"', async () => {
54-
const serviceNames = ['test', 'test2'];
54+
const serviceNames = [{
55+
name: 'test',
56+
service: true
57+
}, {
58+
name: 'test2',
59+
service: true
60+
}];
5561
sinon.stub(server, 'lookupPackage').callsFake(() => ({
56-
test: true,
57-
test2: true,
62+
test: {service: true},
63+
test2: {service: true}
5864
}));
5965
sinon.stub(server, 'getServiceNames').callsFake(() => serviceNames);
6066

@@ -73,7 +79,16 @@ describe('ServerGrpc', () => {
7379
key2: { service: true },
7480
key3: { service: false },
7581
};
76-
const expected = ['key', 'key2'];
82+
const expected = [
83+
{
84+
name: 'key',
85+
service: {service: true}
86+
},
87+
{
88+
name: 'key2',
89+
service: {service: true}
90+
}
91+
];
7792
expect(server.getServiceNames(obj)).to.be.eql(expected);
7893
});
7994
});
@@ -225,4 +240,57 @@ describe('ServerGrpc', () => {
225240
expect(server.deserialize(content)).to.equal(content);
226241
});
227242
});
243+
244+
describe('proto interfaces parser should account for package namespaces', () => {
245+
it('should parse multi-level proto package tree"', () => {
246+
const grpcPkg = {
247+
A: {
248+
C: {
249+
E: {
250+
service: {
251+
serviceName: {}
252+
}
253+
}
254+
}
255+
},
256+
B: {
257+
D: {
258+
service: {
259+
serviceName: {}
260+
}
261+
}
262+
}
263+
};
264+
const svcs = server.getServiceNames(grpcPkg);
265+
expect(svcs.length).to
266+
.be.equal(
267+
2,
268+
'Amount of services collected from namespace should be equal 2'
269+
);
270+
expect(svcs[0].name).to.be.equal('A.C.E');
271+
expect(svcs[1].name).to.be.equal('B.D');
272+
});
273+
it('should parse single level proto package tree"', () => {
274+
const grpcPkg = {
275+
A: {
276+
service: {
277+
serviceName: {}
278+
}
279+
},
280+
B: {
281+
service: {
282+
serviceName: {}
283+
}
284+
}
285+
};
286+
const services = server.getServiceNames(grpcPkg);
287+
expect(services.length).to
288+
.be.equal(
289+
2,
290+
'Amount of services collected from namespace should be equal 2'
291+
);
292+
expect(services[0].name).to.be.equal('A');
293+
expect(services[1].name).to.be.equal('B');
294+
});
295+
});
228296
});

0 commit comments

Comments
 (0)