Skip to content

Commit

Permalink
Add types to ErrorFactory message parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
mmermerkaya committed Apr 24, 2019
1 parent 721ac1c commit fe286a6
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 22 deletions.
44 changes: 26 additions & 18 deletions packages/util/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
* }
* }
*/
export type ErrorList<T> = { [code: string]: string };
export type ErrorList<T extends string = string> = {
readonly [K in T]: string
};

const ERROR_NAME = 'FirebaseError';

Expand Down Expand Up @@ -87,11 +89,10 @@ export interface FirebaseError {
}

export class FirebaseError implements FirebaseError {
public stack: string;
public name: string;
stack: string;
name: string;

constructor(public code: string, public message: string) {
let stack: string;
// We want the stack value, if implemented by Error
if (captureStackTrace) {
// Patches this.stack, omitted calls above ErrorFactory#create
Expand Down Expand Up @@ -119,39 +120,46 @@ FirebaseError.prototype = Object.create(Error.prototype) as FirebaseError;
FirebaseError.prototype.constructor = FirebaseError;
(FirebaseError.prototype as any).name = ERROR_NAME;

export class ErrorFactory<T extends string> {
type AnyParams = { readonly [key: string]: StringLike | undefined };

export class ErrorFactory<
ErrorCode extends string,
ErrorParams extends Partial<{ readonly [K in ErrorCode]: AnyParams }> = {}
> {
// Matches {$name}, by default.
public pattern = /\{\$([^}]+)}/g;
pattern = /\{\$([^}]+)}/g;

constructor(
private service: string,
private serviceName: string,
private errors: ErrorList<T>
private errors: ErrorList<ErrorCode>
) {
// empty
}

create(code: T, data?: { [prop: string]: StringLike }): FirebaseError {
if (data === undefined) {
data = {};
}

let template = this.errors[code as string];
create<K extends ErrorCode>(
code: K,
// For some reason, doesn't work with something like this to make the parameter optional only
// if it's not defined in ErrorParams
// data: ErrorParams[K] extends undefined ? AnyParams | void : ErrorParams[K]
data?: ErrorParams[K] extends undefined ? AnyParams : ErrorParams[K]
): FirebaseError {
let template = this.errors[code];

let fullCode = this.service + '/' + code;
let fullCode = `${this.service}/${code}`;
let message: string;

if (template === undefined) {
message = 'Error';
} else {
message = template.replace(this.pattern, (match, key) => {
let value = data![key];
return value !== undefined ? value.toString() : '<' + key + '?>';
message = template.replace(this.pattern, (_, key) => {
let value = data !== undefined ? data[key] : undefined;
return value !== undefined ? value.toString() : `<${key}?>`;
});
}

// Service: Error message (service/code).
message = this.serviceName + ': ' + message + ' (' + fullCode + ').';
message = `${this.serviceName}: ${message} (${fullCode}).`;
let err = new FirebaseError(fullCode, message);

// Populate the Error object with message parts for programmatic
Expand Down
13 changes: 9 additions & 4 deletions packages/util/test/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ import { ErrorFactory, ErrorList, patchCapture } from '../src/errors';

type Err = 'generic-error' | 'file-not-found' | 'anon-replace';

let errors = {
let errors: ErrorList<Err> = {
'generic-error': 'Unknown error',
'file-not-found': "Could not find file: '{$file}'",
'anon-replace': 'Hello, {$repl_}!'
} as ErrorList<Err>;
};

let error = new ErrorFactory<Err>('fake', 'Fake', errors);
interface ErrorParams {
'file-not-found': { file: string };
'anon-replace': { repl_: string };
}

let error = new ErrorFactory<Err, ErrorParams>('fake', 'Fake', errors);

describe('FirebaseError', () => {
it('create', () => {
Expand Down Expand Up @@ -59,7 +64,7 @@ describe('FirebaseError', () => {
});

it('Missing replacement', () => {
let e = error.create('file-not-found', { fileX: 'foo.txt' });
let e = error.create('file-not-found', { fileX: 'foo.txt' } as any);
assert.equal(e.code, 'fake/file-not-found');
assert.equal(
e.message,
Expand Down

0 comments on commit fe286a6

Please sign in to comment.