UNPKG

7.2 kBPlain TextView Raw
1/*
2 * Copyright 2019 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18import { ChannelCredentials } from './channel-credentials';
19import { ChannelOptions } from './channel-options';
20import { Client } from './client';
21import { UntypedServiceImplementation } from './server';
22
23export interface Serialize<T> {
24 (value: T): Buffer;
25}
26
27export interface Deserialize<T> {
28 (bytes: Buffer): T;
29}
30
31export interface ClientMethodDefinition<RequestType, ResponseType> {
32 path: string;
33 requestStream: boolean;
34 responseStream: boolean;
35 requestSerialize: Serialize<RequestType>;
36 responseDeserialize: Deserialize<ResponseType>;
37 originalName?: string;
38}
39
40export interface ServerMethodDefinition<RequestType, ResponseType> {
41 path: string;
42 requestStream: boolean;
43 responseStream: boolean;
44 responseSerialize: Serialize<ResponseType>;
45 requestDeserialize: Deserialize<RequestType>;
46 originalName?: string;
47}
48
49export interface MethodDefinition<RequestType, ResponseType>
50 extends ClientMethodDefinition<RequestType, ResponseType>,
51 ServerMethodDefinition<RequestType, ResponseType> {}
52
53/* eslint-disable @typescript-eslint/no-explicit-any */
54export type ServiceDefinition<
55 ImplementationType = UntypedServiceImplementation
56> = {
57 readonly [index in keyof ImplementationType]: MethodDefinition<any, any>;
58};
59/* eslint-enable @typescript-eslint/no-explicit-any */
60
61export interface ProtobufTypeDefinition {
62 format: string;
63 type: object;
64 fileDescriptorProtos: Buffer[];
65}
66
67export interface PackageDefinition {
68 [index: string]: ServiceDefinition | ProtobufTypeDefinition;
69}
70
71/**
72 * Map with short names for each of the requester maker functions. Used in
73 * makeClientConstructor
74 * @private
75 */
76const requesterFuncs = {
77 unary: Client.prototype.makeUnaryRequest,
78 server_stream: Client.prototype.makeServerStreamRequest,
79 client_stream: Client.prototype.makeClientStreamRequest,
80 bidi: Client.prototype.makeBidiStreamRequest,
81};
82
83export interface ServiceClient extends Client {
84 [methodName: string]: Function;
85}
86
87export interface ServiceClientConstructor {
88 new (
89 address: string,
90 credentials: ChannelCredentials,
91 options?: Partial<ChannelOptions>
92 ): ServiceClient;
93 service: ServiceDefinition;
94 serviceName: string;
95}
96
97/**
98 * Returns true, if given key is included in the blacklisted
99 * keys.
100 * @param key key for check, string.
101 */
102function isPrototypePolluted(key: string): boolean {
103 return ['__proto__', 'prototype', 'constructor'].includes(key);
104}
105
106/**
107 * Creates a constructor for a client with the given methods, as specified in
108 * the methods argument. The resulting class will have an instance method for
109 * each method in the service, which is a partial application of one of the
110 * [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
111 * and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
112 * arguments predefined.
113 * @param methods An object mapping method names to
114 * method attributes
115 * @param serviceName The fully qualified name of the service
116 * @param classOptions An options object.
117 * @return New client constructor, which is a subclass of
118 * {@link grpc.Client}, and has the same arguments as that constructor.
119 */
120export function makeClientConstructor(
121 methods: ServiceDefinition,
122 serviceName: string,
123 classOptions?: {}
124): ServiceClientConstructor {
125 if (!classOptions) {
126 classOptions = {};
127 }
128
129 class ServiceClientImpl extends Client implements ServiceClient {
130 static service: ServiceDefinition;
131 static serviceName: string;
132 [methodName: string]: Function;
133 }
134
135 Object.keys(methods).forEach(name => {
136 if (isPrototypePolluted(name)) {
137 return;
138 }
139 const attrs = methods[name];
140 let methodType: keyof typeof requesterFuncs;
141 // TODO(murgatroid99): Verify that we don't need this anymore
142 if (typeof name === 'string' && name.charAt(0) === '$') {
143 throw new Error('Method names cannot start with $');
144 }
145 if (attrs.requestStream) {
146 if (attrs.responseStream) {
147 methodType = 'bidi';
148 } else {
149 methodType = 'client_stream';
150 }
151 } else {
152 if (attrs.responseStream) {
153 methodType = 'server_stream';
154 } else {
155 methodType = 'unary';
156 }
157 }
158 const serialize = attrs.requestSerialize;
159 const deserialize = attrs.responseDeserialize;
160 const methodFunc = partial(
161 requesterFuncs[methodType],
162 attrs.path,
163 serialize,
164 deserialize
165 );
166 ServiceClientImpl.prototype[name] = methodFunc;
167 // Associate all provided attributes with the method
168 Object.assign(ServiceClientImpl.prototype[name], attrs);
169 if (attrs.originalName && !isPrototypePolluted(attrs.originalName)) {
170 ServiceClientImpl.prototype[attrs.originalName] =
171 ServiceClientImpl.prototype[name];
172 }
173 });
174
175 ServiceClientImpl.service = methods;
176 ServiceClientImpl.serviceName = serviceName;
177
178 return ServiceClientImpl;
179}
180
181function partial(
182 fn: Function,
183 path: string,
184 serialize: Function,
185 deserialize: Function
186): Function {
187 // eslint-disable-next-line @typescript-eslint/no-explicit-any
188 return function (this: any, ...args: any[]) {
189 return fn.call(this, path, serialize, deserialize, ...args);
190 };
191}
192
193export interface GrpcObject {
194 [index: string]:
195 | GrpcObject
196 | ServiceClientConstructor
197 | ProtobufTypeDefinition;
198}
199
200function isProtobufTypeDefinition(
201 obj: ServiceDefinition | ProtobufTypeDefinition
202): obj is ProtobufTypeDefinition {
203 return 'format' in obj;
204}
205
206/**
207 * Load a gRPC package definition as a gRPC object hierarchy.
208 * @param packageDef The package definition object.
209 * @return The resulting gRPC object.
210 */
211export function loadPackageDefinition(
212 packageDef: PackageDefinition
213): GrpcObject {
214 const result: GrpcObject = {};
215 for (const serviceFqn in packageDef) {
216 if (Object.prototype.hasOwnProperty.call(packageDef, serviceFqn)) {
217 const service = packageDef[serviceFqn];
218 const nameComponents = serviceFqn.split('.');
219 if (nameComponents.some((comp: string) => isPrototypePolluted(comp))) {
220 continue;
221 }
222 const serviceName = nameComponents[nameComponents.length - 1];
223 let current = result;
224 for (const packageName of nameComponents.slice(0, -1)) {
225 if (!current[packageName]) {
226 current[packageName] = {};
227 }
228 current = current[packageName] as GrpcObject;
229 }
230 if (isProtobufTypeDefinition(service)) {
231 current[serviceName] = service;
232 } else {
233 current[serviceName] = makeClientConstructor(service, serviceName, {});
234 }
235 }
236 }
237 return result;
238}