Skip to content

Commit 49041e2

Browse files
committed
Address comments and fix a few issues
1 parent 13e0466 commit 49041e2

File tree

2 files changed

+51
-31
lines changed

2 files changed

+51
-31
lines changed

packages/util/src/errors.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,40 +61,33 @@ export type ErrorList<T extends string = string> = {
6161
const ERROR_NAME = 'FirebaseError';
6262

6363
export interface StringLike {
64-
toString: () => string;
64+
toString(): string;
6565
}
6666

6767
export interface ErrorData {
6868
[key: string]: StringLike | undefined;
6969
}
7070

71-
export interface FirebaseError {
72-
// Unique code for error - format is service/error-code-string
71+
export interface FirebaseError extends Error, ErrorData {
72+
// Unique code for error - format is service/error-code-string.
7373
readonly code: string;
7474

7575
// Developer-friendly error message.
7676
readonly message: string;
7777

78-
// Always 'FirebaseError'
78+
// Always 'FirebaseError'.
7979
readonly name: typeof ERROR_NAME;
8080

81-
// Where available - stack backtrace in a string
82-
readonly stack: string;
83-
84-
// Additional custom error data that was used in the template.
85-
readonly data: ErrorData;
81+
// Where available - stack backtrace in a string.
82+
readonly stack?: string;
8683
}
8784

8885
// Based on code from:
8986
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Custom_Error_Types
9087
export class FirebaseError extends Error {
9188
readonly name = ERROR_NAME;
9289

93-
constructor(
94-
readonly code: string,
95-
message: string,
96-
readonly data: ErrorData = {}
97-
) {
90+
constructor(readonly code: string, message: string) {
9891
super(message);
9992

10093
// Fix For ES5
@@ -127,20 +120,25 @@ export class ErrorFactory<ErrorCode extends string> {
127120

128121
// Keys with an underscore at the end of their name are not included in
129122
// error.data for some reason.
130-
const filteredData: ErrorData = {};
123+
const error = new FirebaseError(fullCode, fullMessage);
131124
// TODO: Replace with Object.entries when lib is updated to es2017.
132125
for (const key of Object.keys(data)) {
133126
if (key.slice(-1) !== '_') {
134-
filteredData[key] = data[key];
127+
if (key in error) {
128+
console.warn(
129+
`Overwriting FirebaseError base field "${key}" can cause unexpected behavior.`
130+
);
131+
}
132+
error[key] = data[key];
135133
}
136134
}
137-
return new FirebaseError(fullCode, fullMessage, filteredData);
135+
return error;
138136
}
139137
}
140138

141139
function replaceTemplate(template: string, data: ErrorData): string {
142140
return template.replace(PATTERN, (_, key) => {
143-
let value = data != null ? data[key] : undefined;
141+
const value = data[key];
144142
return value != null ? value.toString() : `<${key}?>`;
145143
});
146144
}

packages/util/test/errors.test.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,62 +15,84 @@
1515
* limitations under the License.
1616
*/
1717
import { assert } from 'chai';
18+
import { stub } from 'sinon';
1819
import { ErrorFactory, ErrorList, FirebaseError } from '../src/errors';
1920

20-
type Err = 'generic-error' | 'file-not-found' | 'anon-replace';
21+
type ErrorCode =
22+
| 'generic-error'
23+
| 'file-not-found'
24+
| 'anon-replace'
25+
| 'overwrite-field';
2126

22-
let errors: ErrorList<Err> = {
27+
const ERROR_MAP: ErrorList<ErrorCode> = {
2328
'generic-error': 'Unknown error',
2429
'file-not-found': "Could not find file: '{$file}'",
25-
'anon-replace': 'Hello, {$repl_}!'
30+
'anon-replace': 'Hello, {$repl_}!',
31+
'overwrite-field':
32+
'I decided to use {$code} to represent the error code from my server.'
2633
};
2734

28-
let error = new ErrorFactory<Err>('fake', 'Fake', errors);
35+
const ERROR_FACTORY = new ErrorFactory<ErrorCode>('fake', 'Fake', ERROR_MAP);
2936

3037
describe('FirebaseError', () => {
3138
it('creates an Error', () => {
32-
let e = error.create('generic-error');
39+
const e = ERROR_FACTORY.create('generic-error');
3340
assert.instanceOf(e, Error);
3441
assert.instanceOf(e, FirebaseError);
3542
assert.equal(e.code, 'fake/generic-error');
3643
assert.equal(e.message, 'Fake: Unknown error (fake/generic-error).');
3744
});
3845

3946
it('replaces template values with data', () => {
40-
let e = error.create('file-not-found', { file: 'foo.txt' });
47+
const e = ERROR_FACTORY.create('file-not-found', { file: 'foo.txt' });
4148
assert.equal(e.code, 'fake/file-not-found');
4249
assert.equal(
4350
e.message,
4451
"Fake: Could not find file: 'foo.txt' (fake/file-not-found)."
4552
);
46-
assert.equal(e.data.file, 'foo.txt');
53+
assert.equal(e.file, 'foo.txt');
4754
});
4855

4956
it('anonymously replaces template values with data', () => {
50-
let e = error.create('anon-replace', { repl_: 'world' });
57+
const e = ERROR_FACTORY.create('anon-replace', { repl_: 'world' });
5158
assert.equal(e.code, 'fake/anon-replace');
5259
assert.equal(e.message, 'Fake: Hello, world! (fake/anon-replace).');
53-
assert.isUndefined(e.data.repl_);
60+
assert.isUndefined(e.repl_);
5461
});
5562

5663
it('uses "Error" as template when template is missing', () => {
5764
// Cast to avoid compile-time error.
58-
let e = error.create(('no-such-code' as any) as Err);
65+
const e = ERROR_FACTORY.create(('no-such-code' as any) as ErrorCode);
5966
assert.equal(e.code, 'fake/no-such-code');
6067
assert.equal(e.message, 'Fake: Error (fake/no-such-code).');
6168
});
6269

6370
it('uses the key in the template if the replacement is missing', () => {
64-
let e = error.create('file-not-found', { fileX: 'foo.txt' });
71+
const e = ERROR_FACTORY.create('file-not-found', { fileX: 'foo.txt' });
6572
assert.equal(e.code, 'fake/file-not-found');
6673
assert.equal(
6774
e.message,
6875
"Fake: Could not find file: '<file?>' (fake/file-not-found)."
6976
);
7077
});
7178

79+
it('warns if overwriting a base error field with custom data', () => {
80+
const warnStub = stub(console, 'warn');
81+
const e = ERROR_FACTORY.create('overwrite-field', {
82+
code: 'overwritten code'
83+
});
84+
assert.equal(e.code, 'overwritten code');
85+
// TODO: use sinon-chai for this.
86+
assert.ok(
87+
warnStub.calledOnceWith(
88+
'Overwriting FirebaseError base field "code" can cause unexpected behavior.'
89+
)
90+
);
91+
warnStub.restore();
92+
});
93+
7294
it('has stack', () => {
73-
let e = error.create('generic-error');
95+
const e = ERROR_FACTORY.create('generic-error');
7496
// Multi-line match trick - .* does not match \n
7597
assert.match(e.stack, /FirebaseError[\s\S]/);
7698
});
@@ -91,6 +113,6 @@ function dummy1() {
91113
}
92114

93115
function dummy2() {
94-
let error = new ErrorFactory<Err>('dummy', 'Dummy', errors);
116+
const error = new ErrorFactory<ErrorCode>('dummy', 'Dummy', ERROR_MAP);
95117
throw error.create('generic-error');
96118
}

0 commit comments

Comments
 (0)