@rsdk/grpc.server 4.0.0-next.0 → 4.0.0-next.10

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/controllers/health.controller.js +4 -4
  3. package/dist/controllers/health.controller.js.map +1 -1
  4. package/dist/controllers/index.d.ts +2 -0
  5. package/dist/controllers/index.js +2 -0
  6. package/dist/controllers/index.js.map +1 -1
  7. package/dist/controllers/metrics.controller.js +2 -1
  8. package/dist/controllers/metrics.controller.js.map +1 -1
  9. package/dist/controllers/reflection/v1.base.controller.d.ts +32 -0
  10. package/dist/controllers/reflection/v1.base.controller.js +177 -0
  11. package/dist/controllers/reflection/v1.base.controller.js.map +1 -0
  12. package/dist/controllers/reflection/v1.controller.d.ts +10 -0
  13. package/dist/controllers/reflection/v1.controller.js +25 -0
  14. package/dist/controllers/reflection/v1.controller.js.map +1 -0
  15. package/dist/controllers/reflection/v1alpha.controller.d.ts +10 -0
  16. package/dist/controllers/reflection/v1alpha.controller.js +25 -0
  17. package/dist/controllers/reflection/v1alpha.controller.js.map +1 -0
  18. package/dist/grpc-logger.interceptor.d.ts +10 -0
  19. package/dist/grpc-logger.interceptor.js +73 -0
  20. package/dist/grpc-logger.interceptor.js.map +1 -0
  21. package/dist/grpc.config.d.ts +2 -0
  22. package/dist/grpc.config.js +18 -1
  23. package/dist/grpc.config.js.map +1 -1
  24. package/dist/grpc.transport.d.ts +13 -1
  25. package/dist/grpc.transport.js +55 -11
  26. package/dist/grpc.transport.js.map +1 -1
  27. package/dist/grpc.utils.d.ts +9 -0
  28. package/dist/grpc.utils.js +18 -0
  29. package/dist/grpc.utils.js.map +1 -0
  30. package/dist/reflection.module.d.ts +5 -0
  31. package/dist/reflection.module.js +33 -0
  32. package/dist/reflection.module.js.map +1 -0
  33. package/dist/reflection.service.d.ts +43 -0
  34. package/dist/reflection.service.js +151 -0
  35. package/dist/reflection.service.js.map +1 -0
  36. package/jest.config.js +11 -0
  37. package/package.json +17 -12
  38. package/src/controllers/index.ts +2 -0
  39. package/src/controllers/reflection/v1.base.controller.ts +173 -0
  40. package/src/controllers/reflection/v1.controller.ts +25 -0
  41. package/src/controllers/reflection/v1alpha.controller.ts +25 -0
  42. package/src/grpc-logger.interceptor.ts +72 -0
  43. package/src/grpc.config.ts +13 -0
  44. package/src/grpc.transport.ts +64 -15
  45. package/src/grpc.utils.ts +20 -0
  46. package/src/reflection.module.ts +26 -0
  47. package/src/reflection.service.spec.ts +86 -0
  48. package/src/reflection.service.ts +210 -0
@@ -0,0 +1,25 @@
1
+ import type {
2
+ ServerReflectionGrpcController,
3
+ ServerReflectionRequest,
4
+ ServerReflectionResponse,
5
+ } from '@rsdk/builtin-contract/dist/grpc.reflection.v1';
6
+ import { ServerReflectionGrpcMethods } from '@rsdk/builtin-contract/dist/grpc.reflection.v1';
7
+ import type { Observable } from 'rxjs';
8
+
9
+ import { BaseV1GrpcReflectionController } from './v1.base.controller';
10
+
11
+ /** Implements the 'v1' (final) version of the gRPC Reflection API spec
12
+ *
13
+ * @see {@link https://github.com/grpc/grpc/blob/master/doc/server-reflection.md}
14
+ */
15
+ @ServerReflectionGrpcMethods()
16
+ export class ReflectionV1Controller
17
+ extends BaseV1GrpcReflectionController
18
+ implements ServerReflectionGrpcController
19
+ {
20
+ override serverReflectionInfo(
21
+ request: Observable<ServerReflectionRequest>,
22
+ ): Observable<ServerReflectionResponse> {
23
+ return super.serverReflectionInfo(request);
24
+ }
25
+ }
@@ -0,0 +1,25 @@
1
+ import { ServerReflectionGrpcMethods } from '@rsdk/builtin-contract/dist/grpc.reflection.v1';
2
+ import type {
3
+ ServerReflectionGrpcController,
4
+ ServerReflectionRequest,
5
+ ServerReflectionResponse,
6
+ } from '@rsdk/builtin-contract/dist/grpc.reflection.v1alpha';
7
+ import type { Observable } from 'rxjs';
8
+
9
+ import { BaseV1GrpcReflectionController } from './v1.base.controller';
10
+
11
+ /** Implements the 'v1alpha' version of the gRPC Reflection API spec
12
+ *
13
+ * @see {@link https://github.com/grpc/grpc/blob/master/doc/server-reflection.md}
14
+ */
15
+ @ServerReflectionGrpcMethods()
16
+ export class ReflectionV1alphaController
17
+ extends BaseV1GrpcReflectionController
18
+ implements ServerReflectionGrpcController
19
+ {
20
+ override serverReflectionInfo(
21
+ request: Observable<ServerReflectionRequest>,
22
+ ): Observable<ServerReflectionResponse> {
23
+ return super.serverReflectionInfo(request);
24
+ }
25
+ }
@@ -0,0 +1,72 @@
1
+ import type {
2
+ CallHandler,
3
+ ExecutionContext,
4
+ NestInterceptor,
5
+ } from '@nestjs/common/interfaces';
6
+ import { InjectLogger } from '@rsdk/core';
7
+ import { ILogger } from '@rsdk/logging';
8
+ import type { Observable } from 'rxjs';
9
+ import { catchError, tap, throwError } from 'rxjs';
10
+
11
+ import { GRPCConfig } from './grpc.config';
12
+
13
+ export class GrpcLoggerInterceptor implements NestInterceptor {
14
+ constructor(
15
+ @InjectLogger(GrpcLoggerInterceptor) private logger: ILogger,
16
+ private grpcConfig: GRPCConfig,
17
+ ) {}
18
+
19
+ intercept(
20
+ context: ExecutionContext,
21
+ next: CallHandler<any>,
22
+ ): Observable<any> | Promise<Observable<any>> {
23
+ if (!this.grpcConfig.requestLogging) {
24
+ return next.handle();
25
+ }
26
+ const isRpc = context.getType() === 'rpc';
27
+ if (!isRpc) {
28
+ return next.handle();
29
+ }
30
+
31
+ const rpc = context.switchToRpc();
32
+
33
+ const startDate = Date.now();
34
+ const rpcContext = rpc.getContext();
35
+ const serializedCtx = rpcContext?.toJSON?.() ?? rpcContext;
36
+ const requestForLog = {
37
+ data: rpc.getData(),
38
+ ctx: serializedCtx,
39
+ };
40
+
41
+ this.logger.trace('received rpc request', {
42
+ request: requestForLog,
43
+ timestamp: startDate,
44
+ });
45
+
46
+ return next.handle().pipe(
47
+ catchError((error) => {
48
+ const endDate = Date.now();
49
+
50
+ this.logger.trace('handled rpc request with error', {
51
+ request: requestForLog,
52
+ startDate,
53
+ endDate,
54
+ duration: endDate - startDate,
55
+ error,
56
+ });
57
+ return throwError(() => error);
58
+ }),
59
+ tap((response) => {
60
+ const endDate = Date.now();
61
+
62
+ this.logger.trace('handled rpc request', {
63
+ request: requestForLog,
64
+ startDate,
65
+ endDate,
66
+ duration: endDate - startDate,
67
+ response,
68
+ });
69
+ }),
70
+ );
71
+ }
72
+ }
@@ -4,6 +4,7 @@ import {
4
4
  } from '@grpc/grpc-js/build/src/constants';
5
5
  import { text } from '@rsdk/common';
6
6
  import {
7
+ BoolParser,
7
8
  Config,
8
9
  ConfigSection,
9
10
  ConfigTag,
@@ -36,4 +37,16 @@ export class GRPCConfig extends Config {
36
37
  description: 'Max gRPC message size (send)',
37
38
  })
38
39
  readonly maxSend!: number;
40
+
41
+ @Property('GRPC_REFLECTION', new BoolParser(), {
42
+ defaultValue: false,
43
+ description: 'Enable reflection',
44
+ })
45
+ readonly reflection!: boolean;
46
+
47
+ @Property('GRPC_REQUEST_LOGGING', new BoolParser(), {
48
+ defaultValue: false,
49
+ description: 'Enable trace logging all requests',
50
+ })
51
+ readonly requestLogging!: boolean;
39
52
  }
@@ -1,7 +1,10 @@
1
1
  import type { Controller } from '@nestjs/common/interfaces';
2
+ import { APP_INTERCEPTOR } from '@nestjs/core';
2
3
  import type { MicroserviceOptions } from '@nestjs/microservices';
3
4
  import HealthGrpcPackage from '@rsdk/builtin-contract/dist/grpc.health.v1';
4
5
  import MetricsGrpcPackage from '@rsdk/builtin-contract/dist/grpc.metrics.v1';
6
+ import ReflectionGrpcPackageV1 from '@rsdk/builtin-contract/dist/grpc.reflection.v1';
7
+ import ReflectionGrpcPackageV1alpha from '@rsdk/builtin-contract/dist/grpc.reflection.v1alpha';
5
8
  import type { Constructor } from '@rsdk/common';
6
9
  import type {
7
10
  ConfigContext,
@@ -9,6 +12,7 @@ import type {
9
12
  IErrorsSender,
10
13
  IErrorsTransformer,
11
14
  IMicroserviceTransport,
15
+ NestModuleDefinitions,
12
16
  } from '@rsdk/core';
13
17
  import { SequenceException } from '@rsdk/core';
14
18
  import type { Package } from '@rsdk/grpc.loader';
@@ -17,10 +21,20 @@ import { LoggerFactory } from '@rsdk/logging';
17
21
 
18
22
  import { HealthController, MetricsController } from './controllers';
19
23
  import { GRPCConfig } from './grpc.config';
24
+ import { isPackageNotIncluded } from './grpc.utils';
20
25
  import { GrpcErrorsFormatter } from './grpc-errors.formatter';
21
26
  import { GrpcErrorsSender } from './grpc-errors.sender';
27
+ import { GrpcLoggerInterceptor } from './grpc-logger.interceptor';
28
+ import { ReflectionModule } from './reflection.module';
22
29
 
23
30
  export class GrpcTransport implements IMicroserviceTransport {
31
+ private readonly systemPackages: Package[] = [
32
+ HealthGrpcPackage,
33
+ MetricsGrpcPackage,
34
+ ReflectionGrpcPackageV1,
35
+ ReflectionGrpcPackageV1alpha,
36
+ ];
37
+
24
38
  private readonly logger = LoggerFactory.create(GrpcTransport);
25
39
  private readonly packages: Package[];
26
40
 
@@ -58,6 +72,21 @@ export class GrpcTransport implements IMicroserviceTransport {
58
72
  return HealthController;
59
73
  }
60
74
 
75
+ modules(): NestModuleDefinitions {
76
+ return [
77
+ ReflectionModule.forRoot(this.packages),
78
+ {
79
+ module: GrpcTransport,
80
+ providers: [
81
+ {
82
+ provide: APP_INTERCEPTOR,
83
+ useClass: GrpcLoggerInterceptor,
84
+ },
85
+ ],
86
+ },
87
+ ];
88
+ }
89
+
61
90
  /**
62
91
  * Unfortunally we can't access GRPCConfig in constructor because
63
92
  * PlatformConfigModule is not initilalized yet
@@ -72,17 +101,7 @@ export class GrpcTransport implements IMicroserviceTransport {
72
101
  }
73
102
 
74
103
  createMicroserviceOptions(): MicroserviceOptions {
75
- this.checkIsInitialized();
76
-
77
- const packages = [...this.packages];
78
-
79
- if (!packages.some((x) => x.name === HealthGrpcPackage.name)) {
80
- packages.push(HealthGrpcPackage);
81
- }
82
-
83
- if (!packages.some((x) => x.name === MetricsGrpcPackage.name)) {
84
- packages.push(MetricsGrpcPackage);
85
- }
104
+ const packages = this.getUniquePackages();
86
105
 
87
106
  for (const pkg of packages) {
88
107
  for (const service of Object.keys(pkg.definitions['grpc-js'])) {
@@ -90,11 +109,15 @@ export class GrpcTransport implements IMicroserviceTransport {
90
109
  }
91
110
  }
92
111
 
112
+ this.checkIsInitialized();
113
+ this.assertIsDefined(this.address);
114
+ this.assertIsDefined(this.maxRecv);
115
+ this.assertIsDefined(this.maxSend);
116
+
93
117
  return createGrpcOptions(packages, {
94
- // FIXME добавить проверку
95
- address: this.address!,
96
- maxRecv: this.maxRecv!,
97
- maxSend: this.maxSend!,
118
+ address: this.address,
119
+ maxRecv: this.maxRecv,
120
+ maxSend: this.maxSend,
98
121
  });
99
122
  }
100
123
 
@@ -109,4 +132,30 @@ export class GrpcTransport implements IMicroserviceTransport {
109
132
  throw new SequenceException('You should call init() method first!');
110
133
  }
111
134
  }
135
+
136
+ /**
137
+ * Checks the value must be defined and not null
138
+ * @param value any value
139
+ */
140
+ private assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
141
+ if (value === undefined || value === null) {
142
+ throw new Error(`${value} is not defined`);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Merge user packages with system packages
148
+ * @returns array of unique packages
149
+ */
150
+ private getUniquePackages(): Package[] {
151
+ const packages = [...this.packages];
152
+
153
+ for (const pkg of this.systemPackages) {
154
+ if (isPackageNotIncluded(packages, pkg)) {
155
+ packages.push(pkg);
156
+ }
157
+ }
158
+
159
+ return packages;
160
+ }
112
161
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Match packages name
3
+ * @param pkg
4
+ * @returns true if names is equal
5
+ */
6
+ const isEqualPackageName =
7
+ <T extends { name: string }>(pkg: T) =>
8
+ (x: T): boolean =>
9
+ x.name === pkg.name;
10
+
11
+ /**
12
+ * Checks whether a pkg is in the array
13
+ * @param packages array of packages
14
+ * @param pkg package you are looking for
15
+ * @returns true if pkg exists in packages
16
+ */
17
+ export const isPackageNotIncluded = <T extends { name: string }>(
18
+ packages: T[],
19
+ pkg: T,
20
+ ): boolean => !packages.some(isEqualPackageName(pkg));
@@ -0,0 +1,26 @@
1
+ import type { DynamicModule } from '@nestjs/common';
2
+ import { Module } from '@nestjs/common';
3
+ import type { Package } from '@rsdk/grpc.loader';
4
+
5
+ import {
6
+ ReflectionV1alphaController,
7
+ ReflectionV1Controller,
8
+ } from './controllers';
9
+ import { ReflectionService } from './reflection.service';
10
+
11
+ @Module({})
12
+ export class ReflectionModule {
13
+ static forRoot(packages: Package[]): DynamicModule {
14
+ return {
15
+ module: ReflectionModule,
16
+ controllers: [ReflectionV1alphaController, ReflectionV1Controller],
17
+ providers: [
18
+ ReflectionService,
19
+ {
20
+ provide: 'GRPC_PACKAGES',
21
+ useValue: packages,
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ }
@@ -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
+ }