1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | const errorClasses = {};
|
7 | const deserializers = {};
|
8 |
|
9 | export const addCustomErrorDeserializer = (
|
10 | name: string,
|
11 | deserializer: (obj: any) => any
|
12 | ): void => {
|
13 | deserializers[name] = deserializer;
|
14 | };
|
15 |
|
16 | type CustomErrorFunc = (
|
17 | message?: string,
|
18 | fields?: { [key: string]: any }
|
19 | ) => void;
|
20 |
|
21 | export const createCustomErrorClass = (name: string): CustomErrorFunc => {
|
22 | const C: CustomErrorFunc = function CustomError(message, fields): void {
|
23 | Object.assign(this, fields);
|
24 | this.name = name;
|
25 | this.message = message || name;
|
26 | this.stack = new Error().stack;
|
27 | };
|
28 | C.prototype = new Error();
|
29 | errorClasses[name] = C;
|
30 | return C;
|
31 | };
|
32 |
|
33 |
|
34 | export const deserializeError = (object: any): Error => {
|
35 | if (typeof object === "object" && object) {
|
36 | try {
|
37 |
|
38 | const msg = JSON.parse(object.message);
|
39 | if (msg.message && msg.name) {
|
40 | object = msg;
|
41 | }
|
42 | } catch (e) {
|
43 |
|
44 | }
|
45 |
|
46 | let error;
|
47 | if (typeof object.name === "string") {
|
48 | const { name } = object;
|
49 | const des = deserializers[name];
|
50 | if (des) {
|
51 | error = des(object);
|
52 | } else {
|
53 | let constructor = name === "Error" ? Error : errorClasses[name];
|
54 |
|
55 | if (!constructor) {
|
56 | console.warn("deserializing an unknown class '" + name + "'");
|
57 | constructor = createCustomErrorClass(name);
|
58 | }
|
59 |
|
60 | error = Object.create(constructor.prototype);
|
61 | try {
|
62 | for (const prop in object) {
|
63 | if (object.hasOwnProperty(prop)) {
|
64 | error[prop] = object[prop];
|
65 | }
|
66 | }
|
67 | } catch (e) {
|
68 |
|
69 | }
|
70 | }
|
71 | } else {
|
72 | error = new Error(object.message);
|
73 | }
|
74 |
|
75 | if (!error.stack && Error.captureStackTrace) {
|
76 | Error.captureStackTrace(error, deserializeError);
|
77 | }
|
78 | return error;
|
79 | }
|
80 | return new Error(String(object));
|
81 | };
|
82 |
|
83 |
|
84 | export const serializeError = (value: any): undefined | To | string => {
|
85 | if (!value) return value;
|
86 | if (typeof value === "object") {
|
87 | return destroyCircular(value, []);
|
88 | }
|
89 | if (typeof value === "function") {
|
90 | return `[Function: ${value.name || "anonymous"}]`;
|
91 | }
|
92 | return value;
|
93 | };
|
94 |
|
95 | interface To {
|
96 | name?: string;
|
97 | message?: string;
|
98 | stack?: string;
|
99 | }
|
100 |
|
101 |
|
102 | function destroyCircular(from: any, seen: any[]): To {
|
103 | const to: To = {};
|
104 | seen.push(from);
|
105 | for (const key of Object.keys(from)) {
|
106 | const value = from[key];
|
107 | if (typeof value === "function") {
|
108 | continue;
|
109 | }
|
110 | if (!value || typeof value !== "object") {
|
111 | to[key] = value;
|
112 | continue;
|
113 | }
|
114 | if (seen.indexOf(from[key]) === -1) {
|
115 | to[key] = destroyCircular(from[key], seen.slice(0));
|
116 | continue;
|
117 | }
|
118 | to[key] = "[Circular]";
|
119 | }
|
120 | if (typeof from.name === "string") {
|
121 | to.name = from.name;
|
122 | }
|
123 | if (typeof from.message === "string") {
|
124 | to.message = from.message;
|
125 | }
|
126 | if (typeof from.stack === "string") {
|
127 | to.stack = from.stack;
|
128 | }
|
129 | return to;
|
130 | }
|