Skip to content

Commit

Permalink
feat(factory): add static .throw factory to helpful errors for convin…
Browse files Browse the repository at this point in the history
…ience
  • Loading branch information
uladkasach committed Jun 16, 2024
1 parent 15853e2 commit 3f82ef6
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 1 deletion.
21 changes: 21 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,24 @@ expect(error).toBeInstanceOf(HelpfulError);
expect(error.message).toContain('found me')
```

### .throw

The errors extended from the `HelpfulError` include a `.throw` static method for convenient usage with ternaries or condition chains

For example, instead of
```ts
const phone = customer.phoneNumber ?? (() => {
throw new UnexpectedCodePathError(
'customer has relationship without phone number. how is that possible?',
{ customer },
);
})();
```

You can simply write
```ts
const phone = customer.phoneNumber ?? UnexpectedCodePathError.throw(
'customer does not have a phone. how is that possible?',
{ customer },
);
```
20 changes: 20 additions & 0 deletions src/BadRequestError.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BadRequestError } from './BadRequestError';
import { getError } from './getError';

describe('BadRequestError', () => {
it('should produce a helpful, observable error message', () => {
Expand All @@ -7,4 +8,23 @@ describe('BadRequestError', () => {
});
expect(error).toMatchSnapshot();
});
it('should be throwable in a ternary conveniently and precisely', () => {
const error = getError(() => {
// this case should not throw
const customerOne: { phone: string | null } = {
phone: 'yes',
};
const phoneOne =
customerOne.phone ?? BadRequestError.throw('phone one not found!');

// but this case should throw
const customerTwo: { phone: string | null } = {
phone: null,
};
const phoneTwo =
customerTwo.phone ?? BadRequestError.throw('phone two not found!');
});
expect(error).toBeInstanceOf(BadRequestError);
expect(error.message).toContain('phone two not found!');
});
});
22 changes: 21 additions & 1 deletion src/HelpfulError.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import { HelpfulError } from './HelpfulError';
import { getError } from './getError';

describe('HelpfulError', () => {
it('should produce a helpful, observable error message', () => {
const error = new HelpfulError('the dogs were let out', {
who: 'your mom',
who: 'your mom', // 🙄😂
});
expect(error).toMatchSnapshot();
});
it('should be throwable in a ternary conveniently and precisely', () => {
const error = getError(() => {
// this case should not throw
const customerOne: { phone: string | null } = {
phone: 'yes',
};
const phoneOne =
customerOne.phone ?? HelpfulError.throw('phone one not found!');

// but this case should throw
const customerTwo: { phone: string | null } = {
phone: null,
};
const phoneTwo =
customerTwo.phone ?? HelpfulError.throw('phone two not found!');
});
expect(error).toBeInstanceOf(HelpfulError);
expect(error.message).toEqual('phone two not found!');
});
});
17 changes: 17 additions & 0 deletions src/HelpfulError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,21 @@ export class HelpfulError extends Error {
}`;
super(fullMessage);
}

/**
* a utility to throw an error of this class, for convenience
*
* e.g.,
* ```ts
* const phone = customer.phone ?? HelpfulError.throw('expected a phone');
* ```
*/
public static throw<T extends typeof HelpfulError>(
this: T, // https://stackoverflow.com/a/51749145/3068233
message: string,
metadata?: Record<string, any>,
): never {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw new this(message, metadata) as InstanceType<T>;
}
}
22 changes: 22 additions & 0 deletions src/UnexpectedCodePathError.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UnexpectedCodePathError } from './UnexpectedCodePathError';
import { getError } from './getError';

describe('UnexpectedCodePathError', () => {
it('should produce a helpful, observable error message', () => {
Expand All @@ -7,4 +8,25 @@ describe('UnexpectedCodePathError', () => {
});
expect(error).toMatchSnapshot();
});
it('should be throwable in a ternary conveniently and precisely', () => {
const error = getError(() => {
// this case should not throw
const customerOne: { phone: string | null } = {
phone: 'yes',
};
const phoneOne =
customerOne.phone ??
UnexpectedCodePathError.throw('phone one not found!');

// but this case should throw
const customerTwo: { phone: string | null } = {
phone: null,
};
const phoneTwo =
customerTwo.phone ??
UnexpectedCodePathError.throw('phone two not found!');
});
expect(error).toBeInstanceOf(UnexpectedCodePathError);
expect(error.message).toContain('phone two not found!');
});
});

0 comments on commit 3f82ef6

Please sign in to comment.