Merge pull request #206 from kjin/l-p-d

grpc-js: add makeClientConstructor and loadPackageDefinition
This commit is contained in:
Michael Lumish 2018-03-06 18:27:42 -08:00 committed by GitHub
commit 5b392fadb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 201 additions and 13 deletions

View File

@ -8,20 +8,24 @@ import {ChannelCredentials} from './channel-credentials';
import {Status} from './constants';
import {Metadata} from './metadata';
// This symbol must be exported (for now).
// See: https://github.com/Microsoft/TypeScript/issues/20080
export const kChannel = Symbol();
export interface UnaryCallback<ResponseType> {
(err: ServiceError|null, value?: ResponseType): void;
}
export class Client {
private readonly channel: Channel;
private readonly [kChannel]: Channel;
constructor(
address: string, credentials: ChannelCredentials,
options: Partial<ChannelOptions> = {}) {
this.channel = new Http2Channel(address, credentials, options);
this[kChannel] = new Http2Channel(address, credentials, options);
}
close(): void {
this.channel.close();
this[kChannel].close();
}
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
@ -29,7 +33,7 @@ export class Client {
let cb: (error: Error|null) => void = once(callback);
let callbackCalled = false;
let timer: NodeJS.Timer | null = null;
this.channel.connect().then(() => {
this[kChannel].connect().then(() => {
if (timer) {
clearTimeout(timer);
}
@ -140,7 +144,7 @@ export class Client {
this.checkOptionalUnaryResponseArguments<ResponseType>(
metadata, options, callback));
const call: CallStream =
this.channel.createStream(method, metadata, options);
this[kChannel].createStream(method, metadata, options);
const message: Buffer = serialize(argument);
const writeObj: WriteObject = {message: message};
writeObj.flags = options.flags;
@ -178,7 +182,7 @@ export class Client {
this.checkOptionalUnaryResponseArguments<ResponseType>(
metadata, options, callback));
const call: CallStream =
this.channel.createStream(method, metadata, options);
this[kChannel].createStream(method, metadata, options);
this.handleUnaryResponse<ResponseType>(call, deserialize, callback);
return new ClientWritableStreamImpl<RequestType>(call, serialize);
}
@ -222,7 +226,7 @@ export class Client {
options?: CallOptions): ClientReadableStream<ResponseType> {
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
const call: CallStream =
this.channel.createStream(method, metadata, options);
this[kChannel].createStream(method, metadata, options);
const message: Buffer = serialize(argument);
const writeObj: WriteObject = {message: message};
writeObj.flags = options.flags;
@ -246,7 +250,7 @@ export class Client {
options?: CallOptions): ClientDuplexStream<RequestType, ResponseType> {
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
const call: CallStream =
this.channel.createStream(method, metadata, options);
this[kChannel].createStream(method, metadata, options);
return new ClientDuplexStreamImpl<RequestType, ResponseType>(
call, serialize, deserialize);
}

View File

@ -1,5 +1,40 @@
export * from './call-credentials';
export * from './channel-credentials';
export * from './client';
export * from './constants';
export * from './metadata';
import { CallCredentials } from './call-credentials';
import { ChannelCredentials } from './channel-credentials';
import { Client } from './client';
import { Status} from './constants';
import { makeClientConstructor, loadPackageDefinition } from './make-client';
import { Metadata } from './metadata';
const notImplementedFn = () => { throw new Error('Not implemented'); };
// Metadata
export { Metadata };
// Client credentials
export const credentials = {
createSsl: ChannelCredentials.createSsl,
createFromMetadataGenerator: CallCredentials.createFromMetadataGenerator,
createFromGoogleCredential: notImplementedFn /*TODO*/,
combineChannelCredentials: (first: ChannelCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first),
combineCallCredentials: (first: CallCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first),
createInsecure: ChannelCredentials.createInsecure
};
// Constants
export {
Status as status
// TODO: Other constants as well
};
// Client
export {
Client,
loadPackageDefinition,
makeClientConstructor,
makeClientConstructor as makeGenericClientConstructor
};
export const closeClient = (client: Client) => client.close();

View File

@ -0,0 +1,149 @@
import { Metadata } from "./metadata";
import { Client, UnaryCallback } from "./client";
import { CallOptions } from "./call-stream";
import * as _ from 'lodash';
import { ChannelCredentials } from "./channel-credentials";
import { ChannelOptions } from "./channel";
export interface Serialize<T> {
(value: T): Buffer;
}
export interface Deserialize<T> {
(bytes: Buffer): T;
}
export interface MethodDefinition<RequestType, ResponseType> {
path: string;
requestStream: boolean;
responseStream: boolean;
requestSerialize: Serialize<RequestType>;
responseSerialize: Serialize<ResponseType>;
requestDeserialize: Deserialize<RequestType>;
responseDeserialize: Deserialize<ResponseType>;
originalName?: string;
}
export interface ServiceDefinition {
[index: string]: MethodDefinition<object, object>;
}
export interface PackageDefinition {
[index: string]: ServiceDefinition;
}
function getDefaultValues<T>(metadata?: Metadata, options?: T): {
metadata: Metadata;
options: Partial<T>;
} {
return {
metadata: metadata || new Metadata(),
options: options || {}
};
}
/**
* Map with short names for each of the requester maker functions. Used in
* makeClientConstructor
* @private
*/
const requesterFuncs = {
unary: Client.prototype.makeUnaryRequest,
server_stream: Client.prototype.makeServerStreamRequest,
client_stream: Client.prototype.makeClientStreamRequest,
bidi: Client.prototype.makeBidiStreamRequest
};
export interface ServiceClient extends Client {
[methodName: string]: Function;
}
export interface ServiceClientConstructor {
new(address: string, credentials: ChannelCredentials,
options?: Partial<ChannelOptions>): ServiceClient;
service: ServiceDefinition;
};
/**
* Creates a constructor for a client with the given methods, as specified in
* the methods argument. The resulting class will have an instance method for
* each method in the service, which is a partial application of one of the
* [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
* and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
* arguments predefined.
* @param methods An object mapping method names to
* method attributes
* @param serviceName The fully qualified name of the service
* @param classOptions An options object.
* @return New client constructor, which is a subclass of
* {@link grpc.Client}, and has the same arguments as that constructor.
*/
export function makeClientConstructor(
methods: ServiceDefinition, serviceName: string,
classOptions?: {}): ServiceClientConstructor {
if (!classOptions) {
classOptions = {};
}
class ServiceClientImpl extends Client implements ServiceClient {
static service: ServiceDefinition;
[methodName: string]: Function;
}
_.each(methods, (attrs, name) => {
let methodType: keyof typeof requesterFuncs;
// TODO(murgatroid99): Verify that we don't need this anymore
if (_.startsWith(name, '$')) {
throw new Error('Method names cannot start with $');
}
if (attrs.requestStream) {
if (attrs.responseStream) {
methodType = 'bidi';
} else {
methodType = 'client_stream';
}
} else {
if (attrs.responseStream) {
methodType = 'server_stream';
} else {
methodType = 'unary';
}
}
const serialize = attrs.requestSerialize;
const deserialize = attrs.responseDeserialize;
const methodFunc = _.partial(requesterFuncs[methodType], attrs.path,
serialize, deserialize);
ServiceClientImpl.prototype[name] = methodFunc;
// Associate all provided attributes with the method
_.assign(ServiceClientImpl.prototype[name], attrs);
if (attrs.originalName) {
ServiceClientImpl.prototype[attrs.originalName] = ServiceClientImpl.prototype[name];
}
});
ServiceClientImpl.service = methods;
return ServiceClientImpl;
};
export type GrpcObject = {
[index: string]: GrpcObject | ServiceClientConstructor;
};
export function loadPackageDefinition(packageDef: PackageDefinition) {
const result: GrpcObject = {};
for (const serviceFqn in packageDef) {
const service = packageDef[serviceFqn];
const nameComponents = serviceFqn.split('.');
const serviceName = nameComponents[nameComponents.length-1];
let current = result;
for (const packageName in nameComponents.slice(0, -1)) {
if (!current[packageName]) {
current[packageName] = {};
}
current = current[packageName] as GrpcObject;
}
current[serviceName] = makeClientConstructor(service, serviceName, {});
}
return result;
}