@rsdk/grpc.server 4.0.0-next.3 → 4.0.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/controllers/index.d.ts +2 -0
- package/dist/controllers/index.js +2 -0
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/reflection/v1.base.controller.d.ts +32 -0
- package/dist/controllers/reflection/v1.base.controller.js +177 -0
- package/dist/controllers/reflection/v1.base.controller.js.map +1 -0
- package/dist/controllers/reflection/v1.controller.d.ts +10 -0
- package/dist/controllers/reflection/v1.controller.js +25 -0
- package/dist/controllers/reflection/v1.controller.js.map +1 -0
- package/dist/controllers/reflection/v1alpha.controller.d.ts +10 -0
- package/dist/controllers/reflection/v1alpha.controller.js +25 -0
- package/dist/controllers/reflection/v1alpha.controller.js.map +1 -0
- package/dist/grpc.config.d.ts +1 -0
- package/dist/grpc.config.js +8 -0
- package/dist/grpc.config.js.map +1 -1
- package/dist/grpc.transport.d.ts +13 -1
- package/dist/grpc.transport.js +40 -9
- package/dist/grpc.transport.js.map +1 -1
- package/dist/grpc.utils.d.ts +9 -0
- package/dist/grpc.utils.js +18 -0
- package/dist/grpc.utils.js.map +1 -0
- package/dist/reflection.module.d.ts +5 -0
- package/dist/reflection.module.js +33 -0
- package/dist/reflection.module.js.map +1 -0
- package/dist/reflection.service.d.ts +43 -0
- package/dist/reflection.service.js +151 -0
- package/dist/reflection.service.js.map +1 -0
- package/jest.config.js +11 -0
- package/package.json +7 -6
- package/src/controllers/index.ts +2 -0
- package/src/controllers/reflection/v1.base.controller.ts +173 -0
- package/src/controllers/reflection/v1.controller.ts +25 -0
- package/src/controllers/reflection/v1alpha.controller.ts +25 -0
- package/src/grpc.config.ts +7 -0
- package/src/grpc.transport.ts +51 -15
- package/src/grpc.utils.ts +20 -0
- package/src/reflection.module.ts +26 -0
- package/src/reflection.service.spec.ts +86 -0
- package/src/reflection.service.ts +210 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FileDescriptorProto } from '@bufbuild/protobuf';
|
|
2
|
+
import type { PlatformTestingModule } from '@rsdk/testing';
|
|
3
|
+
import { PlatformTest } from '@rsdk/testing';
|
|
4
|
+
|
|
5
|
+
import { ReflectionService } from './reflection.service';
|
|
6
|
+
|
|
7
|
+
describe('ReflectionService', () => {
|
|
8
|
+
let testingModule: PlatformTestingModule;
|
|
9
|
+
let reflectionService: ReflectionService;
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
const platformTesting = await PlatformTest.create({});
|
|
13
|
+
|
|
14
|
+
testingModule = await platformTesting
|
|
15
|
+
.createTestingModule({
|
|
16
|
+
providers: [
|
|
17
|
+
ReflectionService,
|
|
18
|
+
{
|
|
19
|
+
provide: 'GRPC_PACKAGES',
|
|
20
|
+
useValue: [],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
})
|
|
24
|
+
.compile();
|
|
25
|
+
|
|
26
|
+
reflectionService = testingModule.get(ReflectionService);
|
|
27
|
+
|
|
28
|
+
reflectionService.onModuleInit();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterAll(async () => {
|
|
32
|
+
await testingModule.close();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('listServices()', () => {
|
|
36
|
+
it('lists all services', () => {
|
|
37
|
+
const services = reflectionService.listServices();
|
|
38
|
+
|
|
39
|
+
expect(services).toHaveLength(4);
|
|
40
|
+
expect(services).toContain('grpc.health.v1.Health');
|
|
41
|
+
expect(services).toContain('grpc.metrics.v1.Metrics');
|
|
42
|
+
expect(services).toContain('grpc.reflection.v1.ServerReflection');
|
|
43
|
+
expect(services).toContain('grpc.reflection.v1alpha.ServerReflection');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('fileByFilename()', () => {
|
|
48
|
+
it('finds files with no transitive dependencies', () => {
|
|
49
|
+
const descriptor = FileDescriptorProto.fromBinary(
|
|
50
|
+
reflectionService.fileByName('grpc/health/v1/health.proto'),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(descriptor.name).toEqual('grpc/health/v1/health.proto');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('errors with no file found', () => {
|
|
57
|
+
expect(() => reflectionService.fileByName('nonexistent.proto')).toThrow(
|
|
58
|
+
'file: nonexistent.proto not found',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('fileContainingSymbol()', () => {
|
|
64
|
+
it('finds symbols and returns transitive file dependencies', () => {
|
|
65
|
+
const descriptor = FileDescriptorProto.fromBinary(
|
|
66
|
+
reflectionService.fileBySymbol('grpc.health.v1.HealthCheckRequest'),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(descriptor.name).toEqual('grpc/health/v1/health.proto');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('resolves references to method types', () => {
|
|
73
|
+
const descriptor = FileDescriptorProto.fromBinary(
|
|
74
|
+
reflectionService.fileBySymbol('grpc.health.v1.Health.Check'),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(descriptor.name).toEqual('grpc/health/v1/health.proto');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('errors with no symbol found', () => {
|
|
81
|
+
expect(() =>
|
|
82
|
+
reflectionService.fileBySymbol('non.existant.symbol'),
|
|
83
|
+
).toThrow('symbol: non.existant.symbol not found');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EnumDescriptorProto,
|
|
3
|
+
MethodDescriptorProto,
|
|
4
|
+
} from '@bufbuild/protobuf';
|
|
5
|
+
import {
|
|
6
|
+
DescriptorProto,
|
|
7
|
+
FileDescriptorProto,
|
|
8
|
+
FileDescriptorSet,
|
|
9
|
+
ServiceDescriptorProto,
|
|
10
|
+
} from '@bufbuild/protobuf';
|
|
11
|
+
import type { OnModuleInit } from '@nestjs/common';
|
|
12
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
13
|
+
import HealthGrpcPackage from '@rsdk/builtin-contract/dist/grpc.health.v1';
|
|
14
|
+
import MetricsGrpcPackage from '@rsdk/builtin-contract/dist/grpc.metrics.v1';
|
|
15
|
+
import ReflectionGrpcPackageV1 from '@rsdk/builtin-contract/dist/grpc.reflection.v1';
|
|
16
|
+
import ReflectionGrpcPackageV1alpha from '@rsdk/builtin-contract/dist/grpc.reflection.v1alpha';
|
|
17
|
+
import type { Package } from '@rsdk/grpc.loader';
|
|
18
|
+
import { LoggerFactory } from '@rsdk/logging';
|
|
19
|
+
|
|
20
|
+
@Injectable()
|
|
21
|
+
export class ReflectionService implements OnModuleInit {
|
|
22
|
+
private readonly services: Set<string> = new Set();
|
|
23
|
+
private readonly files: FileDescriptorProto[] = [];
|
|
24
|
+
private readonly logger = LoggerFactory.create(ReflectionService);
|
|
25
|
+
|
|
26
|
+
constructor(@Inject('GRPC_PACKAGES') private readonly packages: Package[]) {}
|
|
27
|
+
|
|
28
|
+
onModuleInit(): void {
|
|
29
|
+
const allPackages = [
|
|
30
|
+
...this.packages,
|
|
31
|
+
HealthGrpcPackage,
|
|
32
|
+
MetricsGrpcPackage,
|
|
33
|
+
ReflectionGrpcPackageV1,
|
|
34
|
+
ReflectionGrpcPackageV1alpha,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const allPackagesNames = new Set(allPackages.map((pkg) => pkg.name));
|
|
38
|
+
|
|
39
|
+
for (const pkg of allPackages) {
|
|
40
|
+
if (pkg.descriptor) {
|
|
41
|
+
for (const service of Object.keys(pkg.definitions['grpc-js'])) {
|
|
42
|
+
this.logger.info(
|
|
43
|
+
`GRPC descriptor loaded for service: ${pkg.name}.${service}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const file of FileDescriptorSet.fromBinary(pkg.descriptor).file) {
|
|
48
|
+
this.files.push(file);
|
|
49
|
+
|
|
50
|
+
if (allPackagesNames.has(`${file.package}`)) {
|
|
51
|
+
for (const service of file.service) {
|
|
52
|
+
this.services.add(`${file.package}.${service.name}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* List the full names of registered gRPC services
|
|
62
|
+
*
|
|
63
|
+
* note: the spec is unclear as to what the 'listServices' param can be; most
|
|
64
|
+
* clients seem to only pass '*' but unsure if this should behave like a
|
|
65
|
+
* filter. Until we know how this should behave with different inputs this
|
|
66
|
+
* just always returns *all* services.
|
|
67
|
+
*
|
|
68
|
+
* @returns full-qualified service names (eg. 'sample.SampleService')
|
|
69
|
+
*/
|
|
70
|
+
listServices(): string[] {
|
|
71
|
+
return [...this.services];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Find the proto file(s) that declares the given fully-qualified symbol name
|
|
76
|
+
*
|
|
77
|
+
* @param symbol fully-qualified name of the symbol to lookup
|
|
78
|
+
* (e.g. package.service[.method] or package.type)
|
|
79
|
+
*
|
|
80
|
+
* @returns descriptors of the file which contains this symbol and its imports
|
|
81
|
+
*/
|
|
82
|
+
fileBySymbol(symbol: string): Buffer {
|
|
83
|
+
const file = this.files.find((file) => this.findSymbol(symbol, file));
|
|
84
|
+
|
|
85
|
+
if (file) {
|
|
86
|
+
return Buffer.from(file.toBinary());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error(`symbol: ${symbol} not found`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find a proto file by the file name
|
|
94
|
+
* @returns descriptors of the file which contains this symbol and its imports
|
|
95
|
+
*/
|
|
96
|
+
fileByName(filename: string): Buffer {
|
|
97
|
+
const file = this.files.find((file) => file.name === filename);
|
|
98
|
+
|
|
99
|
+
if (file) {
|
|
100
|
+
return Buffer.from(file.toBinary());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new Error(`file: ${filename} not found`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find a proto file containing an extension to a message type
|
|
108
|
+
* @returns descriptors of the file which contains this symbol and its imports
|
|
109
|
+
*/
|
|
110
|
+
fileContainingExtension(
|
|
111
|
+
containingType: string,
|
|
112
|
+
extensionNumber: number,
|
|
113
|
+
): Buffer {
|
|
114
|
+
const extension = this.files.find((file) => {
|
|
115
|
+
const descriptor = this.findSymbol(containingType, file);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
descriptor instanceof DescriptorProto &&
|
|
119
|
+
descriptor.extension.some(
|
|
120
|
+
(extension) => extensionNumber === extension.number,
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (extension) {
|
|
126
|
+
return Buffer.from(extension.toBinary());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw new Error(
|
|
130
|
+
`containingType: ${containingType}, extensionNumber: ${extensionNumber} not found`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
allExtensionNumbersOfType(symbol: string): number[] {
|
|
135
|
+
return [
|
|
136
|
+
...new Set(
|
|
137
|
+
this.files.flatMap((file) =>
|
|
138
|
+
file.extension.flatMap((_fqn, _file, ext) =>
|
|
139
|
+
ext
|
|
140
|
+
.filter((e) => e.extendee === symbol)
|
|
141
|
+
.flatMap((e) => Number(e.number)),
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private findSymbol(
|
|
149
|
+
symbol: string,
|
|
150
|
+
descriptor:
|
|
151
|
+
| FileDescriptorProto
|
|
152
|
+
| DescriptorProto
|
|
153
|
+
| EnumDescriptorProto
|
|
154
|
+
| ServiceDescriptorProto
|
|
155
|
+
| MethodDescriptorProto,
|
|
156
|
+
prefix = '',
|
|
157
|
+
):
|
|
158
|
+
| DescriptorProto
|
|
159
|
+
| EnumDescriptorProto
|
|
160
|
+
| ServiceDescriptorProto
|
|
161
|
+
| MethodDescriptorProto
|
|
162
|
+
| undefined {
|
|
163
|
+
if (descriptor instanceof FileDescriptorProto) {
|
|
164
|
+
const packageName = descriptor.package;
|
|
165
|
+
|
|
166
|
+
const packagePrefix = packageName ? `${packageName}.` : '';
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
descriptor.messageType.find((type) =>
|
|
170
|
+
this.findSymbol(symbol, type, packagePrefix),
|
|
171
|
+
) ??
|
|
172
|
+
descriptor.enumType.find((type) =>
|
|
173
|
+
this.findSymbol(symbol, type, packagePrefix),
|
|
174
|
+
) ??
|
|
175
|
+
descriptor.service.find((type) =>
|
|
176
|
+
this.findSymbol(symbol, type, packagePrefix),
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const fullName = prefix + descriptor.name;
|
|
182
|
+
|
|
183
|
+
if (symbol === fullName) {
|
|
184
|
+
return descriptor;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (descriptor instanceof DescriptorProto) {
|
|
188
|
+
const messagePrefix = `${fullName}.`;
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
descriptor.nestedType.find((type) =>
|
|
192
|
+
this.findSymbol(symbol, type, messagePrefix),
|
|
193
|
+
) ??
|
|
194
|
+
descriptor.enumType.find((type) =>
|
|
195
|
+
this.findSymbol(symbol, type, messagePrefix),
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (descriptor instanceof ServiceDescriptorProto) {
|
|
201
|
+
const servicePrefix = `${fullName}.`;
|
|
202
|
+
|
|
203
|
+
return descriptor.method.find((method) =>
|
|
204
|
+
this.findSymbol(symbol, method, servicePrefix),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|