Lint and format

This commit is contained in:
murgatroid99 2017-08-29 10:27:42 -07:00
parent f012088ecc
commit a3c1136fb4
16 changed files with 489 additions and 479 deletions

View File

@ -1,12 +1,12 @@
import {promisify} from 'util'
import {Filter, BaseFilter, FilterFactory} from './filter'
import {CallCredentials} from './call-credentials'
import {Http2Channel} from './channel'
import {CallStream} from './call-stream'
import {Metadata} from './metadata'
import {promisify} from 'util';
import {CallCredentials} from './call-credentials';
import {CallStream} from './call-stream';
import {Http2Channel} from './channel';
import {BaseFilter, Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
export class CallCredentialsFilter extends BaseFilter implements Filter {
constructor(private readonly credentials: CallCredentials) {
super();
}
@ -20,13 +20,15 @@ export class CallCredentialsFilter extends BaseFilter implements Filter {
}
}
export class CallCredentialsFilterFactory implements FilterFactory<CallCredentialsFilter> {
export class CallCredentialsFilterFactory implements
FilterFactory<CallCredentialsFilter> {
private readonly credentials: CallCredentials;
constructor(channel: Http2Channel) {
this.credentials = channel.credentials.getCallCredentials();
}
createFilter(callStream: CallStream): CallCredentialsFilter {
return new CallCredentialsFilter(this.credentials.compose(callStream.getCredentials()));
return new CallCredentialsFilter(
this.credentials.compose(callStream.getCredentials()));
}
}

View File

@ -1,10 +1,10 @@
import { Metadata } from './metadata';
import {map, reduce} from 'lodash'
import {map, reduce} from 'lodash';
export type CallMetadataGenerator = (
options: Object,
cb: (err: Error | null, metadata?: Metadata) => void
) => void;
import {Metadata} from './metadata';
export type CallMetadataGenerator =
(options: Object, cb: (err: Error|null, metadata?: Metadata) => void) =>
void;
/**
* A class that represents a generic method of adding authentication-related
@ -25,32 +25,13 @@ export interface CallCredentials {
compose(callCredentials: CallCredentials): CallCredentials;
}
export namespace CallCredentials {
/**
* Creates a new CallCredentials object from a given function that generates
* Metadata objects.
* @param metadataGenerator A function that accepts a set of options, and
* generates a Metadata object based on these options, which is passed back
* to the caller via a supplied (err, metadata) callback.
*/
export function createFromMetadataGenerator(
metadataGenerator: CallMetadataGenerator
): CallCredentials {
return new SingleCallCredentials(metadataGenerator);
}
export function createEmpty(): CallCredentials {
return new EmptyCallCredentials();
}
}
class ComposedCallCredentials implements CallCredentials {
constructor(private creds: CallCredentials[]) {}
async generateMetadata(options: Object): Promise<Metadata> {
let base: Metadata = new Metadata();
let generated: Metadata[] = await Promise.all(map(
this.creds, (cred) => cred.generateMetadata(options)));
let generated: Metadata[] = await Promise.all(
map(this.creds, (cred) => cred.generateMetadata(options)));
for (let gen of generated) {
base.merge(gen);
}
@ -83,8 +64,6 @@ class SingleCallCredentials implements CallCredentials{
}
class EmptyCallCredentials implements CallCredentials {
constructor () {}
async generateMetadata(options: Object): Promise<Metadata> {
return new Metadata();
}
@ -93,3 +72,21 @@ class EmptyCallCredentials implements CallCredentials {
return other;
}
}
export namespace CallCredentials {
/**
* Creates a new CallCredentials object from a given function that generates
* Metadata objects.
* @param metadataGenerator A function that accepts a set of options, and
* generates a Metadata object based on these options, which is passed back
* to the caller via a supplied (err, metadata) callback.
*/
export function createFromMetadataGenerator(
metadataGenerator: CallMetadataGenerator): CallCredentials {
return new SingleCallCredentials(metadataGenerator);
}
export function createEmpty(): CallCredentials {
return new EmptyCallCredentials();
}
}

View File

@ -1,18 +1,14 @@
import * as stream from 'stream';
import * as http2 from 'http2';
import {Duplex} from 'stream';
import {CallCredentials} from './call-credentials';
import {Status} from './constants';
import {Filter} from './filter';
import {FilterStackFactory} from './filter-stack';
import {Metadata} from './metadata';
import {ObjectDuplex} from './object-stream';
import {Filter} from './filter'
import {FilterStackFactory} from './filter-stack';
const {
HTTP2_HEADER_STATUS,
HTTP2_HEADER_CONTENT_TYPE
} = http2.constants;
const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE} = http2.constants;
export type Deadline = Date | number;
@ -85,21 +81,20 @@ enum ReadState {
READING_MESSAGE
}
export class Http2CallStream extends stream.Duplex implements CallStream {
export class Http2CallStream extends Duplex implements CallStream {
public filterStack: Filter;
private statusEmitted: boolean = false;
private statusEmitted = false;
private http2Stream: http2.ClientHttp2Stream|null = null;
private pendingRead: boolean = false;
private pendingRead = false;
private pendingWrite: Buffer|null = null;
private pendingWriteCallback: Function|null = null;
private pendingFinalCallback: Function|null = null;
private readState: ReadState = ReadState.NO_DATA;
private readCompressFlag: boolean = false;
private readCompressFlag = false;
private readPartialSize: Buffer = Buffer.alloc(4);
private readSizeRemaining: number = 4;
private readMessageSize: number = 0;
private readSizeRemaining = 4;
private readMessageSize = 0;
private readPartialMessage: Buffer[] = [];
private readMessageRemaining = 0;
@ -111,7 +106,8 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
// This is populated (non-null) if and only if the call has ended
private finalStatus: StatusObject|null = null;
constructor(private readonly methodName: string,
constructor(
private readonly methodName: string,
private readonly options: CallStreamOptions,
filterStackFactory: FilterStackFactory) {
super({objectMode: true});
@ -163,9 +159,12 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
this.cancelWithStatus(Status.UNKNOWN, e.message);
return;
}
this.filterStack.receiveMetadata(Promise.resolve(metadata)).then((finalMetadata) => {
this.filterStack.receiveMetadata(Promise.resolve(metadata))
.then(
(finalMetadata) => {
this.emit('metadata', finalMetadata);
}, (error) => {
},
(error) => {
this.cancelWithStatus(Status.UNKNOWN, error.message);
});
});
@ -180,7 +179,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
}
delete headers['grpc-status'];
}
let details: string = '';
let details = '';
if (headers.hasOwnProperty('grpc-message')) {
details = decodeURI(headers['grpc-message']);
}
@ -191,9 +190,12 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
metadata = new Metadata();
}
let status: StatusObject = {code, details, metadata};
this.filterStack.receiveTrailers(Promise.resolve(status)).then((finalStatus) => {
this.filterStack.receiveTrailers(Promise.resolve(status))
.then(
(finalStatus) => {
this.endCall(finalStatus);
}, (error) => {
},
(error) => {
this.endCall({
code: Status.INTERNAL,
details: 'Failed to process received status',
@ -219,7 +221,9 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
break;
case ReadState.READING_SIZE:
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
data.copy(this.readPartialSize, 4 - this.readSizeRemaining, readHead, readHead + toRead);
data.copy(
this.readPartialSize, 4 - this.readSizeRemaining, readHead,
readHead + toRead);
this.readSizeRemaining -= toRead;
readHead += toRead;
// readSizeRemaining >=0 here
@ -230,14 +234,17 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
}
break;
case ReadState.READING_MESSAGE:
toRead = Math.min(data.length - readHead, this.readMessageRemaining);
this.readPartialMessage.push(data.slice(readHead, readHead + toRead));
toRead =
Math.min(data.length - readHead, this.readMessageRemaining);
this.readPartialMessage.push(
data.slice(readHead, readHead + toRead));
this.readMessageRemaining -= toRead;
readHead += toRead;
// readMessageRemaining >=0 here
if (this.readMessageRemaining === 0) {
// At this point, we have read a full message
const messageBytes = Buffer.concat(this.readPartialMessage, this.readMessageSize);
const messageBytes = Buffer.concat(
this.readPartialMessage, this.readMessageSize);
// TODO(murgatroid99): Add receive message filters
if (canPush) {
if (!this.push(messageBytes)) {
@ -261,7 +268,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
});
stream.on('streamClosed', (errorCode) => {
let code: Status;
let details: string = '';
let details = '';
switch (errorCode) {
case http2.constants.NGHTTP2_REFUSED_STREAM:
code = Status.UNAVAILABLE;
@ -280,11 +287,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
default:
code = Status.INTERNAL;
}
this.endCall({
code: code,
details: details,
metadata: new Metadata()
});
this.endCall({code: code, details: details, metadata: new Metadata()});
});
stream.on('error', () => {
this.endCall({

View File

@ -1,6 +1,7 @@
import { CallCredentials } from './call-credentials';
import {createSecureContext, SecureContext} from 'tls';
import {CallCredentials} from './call-credentials';
/**
* A class that contains credentials for communicating over a channel, as well
* as a set of per-call credentials, which are applied to every method call made
@ -28,39 +29,6 @@ export interface ChannelCredentials {
getSecureContext(): SecureContext|null;
}
export namespace ChannelCredentials {
/**
* Return a new ChannelCredentials instance with a given set of credentials.
* The resulting instance can be used to construct a Channel that communicates
* over TLS.
* @param rootCerts The root certificate data.
* @param privateKey The client certificate private key, if available.
* @param certChain The client certificate key chain, if available.
*/
export function createSsl(rootCerts?: Buffer | null, privateKey?: Buffer | null, certChain?: Buffer | null) : ChannelCredentials {
if (privateKey && !certChain) {
throw new Error('Private key must be given with accompanying certificate chain');
}
if (!privateKey && certChain) {
throw new Error('Certificate chain must be given with accompanying private key');
}
const secureContext = createSecureContext({
ca: rootCerts || undefined,
key: privateKey || undefined,
cert: certChain || undefined
});
return new SecureChannelCredentialsImpl(secureContext);
}
/**
* Return a new ChannelCredentials instance with no credentials.
*/
export function createInsecure() : ChannelCredentials {
return new InsecureChannelCredentialsImpl();
}
}
abstract class ChannelCredentialsImpl implements ChannelCredentials {
protected callCredentials: CallCredentials;
@ -83,7 +51,7 @@ class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl {
}
compose(callCredentials: CallCredentials): ChannelCredentialsImpl {
throw new Error("Cannot compose insecure credentials");
throw new Error('Cannot compose insecure credentials');
}
getSecureContext(): SecureContext|null {
@ -94,21 +62,55 @@ class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl {
class SecureChannelCredentialsImpl extends ChannelCredentialsImpl {
secureContext: SecureContext;
constructor(
secureContext: SecureContext,
callCredentials?: CallCredentials
) {
constructor(secureContext: SecureContext, callCredentials?: CallCredentials) {
super(callCredentials);
this.secureContext = secureContext;
}
compose(callCredentials: CallCredentials): ChannelCredentialsImpl {
const combinedCallCredentials = this.callCredentials.compose(callCredentials);
return new SecureChannelCredentialsImpl(this.secureContext,
combinedCallCredentials);
const combinedCallCredentials =
this.callCredentials.compose(callCredentials);
return new SecureChannelCredentialsImpl(
this.secureContext, combinedCallCredentials);
}
getSecureContext(): SecureContext|null {
return this.secureContext;
}
}
export namespace ChannelCredentials {
/**
* Return a new ChannelCredentials instance with a given set of credentials.
* The resulting instance can be used to construct a Channel that communicates
* over TLS.
* @param rootCerts The root certificate data.
* @param privateKey The client certificate private key, if available.
* @param certChain The client certificate key chain, if available.
*/
export function createSsl(
rootCerts?: Buffer|null, privateKey?: Buffer|null,
certChain?: Buffer|null): ChannelCredentials {
if (privateKey && !certChain) {
throw new Error(
'Private key must be given with accompanying certificate chain');
}
if (!privateKey && certChain) {
throw new Error(
'Certificate chain must be given with accompanying private key');
}
const secureContext = createSecureContext({
ca: rootCerts || undefined,
key: privateKey || undefined,
cert: certChain || undefined
});
return new SecureChannelCredentialsImpl(secureContext);
}
/**
* Return a new ChannelCredentials instance with no credentials.
*/
export function createInsecure(): ChannelCredentials {
return new InsecureChannelCredentialsImpl();
}
}

View File

@ -1,17 +1,17 @@
import {EventEmitter} from 'events';
import {SecureContext} from 'tls';
import * as http2 from 'http2';
import {SecureContext} from 'tls';
import * as url from 'url';
import {CallOptions, CallStreamOptions, CallStream, Http2CallStream} from './call-stream';
import {CallCredentials} from './call-credentials';
import {ChannelCredentials} from './channel-credentials';
import {Metadata, MetadataObject} from './metadata';
import {Status} from './constants'
import {FilterStackFactory} from './filter-stack'
import {DeadlineFilterFactory} from './deadline-filter'
import {CallCredentialsFilterFactory} from './call-credentials-filter'
import {CompressionFilterFactory} from './compression-filter'
import {CallCredentials} from './call-credentials';
import {CallCredentialsFilterFactory} from './call-credentials-filter';
import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream';
import {ChannelCredentials} from './channel-credentials';
import {CompressionFilterFactory} from './compression-filter';
import {Status} from './constants';
import {DeadlineFilterFactory} from './deadline-filter';
import {FilterStackFactory} from './filter-stack';
import {Metadata, MetadataObject} from './metadata';
const IDLE_TIMEOUT_MS = 300000;
@ -45,7 +45,8 @@ export enum ConnectivityState {
* by a given address.
*/
export interface Channel extends EventEmitter {
createStream(methodName: string, metadata: Metadata, options: CallOptions): CallStream;
createStream(methodName: string, metadata: Metadata, options: CallOptions):
CallStream;
connect(callback: () => void): void;
getConnectivityState(): ConnectivityState;
close(): void;
@ -94,7 +95,7 @@ export class Http2Channel extends EventEmitter implements Channel {
private goIdle(): void {
if (this.subChannel !== null) {
this.subChannel.shutdown({graceful: true}, () => {});
this.subChannel.shutdown({graceful: true}, () => undefined);
this.subChannel = null;
}
this.transitionToState(ConnectivityState.IDLE);
@ -106,7 +107,8 @@ export class Http2Channel extends EventEmitter implements Channel {
}
}
constructor(private readonly address: url.URL,
constructor(
private readonly address: url.URL,
public readonly credentials: ChannelCredentials,
private readonly options: ChannelOptions) {
super();
@ -117,15 +119,17 @@ export class Http2Channel extends EventEmitter implements Channel {
}
this.filterStackFactory = new FilterStackFactory([
new CompressionFilterFactory(this),
new CallCredentialsFilterFactory(this),
new DeadlineFilterFactory(this)
new CallCredentialsFilterFactory(this), new DeadlineFilterFactory(this)
]);
}
private startHttp2Stream(methodName: string, stream: Http2CallStream, metadata: Metadata) {
let finalMetadata: Promise<Metadata> = stream.filterStack.sendMetadata(Promise.resolve(metadata));
private startHttp2Stream(
methodName: string, stream: Http2CallStream, metadata: Metadata) {
let finalMetadata: Promise<Metadata> =
stream.filterStack.sendMetadata(Promise.resolve(metadata));
this.connect(() => {
finalMetadata.then((metadataValue) => {
finalMetadata.then(
(metadataValue) => {
let headers = metadataValue.toHttp2Headers();
headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname;
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
@ -138,20 +142,23 @@ export class Http2Channel extends EventEmitter implements Channel {
(this.subChannel as http2.ClientHttp2Session);
stream.attachHttp2Stream(session.request(headers));
} else {
/* In this case, we lost the connection while finalizing metadata.
* That should be very unusual */
/* In this case, we lost the connection while finalizing
* metadata. That should be very unusual */
setImmediate(() => {
this.startHttp2Stream(methodName, stream, metadata);
});
}
}
}, (error) => {
stream.cancelWithStatus(Status.UNKNOWN, "Failed to generate metadata");
},
(error) => {
stream.cancelWithStatus(
Status.UNKNOWN, 'Failed to generate metadata');
});
});
}
createStream(methodName: string, metadata: Metadata, options: CallOptions): CallStream {
createStream(methodName: string, metadata: Metadata, options: CallOptions):
CallStream {
if (this.connectivityState === ConnectivityState.SHUTDOWN) {
throw new Error('Channel has been shut down');
}
@ -159,8 +166,9 @@ export class Http2Channel extends EventEmitter implements Channel {
deadline: options.deadline === undefined ? Infinity : options.deadline,
credentials: options.credentials || CallCredentials.createEmpty(),
flags: options.flags || 0
}
let stream: Http2CallStream = new Http2CallStream(methodName, finalOptions, this.filterStackFactory);
};
let stream: Http2CallStream =
new Http2CallStream(methodName, finalOptions, this.filterStackFactory);
this.startHttp2Stream(methodName, stream, metadata);
return stream;
}

View File

@ -3,7 +3,7 @@ import {URL} from 'url';
import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError, ServiceErrorImpl} from './call';
import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream';
import {Channel, Http2Channel, ChannelOptions} from './channel';
import {Channel, ChannelOptions, Http2Channel} from './channel';
import {ChannelCredentials} from './channel-credentials';
import {Status} from './constants';
import {Metadata} from './metadata';
@ -34,11 +34,11 @@ export class Client {
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
void {
let cb: (error: Error|null) => void = once(callback);
let callbackCalled: boolean = false;
let callbackCalled = false;
this.channel.connect(() => {
cb(null);
});
if (deadline != Infinity) {
if (deadline !== Infinity) {
let timeout: number;
let now: number = (new Date).getTime();
if (deadline instanceof Date) {

View File

@ -1,10 +1,9 @@
import {CallStream} from './call-stream'
import {Channel} from './channel'
import {Filter, BaseFilter, FilterFactory} from './filter'
import {Metadata} from './metadata'
import {CallStream} from './call-stream';
import {Channel} from './channel';
import {BaseFilter, Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
export class CompressionFilter extends BaseFilter implements Filter {
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
const headers: Metadata = await metadata;
headers.set('grpc-encoding', 'identity');
@ -20,8 +19,9 @@ export class CompressionFilter extends BaseFilter implements Filter {
}
}
export class CompressionFilterFactory implements FilterFactory<CompressionFilter> {
constructor(channel: Channel) {}
export class CompressionFilterFactory implements
FilterFactory<CompressionFilter> {
constructor(private readonly channel: Channel) {}
createFilter(callStream: CallStream): CompressionFilter {
return new CompressionFilter();
}

View File

@ -1,19 +1,17 @@
import {CallStream} from './call-stream'
import {Channel, Http2Channel} from './channel'
import {Filter, BaseFilter, FilterFactory} from './filter'
import {Status} from './constants'
import {Metadata} from './metadata'
import {CallStream} from './call-stream';
import {Channel, Http2Channel} from './channel';
import {Status} from './constants';
import {BaseFilter, Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
const units: [string, number][] = [
['m', 1],
['S', 1000],
['M', 60 * 1000],
['H', 60 * 60 * 1000]
]
const units: [string, number][] =
[['m', 1], ['S', 1000], ['M', 60 * 1000], ['H', 60 * 60 * 1000]];
export class DeadlineFilter extends BaseFilter implements Filter {
private deadline: number;
constructor(private readonly channel: Http2Channel, private readonly callStream: CallStream) {
constructor(
private readonly channel: Http2Channel,
private readonly callStream: CallStream) {
super();
let callDeadline = callStream.getDeadline();
if (callDeadline instanceof Date) {
@ -28,7 +26,8 @@ export class DeadlineFilter extends BaseFilter implements Filter {
}
if (this.deadline !== Infinity) {
setTimeout(() => {
callStream.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
callStream.cancelWithStatus(
Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
}, timeout);
}
}
@ -37,7 +36,8 @@ export class DeadlineFilter extends BaseFilter implements Filter {
if (this.deadline === Infinity) {
return await metadata;
}
let timeoutString : Promise<string> = new Promise<string>((resolve, reject) => {
let timeoutString: Promise<string> =
new Promise<string>((resolve, reject) => {
this.channel.connect(() => {
let now = (new Date()).getTime();
let timeoutMs = this.deadline - now;

View File

@ -1,21 +1,26 @@
import {flow, flowRight, map} from 'lodash';
import {Metadata} from './metadata';
import {CallStream, StatusObject} from './call-stream'
import {CallStream, StatusObject} from './call-stream';
import {Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
export class FilterStack implements Filter {
constructor(private readonly filters: Filter[]) {}
sendMetadata(metadata: Promise<Metadata>) {
return flow(map(this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata);
return flow(map(
this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata);
}
receiveMetadata(metadata: Promise<Metadata>) {
return flowRight(map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))(metadata);
return flowRight(
map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))(
metadata);
}
receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject> {
return flowRight(map(this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status);
return flowRight(map(
this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status);
}
}
@ -23,6 +28,7 @@ export class FilterStackFactory implements FilterFactory<FilterStack> {
constructor(private readonly factories: FilterFactory<any>[]) {}
createFilter(callStream: CallStream): FilterStack {
return new FilterStack(map(this.factories, (factory) => factory.createFilter(callStream)));
return new FilterStack(
map(this.factories, (factory) => factory.createFilter(callStream)));
}
}

View File

@ -1,9 +1,10 @@
import {Metadata} from './metadata'
import {StatusObject, CallStream} from './call-stream'
import {CallStream, StatusObject} from './call-stream';
import {Metadata} from './metadata';
/**
* Filter classes represent related per-call logic and state that is primarily
* used to modify incoming and outgoing data */
* used to modify incoming and outgoing data
*/
export interface Filter {
sendMetadata(metadata: Promise<Metadata>): Promise<Metadata>;

View File

@ -1,11 +1,9 @@
import { forOwn } from 'lodash';
import * as http2 from 'http2';
import {forOwn} from 'lodash';
export type MetadataValue = string | Buffer;
export interface MetadataObject {
[key: string]: Array<MetadataValue>;
}
export interface MetadataObject { [key: string]: Array<MetadataValue>; }
function cloneMetadataObject(repr: MetadataObject): MetadataObject {
const result: MetadataObject = {};
@ -54,7 +52,8 @@ function validate(key: string, value?: MetadataValue): void {
'keys that don\'t end with \'-bin\' must have String values');
}
if (!isLegalNonBinaryValue(value)) {
throw new Error('Metadata string value "' + value +
throw new Error(
'Metadata string value "' + value +
'" contains illegal characters');
}
}
@ -66,7 +65,6 @@ function validate(key: string, value?: MetadataValue): void {
*/
export class Metadata {
protected internalRepr: MetadataObject = {};
constructor() {}
/**
* Sets the given value for the given key by replacing any other values
@ -195,7 +193,7 @@ export class Metadata {
if (Array.isArray(values)) {
values.forEach((value) => {
result.add(key, Buffer.from(value, 'base64'));
})
});
} else {
result.add(key, Buffer.from(values, 'base64'));
}
@ -203,7 +201,7 @@ export class Metadata {
if (Array.isArray(values)) {
values.forEach((value) => {
result.add(key, value);
})
});
} else {
result.add(key, values);
}

View File

@ -1,4 +1,4 @@
import { Readable, Writable, Duplex } from 'stream';
import {Duplex, Readable, Writable} from 'stream';
export interface IntermediateObjectReadable<T> extends Readable {
read(size?: number): any&T;
@ -44,7 +44,8 @@ export interface ObjectWritable<T> extends IntermediateObjectWritable<T> {
end(chunk: T, encoding?: any, cb?: Function): void;
}
export interface ObjectDuplex<T, U> extends Duplex, ObjectWritable<T>, ObjectReadable<U> {
export interface ObjectDuplex<T, U> extends Duplex, ObjectWritable<T>,
ObjectReadable<U> {
read(size?: number): U;
_write(chunk: T, encoding: string, callback: Function): void;

View File

@ -1,7 +1,8 @@
import { Metadata } from '../src/metadata';
import { CallCredentials, CallMetadataGenerator } from '../src/call-credentials';
import * as assert from 'assert';
import {CallCredentials, CallMetadataGenerator} from '../src/call-credentials';
import {Metadata} from '../src/metadata';
// Metadata generators
function makeGenerator(props: Array<string>): CallMetadataGenerator {
@ -13,19 +14,19 @@ function makeGenerator(props: Array<string>): CallMetadataGenerator {
}
});
cb(null, metadata);
}
};
}
function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator {
return (_options, cb) => {
return (options, cb) => {
const metadata = new Metadata();
metadata.add('msElapsed', `${ms}`);
setTimeout(() => cb(null, metadata), ms);
};
};
}
const generateFromName: CallMetadataGenerator = makeGenerator(['name']);
const generateWithError: CallMetadataGenerator = (_options, cb) =>
const generateWithError: CallMetadataGenerator = (options, cb) =>
cb(new Error());
// Tests
@ -33,23 +34,27 @@ const generateWithError: CallMetadataGenerator = (_options, cb) =>
describe('CallCredentials', () => {
describe('createFromMetadataGenerator', () => {
it('should accept a metadata generator', () => {
assert.doesNotThrow(() =>
CallCredentials.createFromMetadataGenerator(generateFromName));
assert.doesNotThrow(
() => CallCredentials.createFromMetadataGenerator(generateFromName));
});
});
describe('compose', () => {
it('should accept a CallCredentials object and return a new object', () => {
const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials1 =
CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 =
CallCredentials.createFromMetadataGenerator(generateFromName);
const combinedCredentials = callCredentials1.compose(callCredentials2);
assert.notEqual(combinedCredentials, callCredentials1);
assert.notEqual(combinedCredentials, callCredentials2);
});
it('should be chainable', () => {
const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials1 =
CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 =
CallCredentials.createFromMetadataGenerator(generateFromName);
assert.doesNotThrow(() => {
callCredentials1.compose(callCredentials2)
.compose(callCredentials2)
@ -61,7 +66,8 @@ describe('CallCredentials', () => {
describe('generateMetadata', () => {
it('should call the function passed to createFromMetadataGenerator',
async () => {
const callCredentials = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials =
CallCredentials.createFromMetadataGenerator(generateFromName);
let metadata: Metadata;
try {
metadata = await callCredentials.generateMetadata({name: 'foo'});
@ -69,13 +75,12 @@ describe('CallCredentials', () => {
throw err;
}
assert.deepEqual(metadata.get('name'), ['foo']);
}
);
});
it('should emit an error if the associated metadataGenerator does',
async () => {
const callCredentials = CallCredentials.createFromMetadataGenerator(
generateWithError);
const callCredentials =
CallCredentials.createFromMetadataGenerator(generateWithError);
let metadata: Metadata|null = null;
try {
metadata = await callCredentials.generateMetadata({});
@ -83,8 +88,7 @@ describe('CallCredentials', () => {
assert.ok(err instanceof Error);
}
assert.strictEqual(metadata, null);
}
);
});
it('should combine metadata from multiple generators', async () => {
const [callCreds1, callCreds2, callCreds3, callCreds4] =
@ -93,23 +97,21 @@ describe('CallCredentials', () => {
makeAfterMsElapsedGenerator(ms);
return CallCredentials.createFromMetadataGenerator(generator);
});
const testCases = [{
credentials: callCreds1
.compose(callCreds2)
const testCases = [
{
credentials: callCreds1.compose(callCreds2)
.compose(callCreds3)
.compose(callCreds4),
expected: ['50', '100', '150', '200']
}, {
credentials: callCreds4
.compose(callCreds3
.compose(callCreds2
.compose(callCreds1))),
},
{
credentials: callCreds4.compose(
callCreds3.compose(callCreds2.compose(callCreds1))),
expected: ['200', '150', '100', '50']
}, {
credentials: callCreds3
.compose(callCreds4
.compose(callCreds1)
.compose(callCreds2)),
},
{
credentials: callCreds3.compose(
callCreds4.compose(callCreds1).compose(callCreds2)),
expected: ['150', '200', '50', '100']
}
];

View File

@ -1,10 +1,12 @@
import * as assert from 'assert';
import { CallCredentials } from '../src/call-credentials';
import { ChannelCredentials } from '../src/channel-credentials';
import { mockFunction, assert2 } from './common';
import * as fs from 'fs';
import {promisify} from 'util';
import {CallCredentials} from '../src/call-credentials';
import {ChannelCredentials} from '../src/channel-credentials';
import {assert2, mockFunction} from './common';
class CallCredentialsMock implements CallCredentials {
child: CallCredentialsMock;
constructor(child?: CallCredentialsMock) {
@ -32,22 +34,17 @@ class CallCredentialsMock implements CallCredentials {
const readFile: (...args: any[]) => Promise<Buffer> = promisify(fs.readFile);
// A promise which resolves to loaded files in the form { ca, key, cert }
const pFixtures = Promise.all([
'ca.pem',
'server1.key',
'server1.pem'
].map((file) => readFile(`test/fixtures/${file}`))
).then((result) => {
return {
ca: result[0],
key: result[1],
cert: result[2]
};
const pFixtures = Promise
.all(['ca.pem', 'server1.key', 'server1.pem'].map(
(file) => readFile(`test/fixtures/${file}`)))
.then((result) => {
return {ca: result[0], key: result[1], cert: result[2]};
});
describe('ChannelCredentials Implementation', () => {
describe('createInsecure', () => {
it('should return a ChannelCredentials object with no associated secure context', () => {
it('should return a ChannelCredentials object with no associated secure context',
() => {
const creds = assert2.noThrowAndReturn(
() => ChannelCredentials.createInsecure());
assert.ok(!creds.getSecureContext());
@ -56,15 +53,15 @@ describe('ChannelCredentials Implementation', () => {
describe('createSsl', () => {
it('should work when given no arguments', () => {
const creds: ChannelCredentials = assert2.noThrowAndReturn(
() => ChannelCredentials.createSsl());
const creds: ChannelCredentials =
assert2.noThrowAndReturn(() => ChannelCredentials.createSsl());
assert.ok(!!creds.getSecureContext());
});
it('should work with just a CA override', async () => {
const {ca} = await pFixtures;
const creds = assert2.noThrowAndReturn(
() => ChannelCredentials.createSsl(ca));
const creds =
assert2.noThrowAndReturn(() => ChannelCredentials.createSsl(ca));
assert.ok(!!creds.getSecureContext());
});
@ -99,8 +96,7 @@ describe('ChannelCredentials Implementation', () => {
const channelCreds = ChannelCredentials.createSsl();
const callCreds = new CallCredentialsMock();
const composedChannelCreds = channelCreds.compose(callCreds);
assert.strictEqual(composedChannelCreds.getCallCredentials(),
callCreds);
assert.strictEqual(composedChannelCreds.getCallCredentials(), callCreds);
});
it('should be chainable', () => {

View File

@ -1,31 +1,30 @@
import * as assert from 'assert';
import * as http2 from 'http2';
import {range} from 'lodash';
import * as metadata from '../src/metadata';
import {Metadata} from '../src/metadata';
class Metadata extends metadata.Metadata {
class TestMetadata extends Metadata {
getInternalRepresentation() {
return this.internalRepr;
}
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata {
const result = metadata.Metadata.fromHttp2Headers(headers) as Metadata;
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): TestMetadata {
const result = Metadata.fromHttp2Headers(headers) as TestMetadata;
result.getInternalRepresentation =
Metadata.prototype.getInternalRepresentation;
TestMetadata.prototype.getInternalRepresentation;
return result;
}
}
const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.';
const validNonBinValueChars = range(0x20, 0x7f)
.map(code => String.fromCharCode(code))
.join('');
const validNonBinValueChars =
range(0x20, 0x7f).map(code => String.fromCharCode(code)).join('');
describe('Metadata', () => {
let metadata: Metadata;
let metadata: TestMetadata;
beforeEach(() => {
metadata = new Metadata();
metadata = new TestMetadata();
});
describe('set', () => {
@ -178,10 +177,8 @@ describe('Metadata', () => {
metadata.add('Key2', 'value2');
metadata.add('KEY3', 'value3a');
metadata.add('KEY3', 'value3b');
assert.deepEqual(metadata.getMap(),
{key1: 'value1',
key2: 'value2',
key3: 'value3a'});
assert.deepEqual(
metadata.getMap(), {key1: 'value1', key2: 'value2', key3: 'value3a'});
});
});
@ -213,7 +210,7 @@ describe('Metadata', () => {
metadata.add('Key2', 'value2a');
metadata.add('KEY3', 'value3a');
metadata.add('key4', 'value4');
const metadata2 = new Metadata();
const metadata2 = new TestMetadata();
metadata2.add('KEY1', 'value1');
metadata2.add('key2', 'value2b');
metadata2.add('key3', 'value3b');
@ -246,8 +243,7 @@ describe('Metadata', () => {
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
'AAECAwQFBgcICQoLDA0ODw==',
'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
]
});
@ -265,27 +261,25 @@ describe('Metadata', () => {
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
'AAECAwQFBgcICQoLDA0ODw==',
'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
]
};
const metadataFromHeaders = Metadata.fromHttp2Headers(headers);
const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers);
const internalRepr = metadataFromHeaders.getInternalRepresentation();
assert.deepEqual(internalRepr, {
key1: ['value1'],
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
Buffer.from(range(0, 16)),
Buffer.from(range(16, 32)),
Buffer.from(range(0, 16)), Buffer.from(range(16, 32)),
Buffer.from(range(0, 32))
]
});
});
it('creates an empty Metadata object from empty headers', () => {
const metadataFromHeaders = Metadata.fromHttp2Headers({});
const metadataFromHeaders = TestMetadata.fromHttp2Headers({});
const internalRepr = metadataFromHeaders.getInternalRepresentation();
assert.deepEqual(internalRepr, {});
});