errors.ts 11.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import nls = require('vs/nls');
import objects = require('vs/base/common/objects');
import platform = require('vs/base/common/platform');
import types = require('vs/base/common/types');
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings');
import {IAction} from 'vs/base/common/actions';
import {IXHRResponse} from 'vs/base/common/http';
import Severity from 'vs/base/common/severity';

export interface ErrorListenerCallback {
	(error: any): void;
}

export interface ErrorListenerUnbind {
	(): void;
}

25
// Avoid circular dependency on EventEmitter by implementing a subset of the interface.
E
Erich Gamma 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
export class ErrorHandler {
	private unexpectedErrorHandler: (e: any) => void;
	private listeners: ErrorListenerCallback[];

	constructor() {

		this.listeners = [];

		this.unexpectedErrorHandler = function(e: any) {
			platform.setTimeout(() => {
				if (e.stack) {
					throw new Error(e.message + '\n\n' + e.stack);
				}

				throw e;
			}, 0);
		};
	}

	public addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
		this.listeners.push(listener);

		return () => {
			this._removeListener(listener);
		};
	}

	private emit(e: any): void {
		this.listeners.forEach((listener) => {
			listener(e);
		});
	}

	private _removeListener(listener: ErrorListenerCallback): void {
		this.listeners.splice(this.listeners.indexOf(listener), 1);
	}

	public setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
		this.unexpectedErrorHandler = newUnexpectedErrorHandler;
	}

	public getUnexpectedErrorHandler(): (e: any) => void {
		return this.unexpectedErrorHandler;
	}

	public onUnexpectedError(e: any): void {
		this.unexpectedErrorHandler(e);
		this.emit(e);
	}
}

B
Benjamin Pasero 已提交
77
export let errorHandler = new ErrorHandler();
E
Erich Gamma 已提交
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
	errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler);
}

export function onUnexpectedError(e: any): void {

	// ignore errors from cancelled promises
	if (!isPromiseCanceledError(e)) {
		errorHandler.onUnexpectedError(e);
	}
}

export interface IConnectionErrorData {
	status: number;
	statusText?: string;
	responseText?: string;
}

export function transformErrorForSerialization(error: any): any {
98 99 100 101 102 103 104 105 106
	if (error instanceof Error) {
		let {name, message} = error;
		let stack: string = (<any>error).stacktrace || (<any>error).stack
		return {
			$isError: true,
			name,
			message,
			stack
		};
E
Erich Gamma 已提交
107
	}
108 109 110

	// return as is
	return error;
E
Erich Gamma 已提交
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
}

/**
 * The base class for all connection errors originating from XHR requests.
 */
export class ConnectionError implements Error {
	public status: number;
	public statusText: string;
	public responseText: string;
	public errorMessage: string;
	public errorCode: string;
	public errorObject: any;
	public name: string;

	constructor(mixin: IConnectionErrorData);
	constructor(request: IXHRResponse);
	constructor(arg: any) {
		this.status = arg.status;
		this.statusText = arg.statusText;
		this.name = 'ConnectionError';

		try {
			this.responseText = arg.responseText;
		} catch (e) {
			this.responseText = '';
		}

		this.errorMessage = null;
		this.errorCode = null;
		this.errorObject = null;

		if (this.responseText) {
			try {
B
Benjamin Pasero 已提交
144
				let errorObj = JSON.parse(this.responseText);
E
Erich Gamma 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
				this.errorMessage = errorObj.message;
				this.errorCode = errorObj.code;
				this.errorObject = errorObj;
			} catch (error) {
				// Ignore
			}
		}
	}

	public get message(): string {
		return this.connectionErrorToMessage(this, false);
	}

	public get verboseMessage(): string {
		return this.connectionErrorToMessage(this, true);
	}

	private connectionErrorDetailsToMessage(error: ConnectionError, verbose: boolean): string {
B
Benjamin Pasero 已提交
163 164
		let errorCode = error.errorCode;
		let errorMessage = error.errorMessage;
E
Erich Gamma 已提交
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

		if (errorCode !== null && errorMessage !== null) {
			return nls.localize(
				{
					key: 'message',
					comment: [
						'{0} represents the error message',
						'{1} represents the error code'
					]
				},
				"{0}. Error code: {1}",
				strings.rtrim(errorMessage, '.'), errorCode);
		}

		if (errorMessage !== null) {
			return errorMessage;
		}

		if (verbose && error.responseText !== null) {
			return error.responseText;
		}

		return null;
	}

	private connectionErrorToMessage(error: ConnectionError, verbose: boolean): string {
B
Benjamin Pasero 已提交
191
		let details = this.connectionErrorDetailsToMessage(error, verbose);
E
Erich Gamma 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

		// Status Code based Error
		if (error.status === 401) {
			if (details !== null) {
				return nls.localize(
					{
						key: 'error.permission.verbose',
						comment: [
							'{0} represents detailed information why the permission got denied'
						]
					},
					"Permission Denied (HTTP {0})",
					details);
			}

			return nls.localize('error.permission', "Permission Denied");
		}

		// Return error details if present
		if (details) {
			return details;
		}

		// Fallback to HTTP Status and Code
		if (error.status > 0 && error.statusText !== null) {
			if (verbose && error.responseText !== null && error.responseText.length > 0) {
				return nls.localize('error.http.verbose', "{0} (HTTP {1}: {2})", error.statusText, error.status, error.responseText);
			}

			return nls.localize('error.http', "{0} (HTTP {1})", error.statusText, error.status);
		}

		// Finally its an Unknown Connection Error
		if (verbose && error.responseText !== null && error.responseText.length > 0) {
			return nls.localize('error.connection.unknown.verbose', "Unknown Connection Error ({0})", error.responseText);
		}

		return nls.localize('error.connection.unknown', "An unknown connection error occurred. Either you are no longer connected to the internet or the server you are connected to is offline.");
	}
}

// Bug: Can not subclass a JS Type. Do it manually (as done in WinJS.Class.derive)
objects.derive(Error, ConnectionError);

236
function xhrToErrorMessage(xhr: IConnectionErrorData, verbose: boolean): string {
B
Benjamin Pasero 已提交
237
	let ce = new ConnectionError(xhr);
E
Erich Gamma 已提交
238 239 240 241 242 243 244
	if (verbose) {
		return ce.verboseMessage;
	} else {
		return ce.message;
	}
}

245
function exceptionToErrorMessage(exception: any, verbose: boolean): string {
E
Erich Gamma 已提交
246
	if (exception.message) {
247 248 249 250 251
		if (verbose && (exception.stack || exception.stacktrace)) {
			return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), exception.stack || exception.stacktrace);
		}

		return detectSystemErrorMessage(exception);
E
Erich Gamma 已提交
252 253 254 255 256
	}

	return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
}

257 258 259 260 261 262 263 264 265 266
function detectSystemErrorMessage(exception: any): string {

	// See https://nodejs.org/api/errors.html#errors_class_system_error
	if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') {
		return nls.localize('nodeExceptionMessage', "A system error occured ({0})", exception.message);
	}

	return exception.message;
}

E
Erich Gamma 已提交
267 268 269
/**
 * Tries to generate a human readable error message out of the error. If the verbose parameter
 * is set to true, the error message will include stacktrace details if provided.
270
 * @returns A string containing the error message.
E
Erich Gamma 已提交
271 272 273 274 275 276 277
 */
export function toErrorMessage(error: any = null, verbose: boolean = false): string {
	if (!error) {
		return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
	}

	if (Array.isArray(error)) {
B
Benjamin Pasero 已提交
278 279
		let errors: any[] = arrays.coalesce(error);
		let msg = toErrorMessage(errors[0], verbose);
E
Erich Gamma 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292

		if (errors.length > 1) {
			return nls.localize('error.moreErrors', "{0} ({1} errors in total)", msg, errors.length);
		}

		return msg;
	}

	if (types.isString(error)) {
		return error;
	}

	if (!types.isUndefinedOrNull(error.status)) {
293
		return xhrToErrorMessage(error, verbose);
E
Erich Gamma 已提交
294 295 296
	}

	if (error.detail) {
B
Benjamin Pasero 已提交
297
		let detail = error.detail;
E
Erich Gamma 已提交
298 299 300

		if (detail.error) {
			if (detail.error && !types.isUndefinedOrNull(detail.error.status)) {
301
				return xhrToErrorMessage(detail.error, verbose);
E
Erich Gamma 已提交
302 303 304
			}

			if (types.isArray(detail.error)) {
B
Benjamin Pasero 已提交
305
				for (let i = 0; i < detail.error.length; i++) {
E
Erich Gamma 已提交
306
					if (detail.error[i] && !types.isUndefinedOrNull(detail.error[i].status)) {
307
						return xhrToErrorMessage(detail.error[i], verbose);
E
Erich Gamma 已提交
308 309 310 311 312
					}
				}
			}

			else {
313
				return exceptionToErrorMessage(detail.error, verbose);
E
Erich Gamma 已提交
314 315 316 317 318
			}
		}

		if (detail.exception) {
			if (!types.isUndefinedOrNull(detail.exception.status)) {
319
				return xhrToErrorMessage(detail.exception, verbose);
E
Erich Gamma 已提交
320 321
			}

322
			return exceptionToErrorMessage(detail.exception, verbose);
E
Erich Gamma 已提交
323 324 325 326
		}
	}

	if (error.stack) {
327
		return exceptionToErrorMessage(error, verbose);
E
Erich Gamma 已提交
328 329 330 331 332 333 334 335 336
	}

	if (error.message) {
		return error.message;
	}

	return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
}

337
const canceledName = 'Canceled';
E
Erich Gamma 已提交
338 339 340 341 342 343 344 345 346

/**
 * Checks if the given error is a promise in canceled state
 */
export function isPromiseCanceledError(error: any): boolean {
	return error instanceof Error && error.name === canceledName && error.message === canceledName;
}

/**
P
Pascal Borreli 已提交
347
 * Returns an error that signals cancellation.
E
Erich Gamma 已提交
348 349
 */
export function canceled(): Error {
B
Benjamin Pasero 已提交
350
	let error = new Error(canceledName);
E
Erich Gamma 已提交
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
	error.name = error.message;
	return error;
}

/**
 * Returns an error that signals something is not implemented.
 */
export function notImplemented(): Error {
	return new Error(nls.localize('notImplementedError', "Not Implemented"));
}

export function illegalArgument(name?: string): Error {
	if (name) {
		return new Error(nls.localize('illegalArgumentError', "Illegal argument: {0}", name));
	} else {
		return new Error(nls.localize('illegalArgumentError2', "Illegal argument"));
	}
}

export function illegalState(name?: string): Error {
	if (name) {
		return new Error(nls.localize('illegalStateError', "Illegal state: {0}", name));
	} else {
		return new Error(nls.localize('illegalStateError2', "Illegal state"));
	}
}

export function readonly(): Error {
	return new Error('readonly property cannot be changed');
}

export function loaderError(err: Error): Error {
	if (platform.isWeb) {
		return new Error(nls.localize('loaderError', "Failed to load a required file. Either you are no longer connected to the internet or the server you are connected to is offline. Please refresh the browser to try again."));
	}

	return new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)));
}

export interface IErrorOptions {
	severity?: Severity;
	actions?: IAction[];
}

export function create(message: string, options: IErrorOptions = {}): Error {
B
Benjamin Pasero 已提交
396
	let result = new Error(message);
E
Erich Gamma 已提交
397 398 399 400 401 402 403 404 405 406 407

	if (types.isNumber(options.severity)) {
		(<any>result).severity = options.severity;
	}

	if (options.actions) {
		(<any>result).actions = options.actions;
	}

	return result;
}