Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
Make every function accept formatted message (#9)
Browse files Browse the repository at this point in the history
* Accept message formatting in WrappedError
* Fix error properties being enumerable
* Allow formatting messages in every function
  • Loading branch information
esdmr authored Sep 3, 2021
1 parent dc5d030 commit b61505e
Show file tree
Hide file tree
Showing 15 changed files with 346 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-coats-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@esdmr/assert": minor
---

New function `wrap` to format messages before creating a `WrappedError`.
6 changes: 6 additions & 0 deletions .changeset/polite-masks-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@esdmr/assert": minor
---

Allow assertions to be `detail`ed with contextual information. Additionally, this
allows the full message to be formatted.
5 changes: 5 additions & 0 deletions .changeset/three-books-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@esdmr/assert": patch
---

Fix `PrimitiveError` and `WrappedError` having enumerable properties, duplicating output.
6 changes: 3 additions & 3 deletions examples/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ export function parseConfig (json: unknown) {
try {
assert.isObject(json);
assert.isNotNull(json);
assert.isString(json.name);
assert.isBoolean(json.private);
assert.isString(json.name, 'property "name"');
assert.isBoolean(json.private, 'property "private"');
} catch (error) {
throw new assert.WrappedError('Failed to parse config', error);
throw assert.wrap(error, 'Failed to parse config');
}

return {
Expand Down
37 changes: 18 additions & 19 deletions src/assert.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import { AssertionError } from './errors.js';
import { AssertionError, WrappedError } from './errors.js';
import { DEFAULT_MESSAGE } from './messages.js';
import { format } from './utils.js';

/**
* Formats strings for `assert` function.
* Asserts that a given condition is true.
*
* @public
* @param condition - The given condition.
* @param message - The message to include in the error. Formatted with `{}`.
* @param args - Format arguments.
* @returns The formatted string.
*/
function format (message: string, ...args: unknown[]) {
for (const item of args) {
message = message.replace('{}', String(item));
export function assert (
condition: boolean,
message = DEFAULT_MESSAGE,
...args: unknown[]
): asserts condition {
if (!condition) {
throw new AssertionError(format(message, ...args));
}

return message;
}

/**
* Asserts that a given condition is true. It formats the message provided with
* the arguments after that which are stringified via `String`.
* Wraps any thrown value.
*
* @public
* @param condition - The given condition.
* @param thrownValue - The value to wrap.
* @param message - The message to include in the error. Formatted with `{}`.
* @param args - Format arguments.
*/
export function assert (
condition: boolean,
export function wrap (
thrownValue: unknown,
message = DEFAULT_MESSAGE,
...args: unknown[]
): asserts condition {
if (condition) {
return;
}

throw new AssertionError(format(message, ...args));
) {
return new WrappedError(format(message, ...args), thrownValue);
}
16 changes: 14 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,15 @@ export class AssertionError extends Error {
export class PrimitiveError extends Error {
name = 'PrimitiveError';
stack = getErrorMessage(this);
readonly value: unknown;

constructor (readonly value: unknown) {
constructor (value: unknown) {
super(String(value));

// This marks the property as not enumerable and not writable.
Object.defineProperty(this, 'value', {
value,
});
}

/**
Expand Down Expand Up @@ -97,11 +103,17 @@ export class PrimitiveError extends Error {
*/
export class WrappedError extends Error {
name = 'WrappedError';
readonly thrownValue: unknown;

constructor (message: string, readonly thrownValue: unknown) {
constructor (message: string, thrownValue: unknown) {
super(message);
Error.captureStackTrace(this, WrappedError);

// This marks the property as not enumerable and not writable.
Object.defineProperty(this, 'thrownValue', {
value: thrownValue,
});

const error = PrimitiveError.getError(thrownValue);
const errorStack = error.stack;

Expand Down
31 changes: 25 additions & 6 deletions src/nullables.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import * as messages from './messages.js';
import { addDetail, format } from './utils.js';

/**
* Asserts that the given value is not `null`.
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isNotNull<T> (value: T | null): asserts value is T {
export function isNotNull<T> (
value: T | null,
detail?: string,
...args: unknown[]
): asserts value is T {
if (value === null) {
throw new TypeError(messages.IS_NULL);
throw new TypeError(format(addDetail(messages.IS_NULL, detail), ...args));
}
}

Expand All @@ -17,10 +24,16 @@ export function isNotNull<T> (value: T | null): asserts value is T {
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isNonNullable<T> (value: T | null | undefined): asserts value is T {
export function isNonNullable<T> (
value: T | null | undefined,
detail?: string,
...args: unknown[]
): asserts value is T {
if (value === null || value === undefined) {
throw new TypeError(messages.IS_NULLABLE);
throw new TypeError(format(addDetail(messages.IS_NULLABLE, detail), ...args));
}
}

Expand All @@ -29,9 +42,15 @@ export function isNonNullable<T> (value: T | null | undefined): asserts value is
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isNotUndefined<T> (value: T | undefined): asserts value is T {
export function isNotUndefined<T> (
value: T | undefined,
detail?: string,
...args: unknown[]
): asserts value is T {
if (value === undefined) {
throw new TypeError(messages.IS_UNDEFINED);
throw new TypeError(format(addDetail(messages.IS_UNDEFINED, detail), ...args));
}
}
46 changes: 36 additions & 10 deletions src/numbers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as messages from './messages.js';
import { addDetail, format } from './utils.js';

/**
* Asserts that the given value is not `NaN`.
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isNotNaN (value: number) {
export function isNotNaN (value: number, detail?: string, ...args: unknown[]) {
if (Number.isNaN(value)) {
throw new RangeError(messages.IS_NAN);
throw new RangeError(format(
addDetail(messages.IS_NAN, detail),
...args,
));
}
}

Expand All @@ -17,10 +23,15 @@ export function isNotNaN (value: number) {
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isFinite (value: number) {
export function isFinite (value: number, detail?: string, ...args: unknown[]) {
if (!Number.isFinite(value)) {
throw new RangeError(messages.NOT_FINITE);
throw new RangeError(format(
addDetail(messages.NOT_FINITE, detail),
...args,
));
}
}

Expand All @@ -30,10 +41,15 @@ export function isFinite (value: number) {
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isAnyInteger (value: number) {
export function isAnyInteger (value: number, detail?: string, ...args: unknown[]) {
if (!Number.isInteger(value)) {
throw new RangeError(messages.NOT_INTEGER);
throw new RangeError(format(
addDetail(messages.NOT_INTEGER, detail),
...args,
));
}
}

Expand All @@ -42,10 +58,15 @@ export function isAnyInteger (value: number) {
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isPositive (value: number) {
export function isPositive (value: number, detail?: string, ...args: unknown[]) {
if (value < 0) {
throw new RangeError(messages.NOT_POSITIVE);
throw new RangeError(format(
addDetail(messages.NOT_POSITIVE, detail),
...args,
));
}
}

Expand All @@ -54,9 +75,14 @@ export function isPositive (value: number) {
*
* @public
* @param value - Value to assert.
* @param detail - Extra description.
* @param args - Format arguments.
*/
export function isSafeInteger (value: number) {
export function isSafeInteger (value: number, detail?: string, ...args: unknown[]) {
if (!Number.isSafeInteger(value)) {
throw new RangeError(messages.NOT_SAFE_INTEGER);
throw new RangeError(format(
addDetail(messages.NOT_SAFE_INTEGER, detail),
...args,
));
}
}
Loading

0 comments on commit b61505e

Please sign in to comment.