Skip to content

Commit 3b4f92e

Browse files
committed
refactor(grpc-reflection): file cleanup and enabled ts strict mode
1 parent 215078f commit 3b4f92e

File tree

12 files changed

+77
-66
lines changed

12 files changed

+77
-66
lines changed

packages/grpc-reflection/example/server.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,9 @@ const INCLUDE_PATH = path.join(__dirname, '../proto/sample/vendor');
99

1010
const server = new grpc.Server();
1111
const packageDefinition = protoLoader.loadSync(PROTO_PATH, { includeDirs: [INCLUDE_PATH] });
12-
const reflection = new ReflectionService(packageDefinition);
12+
const reflection = new ReflectionService(packageDefinition, { services: ['sample.SampleService'] });
1313
reflection.addToServer(server);
1414

1515
server.bindAsync('localhost:5000', grpc.ServerCredentials.createInsecure(), () => {
1616
server.start();
1717
});
18-
19-
// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
20-
21-
22-

packages/grpc-reflection/proto/sample/sample.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ service SampleService {
99
rpc Hello2 (HelloRequest) returns (CommonMessage) {}
1010
}
1111

12+
service IgnoreService {
13+
rpc Hello (HelloRequest) returns (HelloResponse) {}
14+
}
15+
1216
message HelloRequest {
1317
string hello = 1;
1418
HelloNested nested = 2;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** Options to create a reflection server */
2+
export interface ReflectionServerOptions {
3+
/** whitelist of fully-qualified service names to expose. (Default: expose all) */
4+
services?: string[];
5+
}

packages/grpc-reflection/src/protobuf-visitor.ts renamed to packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const visit = (file: FileDescriptorProto, visitor: Visitor): void => {
101101
});
102102
};
103103

104-
const packageName = file.getPackage();
104+
const packageName = file.getPackage() || '';
105105
file.getEnumTypeList().forEach((type) => processEnum(packageName, file, type));
106106
file.getMessageTypeList().forEach((type) => processMessage(packageName, file, type));
107107
file.getServiceList().forEach((service) => processService(packageName, file, service));

packages/grpc-reflection/src/reflection-v1-implementation.ts renamed to packages/grpc-reflection/src/implementations/reflection-v1.ts

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import {
77
import * as grpc from '@grpc/grpc-js';
88
import * as protoLoader from '@grpc/proto-loader';
99

10-
import { ExtensionNumberResponse__Output } from './generated/grpc/reflection/v1/ExtensionNumberResponse';
11-
import { FileDescriptorResponse__Output } from './generated/grpc/reflection/v1/FileDescriptorResponse';
12-
import { ListServiceResponse__Output } from './generated/grpc/reflection/v1/ListServiceResponse';
13-
import { ServerReflectionRequest } from './generated/grpc/reflection/v1/ServerReflectionRequest';
14-
import { ServerReflectionResponse } from './generated/grpc/reflection/v1/ServerReflectionResponse';
15-
import { visit } from './protobuf-visitor';
16-
import { scope } from './utils';
17-
import { PROTO_LOADER_OPTS } from './constants';
10+
import { ExtensionNumberResponse__Output } from '../generated/grpc/reflection/v1/ExtensionNumberResponse';
11+
import { FileDescriptorResponse__Output } from '../generated/grpc/reflection/v1/FileDescriptorResponse';
12+
import { ListServiceResponse__Output } from '../generated/grpc/reflection/v1/ListServiceResponse';
13+
import { ServerReflectionRequest } from '../generated/grpc/reflection/v1/ServerReflectionRequest';
14+
import { ServerReflectionResponse } from '../generated/grpc/reflection/v1/ServerReflectionResponse';
15+
import { visit } from './common/protobuf-visitor';
16+
import { scope } from './common/utils';
17+
import { PROTO_LOADER_OPTS } from './common/constants';
18+
import { ReflectionServerOptions } from './common/interfaces';
1819

1920
export class ReflectionError extends Error {
2021
constructor(
@@ -37,22 +38,27 @@ export class ReflectionError extends Error {
3738
export class ReflectionV1Implementation {
3839

3940
/** The full list of proto files (including imported deps) that the gRPC server includes */
40-
private fileDescriptorSet = new FileDescriptorSet();
41+
private readonly fileDescriptorSet = new FileDescriptorSet();
4142

4243
/** An index of proto files by file name (eg. 'sample.proto') */
43-
private fileNameIndex: Record<string, FileDescriptorProto> = {};
44+
private readonly fileNameIndex: Record<string, FileDescriptorProto> = {};
4445

4546
/** An index of proto files by type extension relationship
4647
*
4748
* extensionIndex[<pkg>.<msg>][<field#>] contains a reference to the file containing an
4849
* extension for the type "<pkg>.<msg>" and field number "<field#>"
4950
*/
50-
private extensionIndex: Record<string, Record<number, FileDescriptorProto>> = {};
51+
private readonly extensionIndex: Record<string, Record<number, FileDescriptorProto>> = {};
5152

5253
/** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */
53-
private symbolMap: Record<string, FileDescriptorProto> = {};
54+
private readonly symbolMap: Record<string, FileDescriptorProto> = {};
55+
56+
/** Options that the user provided for this service */
57+
private readonly options?: ReflectionServerOptions;
58+
59+
constructor(root: protoLoader.PackageDefinition, options?: ReflectionServerOptions) {
60+
this.options = options;
5461

55-
constructor(root: protoLoader.PackageDefinition) {
5662
Object.values(root).forEach(({ fileDescriptorProtos }) => {
5763
// Add file descriptors to the FileDescriptorSet.
5864
// We use the Array check here because a ServiceDefinition could have a method named the same thing
@@ -88,10 +94,10 @@ export class ReflectionV1Implementation {
8894
extension: (fqn, file, ext) => {
8995
index(fqn, file);
9096

91-
const extendeeName = ext.getExtendee();
97+
const extendeeName = ext.getExtendee() || '';
9298
this.extensionIndex[extendeeName] = {
9399
...(this.extensionIndex[extendeeName] || {}),
94-
[ext.getNumber()]: file,
100+
[ext.getNumber() || -1]: file,
95101
};
96102
},
97103
}),
@@ -126,25 +132,26 @@ export class ReflectionV1Implementation {
126132
return;
127133
}
128134

129-
if (referencedFile !== sourceFile) {
130-
sourceFile.addDependency(referencedFile.getName());
135+
const fname = referencedFile.getName();
136+
if (referencedFile !== sourceFile && fname) {
137+
sourceFile.addDependency(fname);
131138
}
132139
};
133140

134141
this.fileDescriptorSet.getFileList().forEach((file) =>
135142
visit(file, {
136-
field: (fqn, file, field) => addReference(field.getTypeName(), file, scope(fqn)),
137-
extension: (fqn, file, ext) => addReference(ext.getTypeName(), file, scope(fqn)),
143+
field: (fqn, file, field) => addReference(field.getTypeName() || '', file, scope(fqn)),
144+
extension: (fqn, file, ext) => addReference(ext.getTypeName() || '', file, scope(fqn)),
138145
method: (fqn, file, method) => {
139-
addReference(method.getInputType(), file, scope(fqn));
140-
addReference(method.getOutputType(), file, scope(fqn));
146+
addReference(method.getInputType() || '', file, scope(fqn));
147+
addReference(method.getOutputType() || '', file, scope(fqn));
141148
},
142149
}),
143150
);
144151
}
145152

146153
addToServer(server: Pick<grpc.Server, 'addService'>) {
147-
const protoPath = path.join(__dirname, '../proto/grpc/reflection/v1/reflection.proto');
154+
const protoPath = path.join(__dirname, '../../proto/grpc/reflection/v1/reflection.proto');
148155
const pkgDefinition = protoLoader.loadSync(protoPath, PROTO_LOADER_OPTS);
149156
const pkg = grpc.loadPackageDefinition(pkgDefinition) as any;
150157

@@ -180,8 +187,10 @@ export class ReflectionV1Implementation {
180187
} else if (message.fileByFilename !== undefined) {
181188
response.fileDescriptorResponse = this.fileByFilename(message.fileByFilename);
182189
} else if (message.fileContainingExtension !== undefined) {
183-
const { containingType, extensionNumber } = message.fileContainingExtension;
184-
response.fileDescriptorResponse = this.fileContainingExtension(containingType, extensionNumber);
190+
response.fileDescriptorResponse = this.fileContainingExtension(
191+
message.fileContainingExtension?.containingType || '',
192+
message.fileContainingExtension?.extensionNumber || -1
193+
);
185194
} else if (message.allExtensionNumbersOfType) {
186195
response.allExtensionNumbersResponse = this.allExtensionNumbersOfType(message.allExtensionNumbersOfType);
187196
} else {
@@ -224,7 +233,12 @@ export class ReflectionV1Implementation {
224233
)
225234
.flat();
226235

227-
return { service: services.map((service) => ({ name: service })) };
236+
const whitelist = new Set(this.options?.services ?? undefined);
237+
const exposedServices = this.options?.services ?
238+
services.filter(service => whitelist.has(service))
239+
: services;
240+
241+
return { service: exposedServices.map((service) => ({ name: service })) };
228242
}
229243

230244
/** Find the proto file(s) that declares the given fully-qualified symbol name

packages/grpc-reflection/src/reflection-v1alpha.ts renamed to packages/grpc-reflection/src/implementations/reflection-v1alpha.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import * as path from 'path';
33
import * as grpc from '@grpc/grpc-js';
44
import * as protoLoader from '@grpc/proto-loader';
55

6-
import { ServerReflectionRequest } from './generated/grpc/reflection/v1/ServerReflectionRequest';
7-
import { ServerReflectionResponse } from './generated/grpc/reflection/v1/ServerReflectionResponse';
8-
import { PROTO_LOADER_OPTS } from './constants';
9-
import { ReflectionV1Implementation } from './reflection-v1-implementation';
6+
import { ServerReflectionRequest } from '../generated/grpc/reflection/v1/ServerReflectionRequest';
7+
import { ServerReflectionResponse } from '../generated/grpc/reflection/v1/ServerReflectionResponse';
8+
import { PROTO_LOADER_OPTS } from './common/constants';
9+
import { ReflectionV1Implementation } from './reflection-v1';
1010

1111

1212
/** Analyzes a gRPC server and exposes methods to reflect on it
@@ -18,12 +18,12 @@ import { ReflectionV1Implementation } from './reflection-v1-implementation';
1818
* For example: if files 'a.proto' and 'b.proto' are both for the same package 'c' then
1919
* we will always return a reference to a combined 'c.proto' instead of the 2 files.
2020
*
21-
* @remarks as the v1 and v1alpha specs are identical, this implementation extends v1
22-
* and just exposes it at the v1alpha package instead
21+
* @privateRemarks as the v1 and v1alpha specs are identical, this implementation extends
22+
* reflection-v1 and exposes it at the v1alpha package instead
2323
*/
2424
export class ReflectionV1AlphaImplementation extends ReflectionV1Implementation {
2525
addToServer(server: Pick<grpc.Server, 'addService'>) {
26-
const protoPath = path.join(__dirname, '../proto/grpc/reflection/v1alpha/reflection.proto');
26+
const protoPath = path.join(__dirname, '../../proto/grpc/reflection/v1alpha/reflection.proto');
2727
const pkgDefinition = protoLoader.loadSync(protoPath, PROTO_LOADER_OPTS);
2828
const pkg = grpc.loadPackageDefinition(pkgDefinition) as any;
2929

packages/grpc-reflection/src/service.ts

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import * as grpc from '@grpc/grpc-js';
22
import * as protoLoader from '@grpc/proto-loader';
33

4-
import { ReflectionV1Implementation } from './reflection-v1-implementation';
5-
import { ReflectionV1AlphaImplementation } from './reflection-v1alpha';
6-
7-
interface ReflectionServerOptions {
8-
/** whitelist of fully-qualified service names to expose. (Default: expose all) */
9-
services?: string[];
10-
}
4+
import { ReflectionV1Implementation } from './implementations/reflection-v1';
5+
import { ReflectionV1AlphaImplementation } from './implementations/reflection-v1alpha';
6+
import { ReflectionServerOptions } from './implementations/common/interfaces';
117

128
/** Analyzes a gRPC package and exposes endpoints providing information about
139
* it according to the gRPC Server Reflection API Specification
@@ -31,21 +27,8 @@ export class ReflectionService {
3127
private readonly v1Alpha: ReflectionV1AlphaImplementation;
3228

3329
constructor(pkg: protoLoader.PackageDefinition, options?: ReflectionServerOptions) {
34-
35-
if (options.services) {
36-
const whitelist = new Set(options.services);
37-
38-
for (const key in Object.keys(pkg)) {
39-
const value = pkg[key];
40-
const isService = value.format !== 'Protocol Buffer 3 DescriptorProto' && value.format !== 'Protocol Buffer 3 EnumDescriptorProto';
41-
if (isService && !whitelist.has(key)) {
42-
delete pkg[key];
43-
}
44-
}
45-
}
46-
47-
this.v1 = new ReflectionV1Implementation(pkg);
48-
this.v1Alpha = new ReflectionV1AlphaImplementation(pkg);
30+
this.v1 = new ReflectionV1Implementation(pkg, options);
31+
this.v1Alpha = new ReflectionV1AlphaImplementation(pkg, options);
4932
}
5033

5134
addToServer(server: Pick<grpc.Server, 'addService'>) {

packages/grpc-reflection/test/test-reflection-v1-implementation.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ import * as path from 'path';
33
import { FileDescriptorProto } from 'google-protobuf/google/protobuf/descriptor_pb';
44
import * as protoLoader from '@grpc/proto-loader';
55

6-
import { ReflectionV1Implementation } from '../src/reflection-v1-implementation';
6+
import { ReflectionV1Implementation } from '../src/implementations/reflection-v1';
77

88
describe('GrpcReflectionService', () => {
99
let reflectionService: ReflectionV1Implementation;
1010

1111
beforeEach(async () => {
12-
console.log(path.join(__dirname, '../proto/sample/sample.proto'));
13-
console.log([path.join(__dirname, '../proto/sample/vendor')]);
1412
const root = protoLoader.loadSync(path.join(__dirname, '../proto/sample/sample.proto'), {
1513
includeDirs: [path.join(__dirname, '../proto/sample/vendor')]
1614
});
@@ -20,6 +18,18 @@ describe('GrpcReflectionService', () => {
2018

2119
describe('listServices()', () => {
2220
it('lists all services', () => {
21+
const { service: services } = reflectionService.listServices('*');
22+
assert.equal(services.length, 2);
23+
assert(services.find((s) => s.name === 'sample.SampleService'));
24+
});
25+
26+
it('whitelists services properly', () => {
27+
const root = protoLoader.loadSync(path.join(__dirname, '../proto/sample/sample.proto'), {
28+
includeDirs: [path.join(__dirname, '../proto/sample/vendor')]
29+
});
30+
31+
reflectionService = new ReflectionV1Implementation(root, { services: ['sample.SampleService'] });
32+
2333
const { service: services } = reflectionService.listServices('*');
2434
assert.equal(services.length, 1);
2535
assert(services.find((s) => s.name === 'sample.SampleService'));

0 commit comments

Comments
 (0)