Skip to content

Commit 891de8e

Browse files
Merge branch 'feature/grpc-method-with-namespaces' of https://github.com/anton-alation/nest into anton-alation-feature/grpc-method-with-namespaces
2 parents 2a5e22e + 595bc98 commit 891de8e

2 files changed

Lines changed: 147 additions & 9 deletions

File tree

packages/microservices/server/server-grpc.ts

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,32 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
5555
this.logger.error(invalidPackageError.message, invalidPackageError.stack);
5656
throw invalidPackageError;
5757
}
58-
for (const name of this.getServiceNames(grpcPkg)) {
58+
// Take all of the services defined in grpcPkg and assign them to
59+
// method handlers defined in Controllers
60+
for (const definition of this.getServiceNames(grpcPkg)) {
5961
this.grpcClient.addService(
60-
grpcPkg[name].service,
61-
await this.createService(grpcPkg[name], name),
62+
// First parameter requires exact service definition from proto
63+
definition.service.service,
64+
// Here full proto definition required along with namespaced pattern name
65+
await this.createService(definition.service, definition.name)
6266
);
6367
}
6468
}
6569

66-
public getServiceNames(grpcPkg: any) {
67-
return Object.keys(grpcPkg).filter(name => grpcPkg[name].service);
70+
/**
71+
* Will return all of the services along with their fully namespaced
72+
* names as an array of objects.
73+
*
74+
* This method initiates recursive scan of grpcPkg object
75+
*
76+
* @param grpcPkg
77+
*/
78+
public getServiceNames(grpcPkg: any): {name: string, service: any}[] {
79+
// Define accumulator to collect all of the services available to load
80+
const services: {name: string, service: any}[] = [];
81+
// Initiate recursive services collector starting with empty name
82+
this._getServiceNamesCollector('', grpcPkg, services);
83+
return services;
6884
}
6985

7086
public async createService(grpcService: any, name: string) {
@@ -177,4 +193,58 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
177193
throw invalidProtoError;
178194
}
179195
}
196+
197+
/**
198+
* Recursively fetch all of the service methods available on loaded
199+
* protobuf descriptor object, and collect those as an objects with
200+
* dot-syntax full-path names.
201+
*
202+
* Example:
203+
* for proto package Bundle.FirstService with service Events { rpc...
204+
* will be resolved to object of (while loaded for Bundle package):
205+
* {
206+
* name: "FirstService.Events",
207+
* service: {Object}
208+
* }
209+
*
210+
* @param name
211+
* @param grpcDefinition
212+
* @param accumulator
213+
* @private
214+
*/
215+
private _getServiceNamesCollector(name, grpcDefinition, accumulator) {
216+
// Confirm that next definition to unwrap is an object
217+
if (typeof grpcDefinition !== 'object')
218+
// Exit for non objects
219+
return;
220+
// Collect keys for traverse
221+
const keys = Object.keys(grpcDefinition);
222+
// Traverse definitions or namespace extensions
223+
for (const key of keys) {
224+
// Define name extension variable
225+
let nameExtended = '';
226+
// If depth is zero then just add key
227+
if (name.length === 0)
228+
nameExtended = key;
229+
// If depth non-zero then add next through dot syntax
230+
else
231+
nameExtended = name + '.' + key;
232+
// Take nested object
233+
const nested = grpcDefinition[key];
234+
// Check if it's in depth with service definition available
235+
const requirement1 = typeof nested.service !== 'undefined';
236+
// Check if first requirement passable and service isn't a boolean value
237+
const requirement2 = requirement1 ? nested.service !== false : false;
238+
// Check if both requirements satisfied
239+
if (requirement1 && requirement2) {
240+
// Add new service object to accumulator
241+
accumulator.push({
242+
name: nameExtended,
243+
service: nested
244+
});
245+
} else
246+
// Continue recursion until objects end or service definition found
247+
this._getServiceNamesCollector(nameExtended, nested, accumulator);
248+
}
249+
}
180250
}

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)