|
54 | 54 | * }
|
55 | 55 | * }
|
56 | 56 | */
|
57 |
| -export type ErrorList<T> = { [code: string]: string }; |
| 57 | +export type ErrorList<T extends string = string> = { |
| 58 | + readonly [K in T]: string |
| 59 | +}; |
58 | 60 |
|
59 | 61 | const ERROR_NAME = 'FirebaseError';
|
60 | 62 |
|
61 | 63 | export interface StringLike {
|
62 | 64 | toString: () => string;
|
63 | 65 | }
|
64 | 66 |
|
65 |
| -let captureStackTrace: (obj: Object, fn?: Function) => void = (Error as any) |
66 |
| - .captureStackTrace; |
67 |
| - |
68 |
| -// Export for faking in tests |
69 |
| -export function patchCapture(captureFake?: any): any { |
70 |
| - let result: any = captureStackTrace; |
71 |
| - captureStackTrace = captureFake; |
72 |
| - return result; |
| 67 | +export interface ErrorData { |
| 68 | + [key: string]: StringLike | undefined; |
73 | 69 | }
|
74 | 70 |
|
75 | 71 | export interface FirebaseError {
|
76 | 72 | // Unique code for error - format is service/error-code-string
|
77 |
| - code: string; |
| 73 | + readonly code: string; |
78 | 74 |
|
79 | 75 | // Developer-friendly error message.
|
80 |
| - message: string; |
| 76 | + readonly message: string; |
81 | 77 |
|
82 | 78 | // Always 'FirebaseError'
|
83 |
| - name: string; |
| 79 | + readonly name: typeof ERROR_NAME; |
84 | 80 |
|
85 | 81 | // Where available - stack backtrace in a string
|
86 |
| - stack: string; |
87 |
| -} |
| 82 | + readonly stack: string; |
88 | 83 |
|
89 |
| -export class FirebaseError implements FirebaseError { |
90 |
| - public stack: string; |
91 |
| - public name: string; |
92 |
| - |
93 |
| - constructor(public code: string, public message: string) { |
94 |
| - let stack: string; |
95 |
| - // We want the stack value, if implemented by Error |
96 |
| - if (captureStackTrace) { |
97 |
| - // Patches this.stack, omitted calls above ErrorFactory#create |
98 |
| - captureStackTrace(this, ErrorFactory.prototype.create); |
99 |
| - } else { |
100 |
| - try { |
101 |
| - // In case of IE11, stack will be set only after error is raised. |
102 |
| - // https://docs.microsoft.com/en-us/scripting/javascript/reference/stack-property-error-javascript |
103 |
| - throw Error.apply(this, arguments); |
104 |
| - } catch (err) { |
105 |
| - this.name = ERROR_NAME; |
106 |
| - // Make non-enumerable getter for the property. |
107 |
| - Object.defineProperty(this, 'stack', { |
108 |
| - get: function() { |
109 |
| - return err.stack; |
110 |
| - } |
111 |
| - }); |
112 |
| - } |
113 |
| - } |
114 |
| - } |
| 84 | + // Additional custom error data that was used in the template. |
| 85 | + readonly data: ErrorData; |
115 | 86 | }
|
116 | 87 |
|
117 |
| -// Back-door inheritance |
118 |
| -FirebaseError.prototype = Object.create(Error.prototype) as FirebaseError; |
119 |
| -FirebaseError.prototype.constructor = FirebaseError; |
120 |
| -(FirebaseError.prototype as any).name = ERROR_NAME; |
121 |
| - |
122 |
| -export class ErrorFactory<T extends string> { |
123 |
| - // Matches {$name}, by default. |
124 |
| - public pattern = /\{\$([^}]+)}/g; |
| 88 | +// Based on code from: |
| 89 | +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types |
| 90 | +export class FirebaseError extends Error { |
| 91 | + readonly name = ERROR_NAME; |
125 | 92 |
|
126 | 93 | constructor(
|
127 |
| - private service: string, |
128 |
| - private serviceName: string, |
129 |
| - private errors: ErrorList<T> |
| 94 | + readonly code: string, |
| 95 | + message: string, |
| 96 | + readonly data: ErrorData = {} |
130 | 97 | ) {
|
131 |
| - // empty |
132 |
| - } |
| 98 | + super(message); |
| 99 | + |
| 100 | + // Fix For ES5 |
| 101 | + // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work |
| 102 | + Object.setPrototypeOf(this, FirebaseError.prototype); |
133 | 103 |
|
134 |
| - create(code: T, data?: { [prop: string]: StringLike }): FirebaseError { |
135 |
| - if (data === undefined) { |
136 |
| - data = {}; |
| 104 | + // Maintains proper stack trace for where our error was thrown. |
| 105 | + // Only available on V8. |
| 106 | + if (Error.captureStackTrace) { |
| 107 | + Error.captureStackTrace(this, ErrorFactory.prototype.create); |
137 | 108 | }
|
| 109 | + } |
| 110 | +} |
138 | 111 |
|
139 |
| - let template = this.errors[code as string]; |
| 112 | +export class ErrorFactory<ErrorCode extends string> { |
| 113 | + constructor( |
| 114 | + private readonly service: string, |
| 115 | + private readonly serviceName: string, |
| 116 | + private readonly errors: ErrorList<ErrorCode> |
| 117 | + ) {} |
140 | 118 |
|
141 |
| - let fullCode = this.service + '/' + code; |
142 |
| - let message: string; |
| 119 | + create(code: ErrorCode, data: ErrorData = {}): FirebaseError { |
| 120 | + const fullCode = `${this.service}/${code}`; |
| 121 | + const template = this.errors[code]; |
143 | 122 |
|
144 |
| - if (template === undefined) { |
145 |
| - message = 'Error'; |
146 |
| - } else { |
147 |
| - message = template.replace(this.pattern, (match, key) => { |
148 |
| - let value = data![key]; |
149 |
| - return value !== undefined ? value.toString() : '<' + key + '?>'; |
150 |
| - }); |
151 |
| - } |
| 123 | + const message = template ? replaceTemplate(template, data) : 'Error'; |
152 | 124 |
|
153 | 125 | // Service: Error message (service/code).
|
154 |
| - message = this.serviceName + ': ' + message + ' (' + fullCode + ').'; |
155 |
| - let err = new FirebaseError(fullCode, message); |
156 |
| - |
157 |
| - // Populate the Error object with message parts for programmatic |
158 |
| - // accesses (e.g., e.file). |
159 |
| - for (let prop in data) { |
160 |
| - if (!data.hasOwnProperty(prop) || prop.slice(-1) === '_') { |
161 |
| - continue; |
| 126 | + const fullMessage = `${this.serviceName}: ${message} (${fullCode}).`; |
| 127 | + |
| 128 | + // Keys with an underscore at the end of their name are not included in |
| 129 | + // error.data for some reason. |
| 130 | + const filteredData: ErrorData = {}; |
| 131 | + // TODO: Replace with Object.entries when lib is updated to es2017. |
| 132 | + for (const key of Object.keys(data)) { |
| 133 | + if (key.slice(-1) !== '_') { |
| 134 | + filteredData[key] = data[key]; |
162 | 135 | }
|
163 |
| - (err as any)[prop] = data[prop]; |
164 | 136 | }
|
165 |
| - |
166 |
| - return err; |
| 137 | + return new FirebaseError(fullCode, fullMessage, filteredData); |
167 | 138 | }
|
168 | 139 | }
|
| 140 | + |
| 141 | +function replaceTemplate(template: string, data: ErrorData): string { |
| 142 | + return template.replace(PATTERN, (_, key) => { |
| 143 | + let value = data != null ? data[key] : undefined; |
| 144 | + return value != null ? value.toString() : `<${key}?>`; |
| 145 | + }); |
| 146 | +} |
| 147 | + |
| 148 | +const PATTERN = /\{\$([^}]+)}/g; |
0 commit comments