mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-31 09:35:39 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
97
dataprotocol-node/jsonrpc/src/cancellation.ts
Normal file
97
dataprotocol-node/jsonrpc/src/cancellation.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Event, Emitter } from './events';
|
||||
|
||||
export interface CancellationToken {
|
||||
/**
|
||||
* Is `true` when the token has been cancelled, `false` otherwise.
|
||||
*/
|
||||
isCancellationRequested: boolean;
|
||||
|
||||
/**
|
||||
* An [event](#Event) which fires upon cancellation.
|
||||
*/
|
||||
onCancellationRequested: Event<any>;
|
||||
}
|
||||
|
||||
export namespace CancellationToken {
|
||||
|
||||
export const None: CancellationToken = Object.freeze({
|
||||
isCancellationRequested: false,
|
||||
onCancellationRequested: Event.None
|
||||
});
|
||||
|
||||
export const Cancelled: CancellationToken = Object.freeze({
|
||||
isCancellationRequested: true,
|
||||
onCancellationRequested: Event.None
|
||||
});
|
||||
}
|
||||
|
||||
const shortcutEvent: Event<any> = Object.freeze(function (callback, context?) {
|
||||
let handle = setTimeout(callback.bind(context), 0);
|
||||
return { dispose() { clearTimeout(handle); } };
|
||||
});
|
||||
|
||||
class MutableToken implements CancellationToken {
|
||||
|
||||
private _isCancelled: boolean = false;
|
||||
private _emitter: Emitter<any>;
|
||||
|
||||
public cancel() {
|
||||
if (!this._isCancelled) {
|
||||
this._isCancelled = true;
|
||||
if (this._emitter) {
|
||||
this._emitter.fire(undefined);
|
||||
this._emitter = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isCancellationRequested(): boolean {
|
||||
return this._isCancelled;
|
||||
}
|
||||
|
||||
get onCancellationRequested(): Event<any> {
|
||||
if (this._isCancelled) {
|
||||
return shortcutEvent;
|
||||
}
|
||||
if (!this._emitter) {
|
||||
this._emitter = new Emitter<any>();
|
||||
}
|
||||
return this._emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
export class CancellationTokenSource {
|
||||
|
||||
private _token: CancellationToken;
|
||||
|
||||
get token(): CancellationToken {
|
||||
if (!this._token) {
|
||||
// be lazy and create the token only when
|
||||
// actually needed
|
||||
this._token = new MutableToken();
|
||||
}
|
||||
return this._token;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (!this._token) {
|
||||
// save an object by returning the default
|
||||
// cancelled token when cancellation happens
|
||||
// before someone asks for the token
|
||||
this._token = CancellationToken.Cancelled;
|
||||
} else {
|
||||
(<MutableToken>this._token).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
213
dataprotocol-node/jsonrpc/src/events.ts
Normal file
213
dataprotocol-node/jsonrpc/src/events.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
export interface Disposable {
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a typed event.
|
||||
*/
|
||||
export interface Event<T> {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param listener The listener function will be call when the event happens.
|
||||
* @param thisArgs The 'this' which will be used when calling the event listener.
|
||||
* @param disposables An array to which a {{IDisposable}} will be added. The
|
||||
* @return
|
||||
*/
|
||||
(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable;
|
||||
}
|
||||
|
||||
export namespace Event {
|
||||
const _disposable = { dispose() { } };
|
||||
export const None: Event<any> = function () { return _disposable; };
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a type which can release resources, such
|
||||
* as event listening or a timer.
|
||||
*/
|
||||
class DisposableImpl implements Disposable {
|
||||
|
||||
/**
|
||||
* Combine many disposable-likes into one. Use this method
|
||||
* when having objects with a dispose function which are not
|
||||
* instances of Disposable.
|
||||
*
|
||||
* @return Returns a new disposable which, upon dispose, will
|
||||
* dispose all provides disposable-likes.
|
||||
*/
|
||||
static from(...disposables: { dispose(): any }[]): DisposableImpl {
|
||||
return new DisposableImpl(function () {
|
||||
if (disposables) {
|
||||
for (let disposable of disposables) {
|
||||
disposable.dispose();
|
||||
}
|
||||
disposables = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _callOnDispose: Function;
|
||||
|
||||
constructor(callOnDispose: Function) {
|
||||
this._callOnDispose = callOnDispose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose(): any {
|
||||
if (typeof this._callOnDispose === 'function') {
|
||||
this._callOnDispose();
|
||||
this._callOnDispose = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CallbackList {
|
||||
|
||||
private _callbacks: Function[];
|
||||
private _contexts: any[];
|
||||
|
||||
public add(callback: Function, context: any = null, bucket?: Disposable[]): void {
|
||||
if (!this._callbacks) {
|
||||
this._callbacks = [];
|
||||
this._contexts = [];
|
||||
}
|
||||
this._callbacks.push(callback);
|
||||
this._contexts.push(context);
|
||||
|
||||
if (Array.isArray(bucket)) {
|
||||
bucket.push({ dispose: () => this.remove(callback, context) });
|
||||
}
|
||||
}
|
||||
|
||||
public remove(callback: Function, context: any = null): void {
|
||||
if (!this._callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
var foundCallbackWithDifferentContext = false;
|
||||
for (var i = 0, len = this._callbacks.length; i < len; i++) {
|
||||
if (this._callbacks[i] === callback) {
|
||||
if (this._contexts[i] === context) {
|
||||
// callback & context match => remove it
|
||||
this._callbacks.splice(i, 1);
|
||||
this._contexts.splice(i, 1);
|
||||
return;
|
||||
} else {
|
||||
foundCallbackWithDifferentContext = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundCallbackWithDifferentContext) {
|
||||
throw new Error('When adding a listener with a context, you should remove it with the same context');
|
||||
}
|
||||
}
|
||||
|
||||
public invoke(...args: any[]): any[] {
|
||||
if (!this._callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ret: any[] = [],
|
||||
callbacks = this._callbacks.slice(0),
|
||||
contexts = this._contexts.slice(0);
|
||||
|
||||
for (var i = 0, len = callbacks.length; i < len; i++) {
|
||||
try {
|
||||
ret.push(callbacks[i].apply(contexts[i], args));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return !this._callbacks || this._callbacks.length === 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._callbacks = undefined;
|
||||
this._contexts = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface EmitterOptions {
|
||||
onFirstListenerAdd?: Function;
|
||||
onLastListenerRemove?: Function;
|
||||
}
|
||||
|
||||
export class Emitter<T> {
|
||||
|
||||
private static _noop = function () { };
|
||||
|
||||
private _event: Event<T>;
|
||||
private _callbacks: CallbackList;
|
||||
|
||||
constructor(private _options?: EmitterOptions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* For the public to allow to subscribe
|
||||
* to events from this Emitter
|
||||
*/
|
||||
get event(): Event<T> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
|
||||
if (!this._callbacks) {
|
||||
this._callbacks = new CallbackList();
|
||||
}
|
||||
if (this._options && this._options.onFirstListenerAdd && this._callbacks.isEmpty()) {
|
||||
this._options.onFirstListenerAdd(this);
|
||||
}
|
||||
this._callbacks.add(listener, thisArgs);
|
||||
|
||||
let result: Disposable;
|
||||
result = {
|
||||
dispose: () => {
|
||||
this._callbacks.remove(listener, thisArgs);
|
||||
result.dispose = Emitter._noop;
|
||||
if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) {
|
||||
this._options.onLastListenerRemove(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.push(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return this._event;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be kept private to fire an event to
|
||||
* subscribers
|
||||
*/
|
||||
fire(event: T): any {
|
||||
if (this._callbacks) {
|
||||
this._callbacks.invoke.call(this._callbacks, event);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._callbacks) {
|
||||
this._callbacks.dispose();
|
||||
this._callbacks = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
dataprotocol-node/jsonrpc/src/is.ts
Normal file
47
dataprotocol-node/jsonrpc/src/is.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
const toString = Object.prototype.toString;
|
||||
|
||||
export function defined(value: any): boolean {
|
||||
return typeof value !== 'undefined';
|
||||
}
|
||||
|
||||
export function undefined(value: any): boolean {
|
||||
return typeof value === 'undefined';
|
||||
}
|
||||
|
||||
export function nil(value: any): boolean {
|
||||
return value === null;
|
||||
}
|
||||
|
||||
export function boolean(value: any): value is boolean {
|
||||
return value === true || value === false;
|
||||
}
|
||||
|
||||
export function string(value: any): value is string {
|
||||
return toString.call(value) === '[object String]';
|
||||
}
|
||||
|
||||
export function number(value: any): value is number {
|
||||
return toString.call(value) === '[object Number]';
|
||||
}
|
||||
|
||||
export function error(value: any): value is Error {
|
||||
return toString.call(value) === '[object Error]';
|
||||
}
|
||||
|
||||
export function func(value: any): value is Function {
|
||||
return toString.call(value) === '[object Function]';
|
||||
}
|
||||
|
||||
export function array<T>(value: any): value is T[] {
|
||||
return Array.isArray(value);
|
||||
}
|
||||
|
||||
export function stringArray(value: any): value is string[] {
|
||||
return array(value) && (<any[]>value).every(elem => string(elem));
|
||||
}
|
||||
576
dataprotocol-node/jsonrpc/src/main.ts
Normal file
576
dataprotocol-node/jsonrpc/src/main.ts
Normal file
@@ -0,0 +1,576 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
import * as is from './is';
|
||||
|
||||
import {
|
||||
Message,
|
||||
RequestMessage, RequestType, isRequestMessage,
|
||||
ResponseMessage, isReponseMessage, ResponseError, ErrorCodes,
|
||||
NotificationMessage, NotificationType, isNotificationMessage
|
||||
} from './messages';
|
||||
|
||||
import { MessageReader, DataCallback, StreamMessageReader, IPCMessageReader } from './messageReader';
|
||||
import { MessageWriter, StreamMessageWriter, IPCMessageWriter } from './messageWriter';
|
||||
import { Disposable, Event, Emitter } from './events';
|
||||
import { CancellationTokenSource, CancellationToken } from './cancellation';
|
||||
|
||||
export {
|
||||
Message, ErrorCodes, ResponseError,
|
||||
RequestMessage, RequestType,
|
||||
NotificationMessage, NotificationType,
|
||||
MessageReader, DataCallback, StreamMessageReader, IPCMessageReader,
|
||||
MessageWriter, StreamMessageWriter, IPCMessageWriter,
|
||||
CancellationTokenSource, CancellationToken,
|
||||
Disposable, Event, Emitter
|
||||
}
|
||||
|
||||
interface CancelParams {
|
||||
/**
|
||||
* The request id to cancel.
|
||||
*/
|
||||
id: number | string;
|
||||
}
|
||||
|
||||
namespace CancelNotification {
|
||||
export const type: NotificationType<CancelParams> = { get method() { return '$/cancelRequest'; } };
|
||||
}
|
||||
|
||||
export interface RequestHandler<P, R, E> {
|
||||
(params: P, token: CancellationToken): R | ResponseError<E> | Thenable<R | ResponseError<E>>;
|
||||
}
|
||||
|
||||
export interface NotificationHandler<P> {
|
||||
(params: P): void;
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
error(message: string): void;
|
||||
warn(message: string): void;
|
||||
info(message: string): void;
|
||||
log(message: string): void;
|
||||
}
|
||||
|
||||
export enum Trace {
|
||||
Off, Messages, Verbose
|
||||
}
|
||||
|
||||
export type TraceValues = 'off' | 'messages' | 'verbose';
|
||||
export namespace Trace {
|
||||
export function fromString(value: string): Trace {
|
||||
value = value.toLowerCase();
|
||||
switch (value) {
|
||||
case 'off':
|
||||
return Trace.Off;
|
||||
case 'messages':
|
||||
return Trace.Messages;
|
||||
case 'verbose':
|
||||
return Trace.Verbose;
|
||||
default:
|
||||
return Trace.Off;
|
||||
}
|
||||
}
|
||||
|
||||
export function toString(value: Trace): TraceValues {
|
||||
switch (value) {
|
||||
case Trace.Off:
|
||||
return 'off';
|
||||
case Trace.Messages:
|
||||
return 'messages';
|
||||
case Trace.Verbose:
|
||||
return 'verbose';
|
||||
default:
|
||||
return 'off';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SetTraceParams {
|
||||
value: TraceValues;
|
||||
}
|
||||
|
||||
export namespace SetTraceNotification {
|
||||
export const type: NotificationType<SetTraceParams> = { get method() { return '$/setTraceNotification'; } };
|
||||
}
|
||||
|
||||
export interface LogTraceParams {
|
||||
message: string;
|
||||
verbose?: string;
|
||||
}
|
||||
|
||||
export namespace LogTraceNotification {
|
||||
export const type: NotificationType<LogTraceParams> = { get method() { return '$/logTraceNotification'; } };
|
||||
}
|
||||
|
||||
export interface Tracer {
|
||||
log(message: string, data?: string): void;
|
||||
}
|
||||
|
||||
export interface MessageConnection {
|
||||
sendRequest<P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken): Thenable<R>;
|
||||
onRequest<P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>): void;
|
||||
sendNotification<P>(type: NotificationType<P>, params?: P): void;
|
||||
onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): void;
|
||||
trace(value: Trace, tracer: Tracer, sendNotification?: boolean): void;
|
||||
onError: Event<[Error, Message, number]>;
|
||||
onClose: Event<void>;
|
||||
onUnhandledNotification: Event<NotificationMessage>;
|
||||
listen();
|
||||
onDispose: Event<void>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface ServerMessageConnection extends MessageConnection {
|
||||
}
|
||||
|
||||
export interface ClientMessageConnection extends MessageConnection {
|
||||
}
|
||||
|
||||
interface ResponsePromise {
|
||||
method: string;
|
||||
timerStart: number;
|
||||
resolve: (response) => void;
|
||||
reject: (error: any) => void
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
New = 1,
|
||||
Listening = 2,
|
||||
Closed = 3,
|
||||
Disposed = 4
|
||||
}
|
||||
|
||||
function createMessageConnection<T extends MessageConnection>(messageReader: MessageReader, messageWriter: MessageWriter, logger: Logger, client: boolean = false): T {
|
||||
let sequenceNumber = 0;
|
||||
const version: string = '2.0';
|
||||
|
||||
let requestHandlers: { [name: string]: RequestHandler<any, any, any> } = Object.create(null);
|
||||
let eventHandlers: { [name: string]: NotificationHandler<any> } = Object.create(null);
|
||||
|
||||
let responsePromises: { [name: string]: ResponsePromise } = Object.create(null);
|
||||
let requestTokens: { [id: string]: CancellationTokenSource } = Object.create(null);
|
||||
|
||||
let trace: Trace = Trace.Off;
|
||||
let tracer: Tracer;
|
||||
|
||||
let state: ConnectionState = ConnectionState.New;
|
||||
let errorEmitter: Emitter<[Error, Message, number]> = new Emitter<[Error, Message, number]>();
|
||||
let closeEmitter: Emitter<void> = new Emitter<void>();
|
||||
let unhandledNotificationEmitter: Emitter<NotificationMessage> = new Emitter<NotificationMessage>();
|
||||
let disposeEmitter: Emitter<void> = new Emitter<void>();
|
||||
|
||||
function isListening(): boolean {
|
||||
return state === ConnectionState.Listening;
|
||||
}
|
||||
|
||||
function isClosed(): boolean {
|
||||
return state === ConnectionState.Closed;
|
||||
}
|
||||
|
||||
function isDisposed(): boolean {
|
||||
return state === ConnectionState.Disposed;
|
||||
}
|
||||
|
||||
function closeHandler(): void {
|
||||
if (state === ConnectionState.New || state === ConnectionState.Listening) {
|
||||
state = ConnectionState.Closed;
|
||||
closeEmitter.fire(undefined);
|
||||
}
|
||||
// If the connection is disposed don't sent close events.
|
||||
};
|
||||
|
||||
function readErrorHandler(error: Error): void {
|
||||
errorEmitter.fire([error, undefined, undefined]);
|
||||
}
|
||||
|
||||
function writeErrorHandler(data: [Error, Message, number]): void {
|
||||
errorEmitter.fire(data);
|
||||
}
|
||||
|
||||
messageReader.onClose(closeHandler);
|
||||
messageReader.onError(readErrorHandler);
|
||||
|
||||
messageWriter.onClose(closeHandler);
|
||||
messageWriter.onError(writeErrorHandler);
|
||||
|
||||
function handleRequest(requestMessage: RequestMessage) {
|
||||
if (isDisposed()) {
|
||||
// we return here silently since we fired an event when the
|
||||
// connection got disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
function reply(resultOrError: any | ResponseError<any>): void {
|
||||
let message: ResponseMessage = {
|
||||
jsonrpc: version,
|
||||
id: requestMessage.id
|
||||
};
|
||||
if (resultOrError instanceof ResponseError) {
|
||||
message.error = (<ResponseError<any>>resultOrError).toJson();
|
||||
} else {
|
||||
message.result = is.undefined(resultOrError) ? null : resultOrError;
|
||||
}
|
||||
messageWriter.write(message);
|
||||
}
|
||||
function replyError(error: ResponseError<any>) {
|
||||
let message: ResponseMessage = {
|
||||
jsonrpc: version,
|
||||
id: requestMessage.id,
|
||||
error: error.toJson()
|
||||
};
|
||||
messageWriter.write(message);
|
||||
}
|
||||
function replySuccess(result: any) {
|
||||
// The JSON RPC defines that a response must either have a result or an error
|
||||
// So we can't treat undefined as a valid response result.
|
||||
if (is.undefined(result)) {
|
||||
result = null;
|
||||
}
|
||||
let message: ResponseMessage = {
|
||||
jsonrpc: version,
|
||||
id: requestMessage.id,
|
||||
result: result
|
||||
};
|
||||
messageWriter.write(message);
|
||||
}
|
||||
|
||||
let requestHandler = requestHandlers[requestMessage.method];
|
||||
if (requestHandler) {
|
||||
let cancellationSource = new CancellationTokenSource();
|
||||
let tokenKey = String(requestMessage.id);
|
||||
requestTokens[tokenKey] = cancellationSource;
|
||||
try {
|
||||
let handlerResult = requestHandler(requestMessage.params, cancellationSource.token);
|
||||
let promise = <Thenable<any | ResponseError<any>>>handlerResult;
|
||||
if (!handlerResult) {
|
||||
delete requestTokens[tokenKey];
|
||||
replySuccess(handlerResult);
|
||||
} else if (promise.then) {
|
||||
promise.then((resultOrError): any | ResponseError<any> => {
|
||||
delete requestTokens[tokenKey];
|
||||
reply(resultOrError);
|
||||
}, error => {
|
||||
delete requestTokens[tokenKey];
|
||||
if (error instanceof ResponseError) {
|
||||
replyError(<ResponseError<any>>error);
|
||||
} else if (error && is.string(error.message)) {
|
||||
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`));
|
||||
} else {
|
||||
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
delete requestTokens[tokenKey];
|
||||
reply(handlerResult);
|
||||
}
|
||||
} catch (error) {
|
||||
delete requestTokens[tokenKey];
|
||||
if (error instanceof ResponseError) {
|
||||
reply(<ResponseError<any>>error);
|
||||
} else if (error && is.string(error.message)) {
|
||||
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`));
|
||||
} else {
|
||||
replyError(new ResponseError<void>(ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
replyError(new ResponseError<void>(ErrorCodes.MethodNotFound, `Unhandled method ${requestMessage.method}`));
|
||||
}
|
||||
}
|
||||
|
||||
function handleResponse(responseMessage: ResponseMessage) {
|
||||
if (isDisposed()) {
|
||||
// See handle request.
|
||||
return;
|
||||
}
|
||||
|
||||
let key = String(responseMessage.id);
|
||||
let responsePromise = responsePromises[key];
|
||||
if (trace != Trace.Off && tracer) {
|
||||
traceResponse(responseMessage, responsePromise);
|
||||
}
|
||||
if (responsePromise) {
|
||||
delete responsePromises[key];
|
||||
try {
|
||||
if (is.defined(responseMessage.error)) {
|
||||
let error = responseMessage.error;
|
||||
responsePromise.reject(new ResponseError(error.code, error.message, error.data));
|
||||
} else if (is.defined(responseMessage.result)) {
|
||||
responsePromise.resolve(responseMessage.result);
|
||||
} else {
|
||||
throw new Error('Should never happen.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.message) {
|
||||
logger.error(`Response handler '${responsePromise.method}' failed with message: ${error.message}`);
|
||||
} else {
|
||||
logger.error(`Response handler '${responsePromise.method}' failed unexpectedly.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleNotification(message: NotificationMessage) {
|
||||
if (isDisposed()) {
|
||||
// See handle request.
|
||||
return;
|
||||
}
|
||||
let eventHandler: NotificationHandler<any>;
|
||||
if (message.method === CancelNotification.type.method) {
|
||||
eventHandler = (params: CancelParams) => {
|
||||
let id = params.id;
|
||||
let source = requestTokens[String(id)];
|
||||
if (source) {
|
||||
source.cancel();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eventHandler = eventHandlers[message.method];
|
||||
}
|
||||
if (eventHandler) {
|
||||
try {
|
||||
if (trace != Trace.Off && tracer) {
|
||||
traceReceivedNotification(message);
|
||||
}
|
||||
eventHandler(message.params);
|
||||
} catch (error) {
|
||||
if (error.message) {
|
||||
logger.error(`Notification handler '${message.method}' failed with message: ${error.message}`);
|
||||
} else {
|
||||
logger.error(`Notification handler '${message.method}' failed unexpectedly.`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unhandledNotificationEmitter.fire(message);
|
||||
}
|
||||
}
|
||||
|
||||
function handleInvalidMessage(message: Message) {
|
||||
if (!message) {
|
||||
logger.error('Received empty message.');
|
||||
return;
|
||||
}
|
||||
logger.error(`Received message which is neither a response nor a notification message:\n${JSON.stringify(message, null, 4)}`);
|
||||
// Test whether we find an id to reject the promise
|
||||
let responseMessage: ResponseMessage = message as ResponseMessage;
|
||||
if (is.string(responseMessage.id) || is.number(responseMessage.id)) {
|
||||
let key = String(responseMessage.id);
|
||||
let responseHandler = responsePromises[key];
|
||||
if (responseHandler) {
|
||||
responseHandler.reject(new Error('The received response has neither a result nor an error property.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function traceRequest(message: RequestMessage): void {
|
||||
let data: string = undefined;
|
||||
if (trace === Trace.Verbose && message.params) {
|
||||
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
||||
}
|
||||
tracer.log(`Sending request '${message.method} - (${message.id})'.`, data);
|
||||
}
|
||||
|
||||
function traceSendNotification(message: NotificationMessage): void {
|
||||
let data: string = undefined;
|
||||
if (trace === Trace.Verbose) {
|
||||
if (message.params) {
|
||||
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
||||
} else {
|
||||
data = 'No parameters provided.\n\n';
|
||||
}
|
||||
}
|
||||
tracer.log(`Sending notification '${message.method}'.`, data);
|
||||
}
|
||||
|
||||
function traceReceivedNotification(message: NotificationMessage): void {
|
||||
if (message.method === LogTraceNotification.type.method) {
|
||||
return;
|
||||
}
|
||||
let data: string = undefined;
|
||||
if (trace === Trace.Verbose) {
|
||||
if (message.params) {
|
||||
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
||||
} else {
|
||||
data = 'No parameters provided.\n\n';
|
||||
}
|
||||
}
|
||||
tracer.log(`Received notification '${message.method}'.`, data);
|
||||
}
|
||||
|
||||
function traceResponse(message: ResponseMessage, responsePromise: ResponsePromise): void {
|
||||
let data: string = undefined;
|
||||
if (trace === Trace.Verbose) {
|
||||
if (message.error && message.error.data) {
|
||||
data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
|
||||
} else {
|
||||
if (message.result) {
|
||||
data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
|
||||
} else if (is.undefined(message.error)) {
|
||||
data = 'No result returned.\n\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (responsePromise) {
|
||||
let error = message.error ? ` Request failed: ${message.error.message} (${message.error.code}).` : '';
|
||||
tracer.log(`Received response '${responsePromise.method} - (${message.id})' in ${Date.now() - responsePromise.timerStart}ms.${error}`, data);
|
||||
} else {
|
||||
tracer.log(`Received response ${message.id} without active response promise.`, data);
|
||||
}
|
||||
}
|
||||
|
||||
let callback: DataCallback = (message) => {
|
||||
if (isRequestMessage(message)) {
|
||||
handleRequest(message);
|
||||
} else if (isReponseMessage(message)) {
|
||||
handleResponse(message)
|
||||
} else if (isNotificationMessage(message)) {
|
||||
handleNotification(message);
|
||||
} else {
|
||||
handleInvalidMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
function throwIfClosedOrDisposed() {
|
||||
if (isClosed()) {
|
||||
throw new Error('Connection is closed.');
|
||||
}
|
||||
if (isDisposed()) {
|
||||
throw new Error('Connection is disposed.');
|
||||
}
|
||||
}
|
||||
|
||||
function throwIfListening() {
|
||||
if (isListening()) {
|
||||
throw new Error('Connection is already listening');
|
||||
}
|
||||
}
|
||||
|
||||
let connection: MessageConnection = {
|
||||
sendNotification: <P>(type: NotificationType<P>, params): void => {
|
||||
throwIfClosedOrDisposed();
|
||||
|
||||
let notificatioMessage: NotificationMessage = {
|
||||
jsonrpc: version,
|
||||
method: type.method,
|
||||
params: params
|
||||
}
|
||||
if (trace != Trace.Off && tracer) {
|
||||
traceSendNotification(notificatioMessage);
|
||||
}
|
||||
messageWriter.write(notificatioMessage);
|
||||
},
|
||||
onNotification: <P>(type: NotificationType<P>, handler: NotificationHandler<P>) => {
|
||||
throwIfClosedOrDisposed();
|
||||
|
||||
eventHandlers[type.method] = handler;
|
||||
},
|
||||
sendRequest: <P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken) => {
|
||||
throwIfClosedOrDisposed();
|
||||
|
||||
let id = sequenceNumber++;
|
||||
let result = new Promise<R | ResponseError<E>>((resolve, reject) => {
|
||||
let requestMessage: RequestMessage = {
|
||||
jsonrpc: version,
|
||||
id: id,
|
||||
method: type.method,
|
||||
params: params
|
||||
}
|
||||
let responsePromise: ResponsePromise = { method: type.method, timerStart: Date.now(), resolve, reject };
|
||||
if (trace != Trace.Off && tracer) {
|
||||
traceRequest(requestMessage);
|
||||
}
|
||||
try {
|
||||
messageWriter.write(requestMessage);
|
||||
} catch (e) {
|
||||
// Writing the message failed. So we need to reject the promise.
|
||||
responsePromise.reject(new ResponseError<void>(ErrorCodes.MessageWriteError, e.message ? e.message : 'Unknown reason'));
|
||||
responsePromise = null;
|
||||
}
|
||||
if (responsePromise) {
|
||||
responsePromises[String(id)] = responsePromise;
|
||||
}
|
||||
});
|
||||
if (token) {
|
||||
token.onCancellationRequested((event) => {
|
||||
connection.sendNotification(CancelNotification.type, { id });
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
onRequest: <P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>) => {
|
||||
throwIfClosedOrDisposed();
|
||||
|
||||
requestHandlers[type.method] = handler;
|
||||
},
|
||||
trace: (_value: Trace, _tracer: Tracer, sendNotification: boolean = false) => {
|
||||
trace = _value;
|
||||
if (trace === Trace.Off) {
|
||||
tracer = null;
|
||||
} else {
|
||||
tracer = _tracer;
|
||||
}
|
||||
if (sendNotification && !isClosed() && !isDisposed()) {
|
||||
connection.sendNotification(SetTraceNotification.type, { value: Trace.toString(_value) });
|
||||
}
|
||||
},
|
||||
onError: errorEmitter.event,
|
||||
onClose: closeEmitter.event,
|
||||
onUnhandledNotification: unhandledNotificationEmitter.event,
|
||||
onDispose: disposeEmitter.event,
|
||||
dispose: () => {
|
||||
if (isDisposed()) {
|
||||
return;
|
||||
}
|
||||
state = ConnectionState.Disposed;
|
||||
disposeEmitter.fire(undefined);
|
||||
let error = new Error('Connection got disposed.');
|
||||
Object.keys(responsePromises).forEach((key) => {
|
||||
responsePromises[key].reject(error);
|
||||
});
|
||||
responsePromises = Object.create(null);
|
||||
requestTokens = Object.create(null);
|
||||
},
|
||||
listen: () => {
|
||||
throwIfClosedOrDisposed();
|
||||
throwIfListening();
|
||||
|
||||
state = ConnectionState.Listening;
|
||||
messageReader.listen(callback);
|
||||
}
|
||||
};
|
||||
|
||||
connection.onNotification(LogTraceNotification.type, (params) => {
|
||||
if (trace === Trace.Off) {
|
||||
return;
|
||||
}
|
||||
tracer.log(params.message, trace === Trace.Verbose ? params.verbose : undefined);
|
||||
});
|
||||
return connection as T;
|
||||
}
|
||||
|
||||
function isMessageReader(value: any): value is MessageReader {
|
||||
return is.defined(value.listen) && is.undefined(value.read);
|
||||
}
|
||||
|
||||
function isMessageWriter(value: any): value is MessageWriter {
|
||||
return is.defined(value.write) && is.undefined(value.end);
|
||||
}
|
||||
|
||||
export function createServerMessageConnection(reader: MessageReader, writer: MessageWriter, logger: Logger): ServerMessageConnection;
|
||||
export function createServerMessageConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, logger: Logger): ServerMessageConnection;
|
||||
export function createServerMessageConnection(input: MessageReader | NodeJS.ReadableStream, output: MessageWriter | NodeJS.WritableStream, logger: Logger): ServerMessageConnection {
|
||||
let reader = isMessageReader(input) ? input : new StreamMessageReader(input);
|
||||
let writer = isMessageWriter(output) ? output : new StreamMessageWriter(output);
|
||||
return createMessageConnection<ServerMessageConnection>(reader, writer, logger);
|
||||
}
|
||||
|
||||
export function createClientMessageConnection(reader: MessageReader, writer: MessageWriter, logger: Logger): ClientMessageConnection;
|
||||
export function createClientMessageConnection(inputStream: NodeJS.ReadableStream, outputStream: NodeJS.WritableStream, logger: Logger): ClientMessageConnection;
|
||||
export function createClientMessageConnection(input: MessageReader | NodeJS.ReadableStream, output: MessageWriter | NodeJS.WritableStream, logger: Logger): ClientMessageConnection {
|
||||
let reader = isMessageReader(input) ? input : new StreamMessageReader(input);
|
||||
let writer = isMessageWriter(output) ? output : new StreamMessageWriter(output);
|
||||
return createMessageConnection<ClientMessageConnection>(reader, writer, logger, true);
|
||||
}
|
||||
265
dataprotocol-node/jsonrpc/src/messageReader.ts
Normal file
265
dataprotocol-node/jsonrpc/src/messageReader.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
|
||||
import { Message } from './messages';
|
||||
import { Event, Emitter } from './events';
|
||||
import * as is from './is';
|
||||
|
||||
let DefaultSize: number = 8192;
|
||||
let CR: number = new Buffer('\r', 'ascii')[0];
|
||||
let LF: number = new Buffer('\n', 'ascii')[0];
|
||||
let CRLF: string = '\r\n';
|
||||
|
||||
class MessageBuffer {
|
||||
|
||||
private encoding: string;
|
||||
private index: number;
|
||||
private buffer: Buffer;
|
||||
|
||||
constructor(encoding: string = 'utf-8') {
|
||||
this.encoding = encoding;
|
||||
this.index = 0;
|
||||
this.buffer = new Buffer(DefaultSize);
|
||||
}
|
||||
|
||||
public append(chunk: Buffer | String): void {
|
||||
var toAppend: Buffer = <Buffer>chunk;
|
||||
if (typeof (chunk) == 'string') {
|
||||
var str = <string>chunk;
|
||||
toAppend = new Buffer(str.length);
|
||||
toAppend.write(str, 0, str.length, this.encoding);
|
||||
}
|
||||
if (this.buffer.length - this.index >= toAppend.length) {
|
||||
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
|
||||
} else {
|
||||
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
|
||||
if (this.index === 0) {
|
||||
this.buffer = new Buffer(newSize);
|
||||
toAppend.copy(this.buffer, 0, 0, toAppend.length);
|
||||
} else {
|
||||
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
|
||||
}
|
||||
}
|
||||
this.index += toAppend.length;
|
||||
}
|
||||
|
||||
public tryReadHeaders(): { [key: string]: string; } {
|
||||
let result: { [key: string]: string; } = undefined;
|
||||
let current = 0;
|
||||
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
|
||||
current++;
|
||||
}
|
||||
// No header / body separator found (e.g CRLFCRLF)
|
||||
if (current + 3 >= this.index) {
|
||||
return result;
|
||||
}
|
||||
result = Object.create(null);
|
||||
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
|
||||
headers.forEach((header) => {
|
||||
let index: number = header.indexOf(':');
|
||||
if (index === -1) {
|
||||
throw new Error('Message header must separate key and value using :');
|
||||
}
|
||||
let key = header.substr(0, index);
|
||||
let value = header.substr(index + 1).trim();
|
||||
result[key] = value;
|
||||
})
|
||||
|
||||
let nextStart = current + 4;
|
||||
this.buffer = this.buffer.slice(nextStart);
|
||||
this.index = this.index - nextStart;
|
||||
return result;
|
||||
}
|
||||
|
||||
public tryReadContent(length: number): string {
|
||||
if (this.index < length) {
|
||||
return null;
|
||||
}
|
||||
let result = this.buffer.toString(this.encoding, 0, length);
|
||||
let nextStart = length;
|
||||
this.buffer.copy(this.buffer, 0, nextStart);
|
||||
this.index = this.index - nextStart;
|
||||
return result;
|
||||
}
|
||||
|
||||
public get numberOfBytes(): number {
|
||||
return this.index;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DataCallback {
|
||||
(data: Message): void;
|
||||
}
|
||||
|
||||
export interface PartialMessageInfo {
|
||||
messageToken: number;
|
||||
waitingTime: number;
|
||||
}
|
||||
|
||||
export interface MessageReader {
|
||||
onError: Event<Error>;
|
||||
onClose: Event<void>;
|
||||
onPartialMessage: Event<PartialMessageInfo>;
|
||||
listen(callback: DataCallback): void;
|
||||
}
|
||||
|
||||
export abstract class AbstractMessageReader {
|
||||
|
||||
private errorEmitter: Emitter<Error>;
|
||||
private closeEmitter: Emitter<void>;
|
||||
|
||||
private partialMessageEmitter: Emitter<PartialMessageInfo>;
|
||||
|
||||
constructor() {
|
||||
this.errorEmitter = new Emitter<Error>();
|
||||
this.closeEmitter = new Emitter<void>();
|
||||
this.partialMessageEmitter = new Emitter<PartialMessageInfo>();
|
||||
}
|
||||
|
||||
public get onError(): Event<Error> {
|
||||
return this.errorEmitter.event;
|
||||
}
|
||||
|
||||
protected fireError(error: any): void {
|
||||
this.errorEmitter.fire(this.asError(error));
|
||||
}
|
||||
|
||||
public get onClose(): Event<void> {
|
||||
return this.closeEmitter.event;
|
||||
}
|
||||
|
||||
protected fireClose(): void {
|
||||
this.closeEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
public get onPartialMessage(): Event<PartialMessageInfo> {
|
||||
return this.partialMessageEmitter.event;
|
||||
}
|
||||
|
||||
protected firePartialMessage(info: PartialMessageInfo): void {
|
||||
this.partialMessageEmitter.fire(info);
|
||||
}
|
||||
|
||||
private asError(error: any): Error {
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
} else {
|
||||
return new Error(`Reader recevied error. Reason: ${is.string(error.message) ? error.message : 'unknown'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamMessageReader extends AbstractMessageReader implements MessageReader {
|
||||
|
||||
private readable: NodeJS.ReadableStream;
|
||||
private callback: DataCallback;
|
||||
private buffer: MessageBuffer;
|
||||
private nextMessageLength: number;
|
||||
private messageToken: number;
|
||||
private partialMessageTimer: NodeJS.Timer;
|
||||
private _partialMessageTimeout: number;
|
||||
|
||||
public constructor(readable: NodeJS.ReadableStream, encoding: string = 'utf-8') {
|
||||
super();
|
||||
this.readable = readable;
|
||||
this.buffer = new MessageBuffer(encoding);
|
||||
this._partialMessageTimeout = 10000;
|
||||
}
|
||||
|
||||
public set partialMessageTimeout(timeout: number) {
|
||||
this._partialMessageTimeout = timeout;
|
||||
}
|
||||
|
||||
public get partialMessageTimeout(): number {
|
||||
return this._partialMessageTimeout;
|
||||
}
|
||||
|
||||
public listen(callback: DataCallback): void {
|
||||
this.nextMessageLength = -1;
|
||||
this.messageToken = 0;
|
||||
this.partialMessageTimer = undefined;
|
||||
this.callback = callback;
|
||||
this.readable.on('data', (data: Buffer) => {
|
||||
this.onData(data);
|
||||
});
|
||||
this.readable.on('error', (error: any) => this.fireError(error));
|
||||
this.readable.on('close', () => this.fireClose());
|
||||
}
|
||||
|
||||
private onData(data: Buffer | String): void {
|
||||
this.buffer.append(data);
|
||||
while (true) {
|
||||
if (this.nextMessageLength === -1) {
|
||||
let headers = this.buffer.tryReadHeaders();
|
||||
if (!headers) {
|
||||
return;
|
||||
}
|
||||
let contentLength = headers['Content-Length'];
|
||||
if (!contentLength) {
|
||||
throw new Error('Header must provide a Content-Length property.');
|
||||
}
|
||||
let length = parseInt(contentLength);
|
||||
if (isNaN(length)) {
|
||||
throw new Error('Content-Length value must be a number.');
|
||||
}
|
||||
this.nextMessageLength = length;
|
||||
}
|
||||
var msg = this.buffer.tryReadContent(this.nextMessageLength);
|
||||
if (msg === null) {
|
||||
/** We haven't recevied the full message yet. */
|
||||
this.setPartialMessageTimer();
|
||||
return;
|
||||
}
|
||||
this.clearPartialMessageTimer();
|
||||
this.nextMessageLength = -1;
|
||||
this.messageToken++;
|
||||
var json = JSON.parse(msg);
|
||||
this.callback(json);
|
||||
}
|
||||
}
|
||||
|
||||
private clearPartialMessageTimer(): void {
|
||||
if (this.partialMessageTimer) {
|
||||
clearTimeout(this.partialMessageTimer);
|
||||
this.partialMessageTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private setPartialMessageTimer(): void {
|
||||
this.clearPartialMessageTimer();
|
||||
if (this._partialMessageTimeout <= 0) {
|
||||
return;
|
||||
}
|
||||
this.partialMessageTimer = setTimeout((token, timeout) => {
|
||||
this.partialMessageTimer = undefined;
|
||||
if (token === this.messageToken) {
|
||||
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
|
||||
this.setPartialMessageTimer();
|
||||
}
|
||||
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
export class IPCMessageReader extends AbstractMessageReader implements MessageReader {
|
||||
|
||||
private process: NodeJS.Process | ChildProcess;
|
||||
|
||||
public constructor(process: NodeJS.Process | ChildProcess) {
|
||||
super();
|
||||
this.process = process;
|
||||
|
||||
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
|
||||
eventEmitter.on('error', (error: any) => this.fireError(error));
|
||||
eventEmitter.on('close', () => this.fireClose());
|
||||
}
|
||||
|
||||
public listen(callback: DataCallback): void {
|
||||
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
|
||||
eventEmitter.on('message', callback);
|
||||
}
|
||||
}
|
||||
118
dataprotocol-node/jsonrpc/src/messageWriter.ts
Normal file
118
dataprotocol-node/jsonrpc/src/messageWriter.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
|
||||
import { Message } from './messages';
|
||||
import { Event, Emitter } from './events';
|
||||
import * as is from './is';
|
||||
|
||||
let ContentLength: string = 'Content-Length: ';
|
||||
let CRLF = '\r\n';
|
||||
|
||||
export interface MessageWriter {
|
||||
onError: Event<[Error, Message, number]>;
|
||||
onClose: Event<void>;
|
||||
write(msg: Message): void;
|
||||
}
|
||||
|
||||
export abstract class AbstractMessageWriter {
|
||||
|
||||
private errorEmitter: Emitter<[Error, Message, number]>;
|
||||
private closeEmitter: Emitter<void>;
|
||||
|
||||
constructor() {
|
||||
this.errorEmitter = new Emitter<[Error, Message, number]>();
|
||||
this.closeEmitter = new Emitter<void>();
|
||||
}
|
||||
|
||||
public get onError(): Event<[Error, Message, number]> {
|
||||
return this.errorEmitter.event;
|
||||
}
|
||||
|
||||
protected fireError(error: any, message?: Message, count?: number): void {
|
||||
this.errorEmitter.fire([this.asError(error), message, count]);
|
||||
}
|
||||
|
||||
public get onClose(): Event<void> {
|
||||
return this.closeEmitter.event;
|
||||
}
|
||||
|
||||
protected fireClose(): void {
|
||||
this.closeEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
private asError(error: any): Error {
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
} else {
|
||||
return new Error(`Writer recevied error. Reason: ${is.string(error.message) ? error.message : 'unknown'}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamMessageWriter extends AbstractMessageWriter implements MessageWriter {
|
||||
|
||||
private writable: NodeJS.WritableStream;
|
||||
private encoding: string;
|
||||
private errorCount: number;
|
||||
|
||||
public constructor(writable: NodeJS.WritableStream, encoding: string = 'utf8') {
|
||||
super();
|
||||
this.writable = writable;
|
||||
this.encoding = encoding;
|
||||
this.errorCount = 0;
|
||||
this.writable.on('error', (error) => this.fireError(error));
|
||||
this.writable.on('close', () => this.fireClose());
|
||||
}
|
||||
|
||||
public write(msg: Message): void {
|
||||
let json = JSON.stringify(msg);
|
||||
let contentLength = Buffer.byteLength(json, this.encoding);
|
||||
|
||||
let headers: string[] = [
|
||||
ContentLength, contentLength.toString(), CRLF,
|
||||
CRLF
|
||||
];
|
||||
try {
|
||||
// Header must be written in ASCII encoding
|
||||
this.writable.write(headers.join(''), 'ascii');
|
||||
|
||||
// Now write the content. This can be written in any encoding
|
||||
this.writable.write(json, this.encoding);
|
||||
this.errorCount = 0;
|
||||
} catch (error) {
|
||||
this.errorCount++;
|
||||
this.fireError(error, msg, this.errorCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class IPCMessageWriter extends AbstractMessageWriter implements MessageWriter {
|
||||
|
||||
private process: NodeJS.Process | ChildProcess;
|
||||
private errorCount: number;
|
||||
|
||||
public constructor(process: NodeJS.Process | ChildProcess) {
|
||||
super();
|
||||
this.process = process;
|
||||
this.errorCount = 0;
|
||||
|
||||
let eventEmitter: NodeJS.EventEmitter = <NodeJS.EventEmitter>this.process;
|
||||
eventEmitter.on('error', (error) => this.fireError(error));
|
||||
eventEmitter.on('close', () => this.fireClose);
|
||||
}
|
||||
|
||||
public write(msg: Message): void {
|
||||
try {
|
||||
this.process.send(msg);
|
||||
this.errorCount = 0;
|
||||
} catch (error) {
|
||||
this.errorCount++;
|
||||
this.fireError(error, msg, this.errorCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
175
dataprotocol-node/jsonrpc/src/messages.ts
Normal file
175
dataprotocol-node/jsonrpc/src/messages.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
import * as is from './is';
|
||||
|
||||
/**
|
||||
* A language server message
|
||||
*/
|
||||
export interface Message {
|
||||
jsonrpc: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request message
|
||||
*/
|
||||
export interface RequestMessage extends Message {
|
||||
|
||||
/**
|
||||
* The request id.
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* The method to be invoked.
|
||||
*/
|
||||
method: string;
|
||||
|
||||
/**
|
||||
* The method's params.
|
||||
*/
|
||||
params?: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined error codes.
|
||||
*/
|
||||
export namespace ErrorCodes {
|
||||
// Defined by JSON RPC
|
||||
export const ParseError: number = -32700;
|
||||
export const InvalidRequest: number = -32600;
|
||||
export const MethodNotFound: number = -32601;
|
||||
export const InvalidParams: number = -32602;
|
||||
export const InternalError: number = -32603;
|
||||
export const serverErrorStart: number = -32099
|
||||
export const serverErrorEnd: number = -32000;
|
||||
|
||||
// Defined by VSCode.
|
||||
export const MessageWriteError: number = 1;
|
||||
export const MessageReadError: number = 2;
|
||||
}
|
||||
|
||||
export interface ResponseErrorLiteral<D> {
|
||||
/**
|
||||
* A number indicating the error type that occured.
|
||||
*/
|
||||
code: number;
|
||||
|
||||
/**
|
||||
* A string providing a short decription of the error.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* A Primitive or Structured value that contains additional
|
||||
* information about the error. Can be omitted.
|
||||
*/
|
||||
data?: D;
|
||||
}
|
||||
|
||||
/**
|
||||
* A error object return in a response in case a request
|
||||
* has failed.
|
||||
*/
|
||||
export class ResponseError<D> extends Error {
|
||||
|
||||
public code: number;
|
||||
|
||||
public message: string;
|
||||
|
||||
public data: D;
|
||||
|
||||
constructor(code: number, message: string, data?: D) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
if (is.defined(data)) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public toJson(): ResponseErrorLiteral<D> {
|
||||
let result: ResponseErrorLiteral<D> = {
|
||||
code: this.code,
|
||||
message: this.message
|
||||
};
|
||||
if (is.defined(this.data)) {
|
||||
result.data = this.data
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A response message.
|
||||
*/
|
||||
export interface ResponseMessage extends Message {
|
||||
/**
|
||||
* The request id.
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* The result of a request. This can be omitted in
|
||||
* the case of an error.
|
||||
*/
|
||||
result?: any;
|
||||
|
||||
/**
|
||||
* The error object in case a request fails.
|
||||
*/
|
||||
error?: ResponseErrorLiteral<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface to type the request parameter / response pair
|
||||
*/
|
||||
export interface RequestType<P, R, E> {
|
||||
method: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Message
|
||||
*/
|
||||
export interface NotificationMessage extends Message {
|
||||
/**
|
||||
* The method to be invoked.
|
||||
*/
|
||||
method: string;
|
||||
|
||||
/**
|
||||
* The notification's params.
|
||||
*/
|
||||
params?: any
|
||||
}
|
||||
|
||||
export interface NotificationType<P> {
|
||||
method: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given message is a request message
|
||||
*/
|
||||
export function isRequestMessage(message: Message): message is RequestMessage {
|
||||
let candidate = <RequestMessage>message;
|
||||
return candidate && is.string(candidate.method) && (is.string(candidate.id) || is.number(candidate.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given message is a notification message
|
||||
*/
|
||||
export function isNotificationMessage(message: Message): message is NotificationMessage {
|
||||
let candidate = <NotificationMessage>message;
|
||||
return candidate && is.string(candidate.method) && is.undefined((<any>message).id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given message is a response message
|
||||
*/
|
||||
export function isReponseMessage(message: Message): message is ResponseMessage {
|
||||
let candidate = <ResponseMessage>message;
|
||||
return candidate && (is.defined(candidate.result) || is.defined(candidate.error)) && (is.string(candidate.id) || is.number(candidate.id));
|
||||
}
|
||||
11
dataprotocol-node/jsonrpc/src/tsconfig.json
Normal file
11
dataprotocol-node/jsonrpc/src/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"outDir": "../lib"
|
||||
}
|
||||
}
|
||||
112
dataprotocol-node/jsonrpc/src/typings/promise.d.ts
vendored
Normal file
112
dataprotocol-node/jsonrpc/src/typings/promise.d.ts
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
/**
|
||||
* The Thenable (E.g. PromiseLike) and Promise declarions are taken from TypeScript's
|
||||
* lib.core.es6.d.ts file. See above Copyright notice.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
|
||||
* and others. This API makes no assumption about what promise libary is being used which
|
||||
* enables reusing existing code without migrating to a specific promise implementation. Still,
|
||||
* we recommand the use of native promises which are available in VS Code.
|
||||
*/
|
||||
interface Thenable<R> {
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of which ever callback is executed.
|
||||
*/
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the completion of an asynchronous operation
|
||||
*/
|
||||
interface Promise<T> extends Thenable<T> {
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of which ever callback is executed.
|
||||
*/
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Promise<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
|
||||
|
||||
/**
|
||||
* Attaches a callback for only the rejection of the Promise.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of the callback.
|
||||
*/
|
||||
catch(onrejected?: (reason: any) => T | Thenable<T>): Promise<T>;
|
||||
}
|
||||
|
||||
interface PromiseConstructor {
|
||||
/**
|
||||
* Creates a new Promise.
|
||||
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
|
||||
* a resolve callback used resolve the promise with a value or the result of another promise,
|
||||
* and a reject callback used to reject the promise with a provided reason or error.
|
||||
*/
|
||||
new <T>(executor: (resolve: (value?: T | Thenable<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
|
||||
|
||||
/**
|
||||
* Creates a Promise that is resolved with an array of results when all of the provided Promises
|
||||
* resolve, or rejected when any Promise is rejected.
|
||||
* @param values An array of Promises.
|
||||
* @returns A new Promise.
|
||||
*/
|
||||
all<T>(values: Array<T | Thenable<T>>): Promise<T[]>;
|
||||
|
||||
/**
|
||||
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
|
||||
* or rejected.
|
||||
* @param values An array of Promises.
|
||||
* @returns A new Promise.
|
||||
*/
|
||||
race<T>(values: Array<T | Thenable<T>>): Promise<T>;
|
||||
|
||||
/**
|
||||
* Creates a new rejected promise for the provided reason.
|
||||
* @param reason The reason the promise was rejected.
|
||||
* @returns A new rejected Promise.
|
||||
*/
|
||||
reject(reason: any): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates a new rejected promise for the provided reason.
|
||||
* @param reason The reason the promise was rejected.
|
||||
* @returns A new rejected Promise.
|
||||
*/
|
||||
reject<T>(reason: any): Promise<T>;
|
||||
|
||||
/**
|
||||
* Creates a new resolved promise for the provided value.
|
||||
* @param value A promise.
|
||||
* @returns A promise whose internal state matches the provided promise.
|
||||
*/
|
||||
resolve<T>(value: T | Thenable<T>): Promise<T>;
|
||||
|
||||
/**
|
||||
* Creates a new resolved promise .
|
||||
* @returns A resolved promise.
|
||||
*/
|
||||
resolve(): Promise<void>;
|
||||
}
|
||||
|
||||
declare var Promise: PromiseConstructor;
|
||||
Reference in New Issue
Block a user