Skip to content

Commit

Permalink
refactor: coerce.ts + dependencies + docs
Browse files Browse the repository at this point in the history
  • Loading branch information
adamchal committed Feb 1, 2022
1 parent 071169a commit e01d99f
Show file tree
Hide file tree
Showing 6 changed files with 476 additions and 227 deletions.
110 changes: 103 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,76 @@ try {

## [`./coerce`](https://github.com/resolute/std/blob/master/coerce.ts)

Type validation and sanitization.
Type validation and sanitization. This module contains a handful of utility functions and
“coercers.” Coercers are unary functions that return validated/mutated input, or `throw`. The
utility function `to` (alias `coerce`) allow you to chain these coercers. The `or` utility function
may be used in the chain in order to specify a backup value to be returned instead of throwing an
error.

### `coerce`
Additionally, the `is` and `not` utility functions return `true` if a coercer or chain of coercers
(`to(…)`) passes or `false` if it throws.

Coerce input to types and formats with sanitizers and validators.
### Utility Functions

### `to`

Chain unary coercing functions.

```ts
import { nonempty, or, string, to, trim } from '@resolute/std/coerce';
to(string, trim, nonempty)(' foo '); // 'foo'
to(string, trim, nonempty)(' '); // throws TypeError
to(string, trim, nonempty, or(undefined))(' '); // undefined
```

### `or`

Provide a backup value to be used when a coercer fails. If `instanceof Error`, then that error will
be `throw`n. For any other value, it will be returned. The `or(…)` utility function must be the last
parameter in `to`.

```ts
import { or, string, to } from '@resolute/std/coerce';
to(string, or(null))('foo'); // 'foo'
to(string, or(null))(1); // null
to(string, or(new Error('foo')))(1); // throws Error: foo
```

### `is`

Type guard test. Use with any type guard or mutating function that `throw`s on failure (almost all
functions here do). The `is` function will catch the error and return `false`, otherwise it will
return `true`.

```ts
import { is, string } from '@resolute/std/coerce';
is(string)('foo'); // true
is(string)(12345); // false
```

### `not`

Negate type guard test. Use with any type guard or mutating function that `throw`s on failure
(almost all functions here do). The `not` function will catch the error and return `true`, otherwise
it will return `false`. @example

```ts
import { coerce, length, not, string, trim } from '@resolute/std/coerce';
coerce(string, trim, not(length(0)))(' foo '); // 'foo'
coerce(string, trim, not(length(0)))(' '); // TypeError
coerce(string, trim, not(length(0)))(' ', undefined); // undefined
import { not, string } from '@resolute/std/coerce';
not(string)('foo'); // false
not(string)(12345); // true
```

### Type Guard Functions

### `string`

Returns the input if it is a string. Throws otherwise.

### `number`

Returns the input if it is a finite number. Throws otherwise including when input is `NaN` or
`Infinity`.

## [`./color`](https://github.com/resolute/std/blob/master/color.ts)

Convert between hex, RGB array, and integer representations of colors. Additionally blend between
Expand Down Expand Up @@ -134,6 +191,36 @@ incr(); // 2
once(incr)(); // 1
```

### `throttle`

Limit the number of invocations of a given function (or different functions) within an interval
window. Useful for avoiding API rate limits.

```ts
import { throttle } from '@resolute/std/control';
const throttled = throttle(1, 1_000)(async () => {});
await throttled();
await throttled(); // 1s later
```

### `debounce`

Returns a function, that, as long as it continues to be invoked (.), will not be triggered (*). The
function will be called after it stops being called for `threshold` milliseconds.

```ts
// /-- 10s --\ /-- 10s --\ /-- 10s --\
// . . . . . . . . . . . . . *
import { debounce } from '@resolute/std/control';
let state = 0;
const fn = (value: number) => state += value;
const debounced = debounce(fn, 50);
debounced(1);
debounced(1);
debounced(1);
// state === 1
```

## [`./cookie`](https://github.com/resolute/std/blob/master/cookie.ts)

Parse and stringify cookies. Methods available for DOM and service worker contexts.
Expand All @@ -146,6 +233,15 @@ Easing functions from [easings.net](https://easings.net/).

Helpers for interacting with `Request` and `Response` objects.

### `fetchOk`

Throw if value is not in list.

```ts
import { fetchOk } from '@resolute/std/http';
await fetchOk('https://httpstat.us/500'); // HttpError: HTTP 500 Error
```

### `method`

Throw if value is not in list.
Expand Down
37 changes: 22 additions & 15 deletions coerce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
date,
dateify,
defined,
digits,
email,
entries,
func,
Expand All @@ -26,6 +27,7 @@ import {
number,
numeric,
object,
or,
own,
pairs,
past,
Expand All @@ -49,12 +51,14 @@ import {

Deno.test('wrapError', () => {
const sampleTypeError = new TypeError('foo');
const invalidErrorConstructor = ((arg: string) => new Error(arg)) as unknown as ErrorConstructor;
strict(wrapError()(sampleTypeError), sampleTypeError);
strict(wrapError(Error)(sampleTypeError), sampleTypeError);
equals(wrapError(SyntaxError)(sampleTypeError), new SyntaxError(sampleTypeError.message));
equals(wrapError()('foo'), new TypeError('foo'));
equals(wrapError()('foo'), new Error('foo'));
equals(wrapError(SyntaxError)('foo'), new SyntaxError('foo'));
throws(() => wrapError()(null as unknown as string));
throws(() => wrapError(invalidErrorConstructor)('foo'));
});

Deno.test('is', () => {
Expand Down Expand Up @@ -259,12 +263,12 @@ Deno.test('instance', () => {
Deno.test('own', () => {
const foo = { foo: 'foo' };
const bar = { bar: 'bar' };
throws(() => own('foo')({} as unknown as typeof foo));
throws(() => own('foo')({}));
strict(own('foo')(foo), foo);
throws(() => own('foo')(bar as unknown as typeof foo));
throws(() => own('foo')(bar));
equals(own('foo')({ foo: new Date(1) }), { foo: new Date(1) });
equals(own('message')(new Error('foo')), new Error('foo'));
throws(() => own('message')(new Date(1) as unknown as Error));
throws(() => own('message')(new Date(1)));
strict(coerce(object, own('foo'))(foo), foo);
throws(() => coerce(object, own('foo'))(bar));
throws(() => coerce(object, is(own('foo')))(bar));
Expand Down Expand Up @@ -295,6 +299,7 @@ Deno.test('length', () => {
Deno.test('split', () => {
equals(coerce(split())('a,b,,,c d e foo'), ['a', 'b', 'c', 'd', 'e', 'foo']);
equals(coerce(split())(',,,,,, , , '), []);
equals(coerce(split())('1\n2\r3'), ['1', '2', '3']);
});

Deno.test('within', () => {
Expand Down Expand Up @@ -335,25 +340,27 @@ Deno.test('date', () => {
throws(() => coerce(past)(new Date(Date.now() + 2)));
});

Deno.test('chain/nest', () => {
const trimNonEmpty = coerce(string, trim, nonempty);
const trimNonEmptyOrNull = coerce(string, trim, nonempty, or(null));
strict(coerce(trimNonEmpty)('a'), 'a');
strict(coerce(trimNonEmpty, string, digits)('a'), '');
strict(coerce(trimNonEmpty, or(null))(' '), null);
strict(coerce(trimNonEmptyOrNull)(' '), null);
});

// a default/backup value
Deno.test('coerce(…)(…, 0)', () => {
Deno.test('coerce(…, or(undefined))(…)', () => {
const defaultValue = undefined;
strict(coerce(number)('foo', defaultValue), defaultValue);
strict(coerce(number, or(defaultValue))('foo'), defaultValue);
});

// throw specific Error instance
Deno.test('coerce(…)(…, new Error(…)))', () => {
Deno.test('coerce(…, or(new Error()))(…)', () => {
const predefinedError = new Error('this is my error, there are many like it…');
try {
coerce(number)('foo', predefinedError);
coerce(number, or(predefinedError))('foo');
} catch (error) {
strict(error, predefinedError);
}
});

// Error function factory
Deno.test('coerce(…)(…, () => CustomError))', () => {
class CustomError extends Error {}
const errorHandler = (error: Error) => new CustomError(error.message);
throws(() => coerce(number)('foo', errorHandler), CustomError);
});
Loading

0 comments on commit e01d99f

Please sign in to comment.