Include code in stringified errors

This is done by returning the entire error stringified instead of just
the message.

This fixes the issue with the "save as" dialog.
This commit is contained in:
Asher 2019-02-26 15:46:05 -06:00
parent be3f0c437f
commit d556e110cb
No known key found for this signature in database
GPG Key ID: 7BB4BA9C783D2BBC
7 changed files with 41 additions and 84 deletions

View File

@ -496,8 +496,9 @@ describe("fs", () => {
});
it("should fail to stat nonexistent file", async () => {
await expect(util.promisify(fs.stat)(tmpFile()))
.rejects.toThrow("ENOENT");
const error = await util.promisify(fs.stat)(tmpFile()).catch((e) => e);
expect(error.message).toContain("ENOENT");
expect(error.code).toBe("ENOENT");
});
});

View File

@ -98,7 +98,7 @@ export class Client {
const eventsMsg = new EvalEventMessage();
eventsMsg.setId(doEval.id);
eventsMsg.setEvent(event);
eventsMsg.setArgsList(args.map(stringify));
eventsMsg.setArgsList(args.map((a) => stringify(a)));
const clientMsg = new ClientMessage();
clientMsg.setEvalEvent(eventsMsg);
this.connection.send(clientMsg.serializeBinary());
@ -140,7 +140,7 @@ export class Client {
const id = this.evalId++;
newEval.setId(id);
newEval.setActive(active);
newEval.setArgsList([a1, a2, a3, a4, a5, a6].map(stringify));
newEval.setArgsList([a1, a2, a3, a4, a5, a6].map((a) => stringify(a)));
newEval.setFunction(func.toString());
const clientMsg = new ClientMessage();
@ -155,16 +155,15 @@ export class Client {
const d1 = this.evalDoneEmitter.event((doneMsg) => {
if (doneMsg.getId() === id) {
const resp = doneMsg.getResponse();
dispose();
resolve(parse(resp));
resolve(parse(doneMsg.getResponse()));
}
});
const d2 = this.evalFailedEmitter.event((failedMsg) => {
if (failedMsg.getId() === id) {
dispose();
reject(new Error(failedMsg.getMessage()));
reject(parse(failedMsg.getResponse()));
}
});
});

View File

@ -25,17 +25,19 @@ export type IEncodingOptions = {
export type IEncodingOptionsCallback = IEncodingOptions | ((err: NodeJS.ErrnoException, ...args: any[]) => void);
/**
* Stringify an event argument.
* Stringify an event argument. isError is because although methods like
* `fs.stat` are supposed to throw Error objects, they currently throw regular
* objects when running tests through Jest.
*/
export const stringify = (arg: any): string => { // tslint:disable-line no-any
if (arg instanceof Error) {
export const stringify = (arg: any, isError?: boolean): string => { // tslint:disable-line no-any
if (arg instanceof Error || isError) {
// Errors don't stringify at all. They just become "{}".
return JSON.stringify({
type: "Error",
data: {
message: arg.message,
name: arg.name,
stack: arg.stack,
code: (arg as NodeJS.ErrnoException).code,
},
});
} else if (arg instanceof Uint8Array) {
@ -74,7 +76,16 @@ export const parse = (arg: string): any => { // tslint:disable-line no-any
// what happens to buffers and stringify them as regular objects.
case "Error":
if (result.data.message) {
return new Error(result.data.message);
const error = new Error(result.data.message);
// TODO: Can we set the stack? Doing so seems to make it into an
// "invalid object".
if (typeof result.data.code !== "undefined") {
(error as NodeJS.ErrnoException).code = result.data.code;
}
// tslint:disable-next-line no-any
(error as any).originalStack = result.data.stack;
return error;
}
break;
}

View File

@ -36,8 +36,7 @@ export const evaluate = (connection: SendableConnection, message: NewEvalMessage
const sendException = (error: Error): void => {
const evalFailed = new EvalFailedMessage();
evalFailed.setId(message.getId());
evalFailed.setReason(EvalFailedMessage.Reason.EXCEPTION);
evalFailed.setMessage(error.toString() + " " + error.stack);
evalFailed.setResponse(stringify(error, true));
const serverMsg = new ServerMessage();
serverMsg.setEvalFailed(evalFailed);
@ -58,7 +57,7 @@ export const evaluate = (connection: SendableConnection, message: NewEvalMessage
logger.trace(() => [
`${event}`,
field("id", message.getId()),
field("args", args.map(stringify)),
field("args", args.map((a) => stringify(a))),
]);
cb(...args);
});
@ -67,11 +66,11 @@ export const evaluate = (connection: SendableConnection, message: NewEvalMessage
logger.trace(() => [
`emit ${event}`,
field("id", message.getId()),
field("args", args.map(stringify)),
field("args", args.map((a) => stringify(a))),
]);
const eventMsg = new EvalEventMessage();
eventMsg.setEvent(event);
eventMsg.setArgsList(args.map(stringify));
eventMsg.setArgsList(args.map((a) => stringify(a)));
eventMsg.setId(message.getId());
const serverMsg = new ServerMessage();
serverMsg.setEvalEvent(eventMsg);

View File

@ -19,13 +19,7 @@ message EvalEventMessage {
message EvalFailedMessage {
uint64 id = 1;
enum Reason {
Timeout = 0;
Exception = 1;
Conflict = 2;
}
Reason reason = 2;
string message = 3;
string response = 2;
}
message EvalDoneMessage {

View File

@ -75,11 +75,8 @@ export class EvalFailedMessage extends jspb.Message {
getId(): number;
setId(value: number): void;
getReason(): EvalFailedMessage.Reason;
setReason(value: EvalFailedMessage.Reason): void;
getMessage(): string;
setMessage(value: string): void;
getResponse(): string;
setResponse(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): EvalFailedMessage.AsObject;
@ -94,14 +91,7 @@ export class EvalFailedMessage extends jspb.Message {
export namespace EvalFailedMessage {
export type AsObject = {
id: number,
reason: EvalFailedMessage.Reason,
message: string,
}
export enum Reason {
TIMEOUT = 0,
EXCEPTION = 1,
CONFLICT = 2,
response: string,
}
}

View File

@ -14,7 +14,6 @@ var global = Function('return this')();
goog.exportSymbol('proto.EvalDoneMessage', null, global);
goog.exportSymbol('proto.EvalEventMessage', null, global);
goog.exportSymbol('proto.EvalFailedMessage', null, global);
goog.exportSymbol('proto.EvalFailedMessage.Reason', null, global);
goog.exportSymbol('proto.NewEvalMessage', null, global);
/**
@ -554,8 +553,7 @@ proto.EvalFailedMessage.prototype.toObject = function(opt_includeInstance) {
proto.EvalFailedMessage.toObject = function(includeInstance, msg) {
var f, obj = {
id: jspb.Message.getFieldWithDefault(msg, 1, 0),
reason: jspb.Message.getFieldWithDefault(msg, 2, 0),
message: jspb.Message.getFieldWithDefault(msg, 3, "")
response: jspb.Message.getFieldWithDefault(msg, 2, "")
};
if (includeInstance) {
@ -597,12 +595,8 @@ proto.EvalFailedMessage.deserializeBinaryFromReader = function(msg, reader) {
msg.setId(value);
break;
case 2:
var value = /** @type {!proto.EvalFailedMessage.Reason} */ (reader.readEnum());
msg.setReason(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setMessage(value);
msg.setResponse(value);
break;
default:
reader.skipField();
@ -640,32 +634,16 @@ proto.EvalFailedMessage.serializeBinaryToWriter = function(message, writer) {
f
);
}
f = message.getReason();
if (f !== 0.0) {
writer.writeEnum(
f = message.getResponse();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getMessage();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
};
/**
* @enum {number}
*/
proto.EvalFailedMessage.Reason = {
TIMEOUT: 0,
EXCEPTION: 1,
CONFLICT: 2
};
/**
* optional uint64 id = 1;
* @return {number}
@ -682,32 +660,17 @@ proto.EvalFailedMessage.prototype.setId = function(value) {
/**
* optional Reason reason = 2;
* @return {!proto.EvalFailedMessage.Reason}
*/
proto.EvalFailedMessage.prototype.getReason = function() {
return /** @type {!proto.EvalFailedMessage.Reason} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
};
/** @param {!proto.EvalFailedMessage.Reason} value */
proto.EvalFailedMessage.prototype.setReason = function(value) {
jspb.Message.setProto3EnumField(this, 2, value);
};
/**
* optional string message = 3;
* optional string response = 2;
* @return {string}
*/
proto.EvalFailedMessage.prototype.getMessage = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
proto.EvalFailedMessage.prototype.getResponse = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/** @param {string} value */
proto.EvalFailedMessage.prototype.setMessage = function(value) {
jspb.Message.setProto3StringField(this, 3, value);
proto.EvalFailedMessage.prototype.setResponse = function(value) {
jspb.Message.setProto3StringField(this, 2, value);
};