Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The `unknownutil` provides the following predicate functions
- `isNull(x: unknown): x is null`
- `isUndefined(x: unknown): x is undefined`
- `isNone(x: unknown): x is null | undefined`
- `isLike<R, T extends unknown>(ref: R, x: unknown, pred?: Predicate<T>): x is R`

For example:

Expand Down Expand Up @@ -52,6 +53,39 @@ if (isArray(a, isString)) {
}
```

Use `isLike` if you need some complicated types like tuple or struct like:

```typescript
import { isLike } from "https://deno.land/x/unknownutil/mod.ts";

const a: unknown = ["a", 0, "b"];
const b: unknown = ["a", 0, "b", "c"];

if (isLike(["", 0, ""], a)) {
// 'a' is [string, number, string] thus this block is called
}

if (isLike(["", 0, ""], b)) {
// 'b' is [string, number, string, string] thus this block is NOT called
}

const c: unknown = { foo: "foo", bar: 100 };
const d: unknown = { foo: "foo", bar: 100, hoge: "hoge" };
const e: unknown = { foo: "foo", hoge: "hoge" };

if (isLike({ foo: "", bar: 0 }, c)) {
// 'c' is {foo: string, bar: number} thus this block is called
}

if (isLike({ foo: "", bar: 0 }, d)) {
// 'd' contains {foo: string, bar: number} thus this block is called
}

if (isLike({ foo: "", bar: 0 }, e)) {
// 'e' does not contain {foo: '', bar: 0} thus this block is NOT called
}
```

### ensureXXXXX

The `unknownutil` provides the following ensure functions which will raise
Expand Down Expand Up @@ -93,6 +127,20 @@ ensureArray(b); // Now 'b' is 'unknown[]'
ensureArray(b, isString); // Raise EnsureError on above while 'b' is not string array
```

Use `ensureLike` if you need some complicated types like tuple or struct like:

```typescript
import { ensureLike } from "https://deno.land/x/unknownutil/mod.ts";

const a: unknown = ["a", "b", "c"];
ensureLike([], a); // Now 'a' is 'unknown[]'
ensureLike(["", "", ""], a); // Now 'a' is '[string, string, string]'

const b: unknown = { foo: "foo", bar: 0 };
ensureLike({}, b); // Now 'b' is 'Record<string, unknown>'
ensureLike({ foo: "", bar: 0 }, b); // Now 'b' is '{foo: string, bar: number}'
```

## License

The code follows MIT license written in [LICENSE](./LICENSE). Contributors need
Expand Down
13 changes: 13 additions & 0 deletions ensure.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
isArray,
isFunction,
isLike,
isNone,
isNull,
isNumber,
Expand Down Expand Up @@ -100,3 +101,15 @@ export function ensureUndefined(x: unknown): asserts x is undefined {
export function ensureNone(x: unknown): asserts x is null | undefined {
return ensure(x, isNone, "The value must be null or undefined");
}

/**
* Ensure if `x` follows the reference by raising an `EnsureError` when it doesn't.
*/
export function ensureLike<R, T extends unknown>(
ref: R,
x: unknown,
ipred?: Predicate<T>,
): asserts x is R {
const pred = (x: unknown): x is T[] => isLike(ref, x, ipred);
return ensure(x, pred, "The value must follow the reference");
}
123 changes: 123 additions & 0 deletions ensure_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ensure,
ensureArray,
ensureFunction,
ensureLike,
ensureNone,
ensureNull,
ensureNumber,
Expand Down Expand Up @@ -141,3 +142,125 @@ Deno.test("ensureNone throws error on non null nor undefined", () => {
assertThrows(() => ensureNone({}));
assertThrows(() => ensureNone(function () {}));
});

Deno.test("ensureLike does it's job on string", () => {
const ref = "";
ensureLike(ref, "Hello");

assertThrows(() => ensureLike(ref, 0));
assertThrows(() => ensureLike(ref, []));
assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, function () {}));
assertThrows(() => ensureLike(ref, undefined));
assertThrows(() => ensureLike(ref, null));
});
Deno.test("ensureLike does it's job on number", () => {
const ref = 0;
ensureLike(ref, 0);
ensureLike(ref, 1);
ensureLike(ref, 0.1);

assertThrows(() => ensureLike(ref, "a"));
assertThrows(() => ensureLike(ref, []));
assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, function () {}));
assertThrows(() => ensureLike(ref, undefined));
assertThrows(() => ensureLike(ref, null));
});
Deno.test("ensureLike does it's job on array", () => {
const ref: unknown[] = [];
ensureLike(ref, []);
ensureLike(ref, [0, 1, 2]);
ensureLike(ref, ["a", "b", "c"]);

assertThrows(() => ensureLike(ref, "a"));
assertThrows(() => ensureLike(ref, 0));
assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, function () {}));
assertThrows(() => ensureLike(ref, undefined));
assertThrows(() => ensureLike(ref, null));
});
Deno.test("ensureLike does it's job on T array", () => {
const ref: unknown[] = [];
ensureLike(ref, [0, 1, 2], isNumber);
ensureLike(ref, ["a", "b", "c"], isString);

assertThrows(() => ensureLike(ref, [0, 1, 2], isString));
assertThrows(() => ensureLike(ref, ["a", "b", "c"], isNumber));
});
Deno.test("ensureLike does it's job on tuple", () => {
const ref = ["", 0, ""];
ensureLike(ref, ["", 0, ""]);
ensureLike(ref, ["Hello", 100, "World"]);

assertThrows(() => ensureLike(ref, ["Hello", 100, "World", "foo"]));
assertThrows(() => ensureLike(ref, [0, 0, 0]));
assertThrows(() => ensureLike(ref, ["", "", ""]));
assertThrows(() => ensureLike(ref, [0, "", 0]));
});
Deno.test("ensureLike does it's job on object", () => {
const ref = {};
ensureLike(ref, {});
ensureLike(ref, { a: 0 });
ensureLike(ref, { a: "a" });

assertThrows(() => ensureLike(ref, "a"));
assertThrows(() => ensureLike(ref, 0));
assertThrows(() => ensureLike(ref, []));
assertThrows(() => ensureLike(ref, function () {}));
assertThrows(() => ensureLike(ref, undefined));
assertThrows(() => ensureLike(ref, null));
});
Deno.test("ensureLike does it's job on T object", () => {
const ref = {};
ensureLike(ref, { a: 0 }, isNumber);
ensureLike(ref, { a: "a" }, isString);

assertThrows(() => ensureLike(ref, { a: 0 }, isString));
assertThrows(() => ensureLike(ref, { a: "a" }, isNumber));
});
Deno.test("ensureLike does it's job on struct", () => {
const ref = { foo: "", bar: 0 };
ensureLike(ref, { foo: "", bar: 0 });
ensureLike(ref, { foo: "", bar: 0, hoge: "" });

assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, { foo: "" }));
assertThrows(() => ensureLike(ref, { bar: 0 }));
});
Deno.test("ensureLike does it's job on function", () => {
const ref = () => {};
ensureLike(ref, ensureFunction);
ensureLike(ref, function () {});
ensureLike(ref, () => {});
ensureLike(ref, setTimeout);

assertThrows(() => ensureLike(ref, "a"));
assertThrows(() => ensureLike(ref, 0));
assertThrows(() => ensureLike(ref, []));
assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, undefined));
assertThrows(() => ensureLike(ref, null));
});
Deno.test("ensureLike does it's job on null", () => {
const ref = null;
ensureLike(ref, null);

assertThrows(() => ensureLike(ref, "a"));
assertThrows(() => ensureLike(ref, 0));
assertThrows(() => ensureLike(ref, []));
assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, function () {}));
assertThrows(() => ensureLike(ref, undefined));
});
Deno.test("ensureLike does it's job on undefined", () => {
const ref = undefined;
ensureLike(ref, undefined);

assertThrows(() => ensureLike(ref, "a"));
assertThrows(() => ensureLike(ref, 0));
assertThrows(() => ensureLike(ref, []));
assertThrows(() => ensureLike(ref, {}));
assertThrows(() => ensureLike(ref, function () {}));
assertThrows(() => ensureLike(ref, null));
});
36 changes: 36 additions & 0 deletions is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,39 @@ export function isUndefined(x: unknown): x is undefined {
export function isNone(x: unknown): x is null | undefined {
return x == null;
}

/**
* Return true if a type of value is like a type of reference.
*/
export function isLike<R, T extends unknown>(
ref: R,
x: unknown,
pred?: Predicate<T>,
): x is R {
if (isString(ref) && isString(x)) {
return true;
}
if (isNumber(ref) && isNumber(x)) {
return true;
}
if (isArray(ref, pred) && isArray(x, pred)) {
return ref.length === 0 || (
ref.length === x.length &&
ref.every((r, i) => isLike(r, x[i]))
);
}
if (isObject(ref, pred) && isObject(x, pred)) {
const es = Object.entries(ref);
return es.length === 0 || es.every(([k, v]) => isLike(v, x[k]));
}
if (isFunction(ref) && isFunction(x)) {
return true;
}
if (isNull(ref) && isNull(x)) {
return true;
}
if (isUndefined(ref) && isUndefined(x)) {
return true;
}
return false;
}
Loading