Skip to content

Commit 90e9f3f

Browse files
authored
Merge pull request #1 from lambdalisue/arbit
Add `isLike` and `ensureLike` for complex tuple/struct
2 parents e552c30 + 10f1e2f commit 90e9f3f

File tree

5 files changed

+350
-2
lines changed

5 files changed

+350
-2
lines changed

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The `unknownutil` provides the following predicate functions
2222
- `isNull(x: unknown): x is null`
2323
- `isUndefined(x: unknown): x is undefined`
2424
- `isNone(x: unknown): x is null | undefined`
25+
- `isLike<R, T extends unknown>(ref: R, x: unknown, pred?: Predicate<T>): x is R`
2526

2627
For example:
2728

@@ -52,6 +53,39 @@ if (isArray(a, isString)) {
5253
}
5354
```
5455

56+
Use `isLike` if you need some complicated types like tuple or struct like:
57+
58+
```typescript
59+
import { isLike } from "https://deno.land/x/unknownutil/mod.ts";
60+
61+
const a: unknown = ["a", 0, "b"];
62+
const b: unknown = ["a", 0, "b", "c"];
63+
64+
if (isLike(["", 0, ""], a)) {
65+
// 'a' is [string, number, string] thus this block is called
66+
}
67+
68+
if (isLike(["", 0, ""], b)) {
69+
// 'b' is [string, number, string, string] thus this block is NOT called
70+
}
71+
72+
const c: unknown = { foo: "foo", bar: 100 };
73+
const d: unknown = { foo: "foo", bar: 100, hoge: "hoge" };
74+
const e: unknown = { foo: "foo", hoge: "hoge" };
75+
76+
if (isLike({ foo: "", bar: 0 }, c)) {
77+
// 'c' is {foo: string, bar: number} thus this block is called
78+
}
79+
80+
if (isLike({ foo: "", bar: 0 }, d)) {
81+
// 'd' contains {foo: string, bar: number} thus this block is called
82+
}
83+
84+
if (isLike({ foo: "", bar: 0 }, e)) {
85+
// 'e' does not contain {foo: '', bar: 0} thus this block is NOT called
86+
}
87+
```
88+
5589
### ensureXXXXX
5690

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

130+
Use `ensureLike` if you need some complicated types like tuple or struct like:
131+
132+
```typescript
133+
import { ensureLike } from "https://deno.land/x/unknownutil/mod.ts";
134+
135+
const a: unknown = ["a", "b", "c"];
136+
ensureLike([], a); // Now 'a' is 'unknown[]'
137+
ensureLike(["", "", ""], a); // Now 'a' is '[string, string, string]'
138+
139+
const b: unknown = { foo: "foo", bar: 0 };
140+
ensureLike({}, b); // Now 'b' is 'Record<string, unknown>'
141+
ensureLike({ foo: "", bar: 0 }, b); // Now 'b' is '{foo: string, bar: number}'
142+
```
143+
96144
## License
97145

98146
The code follows MIT license written in [LICENSE](./LICENSE). Contributors need

ensure.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
isArray,
33
isFunction,
4+
isLike,
45
isNone,
56
isNull,
67
isNumber,
@@ -100,3 +101,15 @@ export function ensureUndefined(x: unknown): asserts x is undefined {
100101
export function ensureNone(x: unknown): asserts x is null | undefined {
101102
return ensure(x, isNone, "The value must be null or undefined");
102103
}
104+
105+
/**
106+
* Ensure if `x` follows the reference by raising an `EnsureError` when it doesn't.
107+
*/
108+
export function ensureLike<R, T extends unknown>(
109+
ref: R,
110+
x: unknown,
111+
ipred?: Predicate<T>,
112+
): asserts x is R {
113+
const pred = (x: unknown): x is T[] => isLike(ref, x, ipred);
114+
return ensure(x, pred, "The value must follow the reference");
115+
}

ensure_test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ensure,
44
ensureArray,
55
ensureFunction,
6+
ensureLike,
67
ensureNone,
78
ensureNull,
89
ensureNumber,
@@ -141,3 +142,125 @@ Deno.test("ensureNone throws error on non null nor undefined", () => {
141142
assertThrows(() => ensureNone({}));
142143
assertThrows(() => ensureNone(function () {}));
143144
});
145+
146+
Deno.test("ensureLike does it's job on string", () => {
147+
const ref = "";
148+
ensureLike(ref, "Hello");
149+
150+
assertThrows(() => ensureLike(ref, 0));
151+
assertThrows(() => ensureLike(ref, []));
152+
assertThrows(() => ensureLike(ref, {}));
153+
assertThrows(() => ensureLike(ref, function () {}));
154+
assertThrows(() => ensureLike(ref, undefined));
155+
assertThrows(() => ensureLike(ref, null));
156+
});
157+
Deno.test("ensureLike does it's job on number", () => {
158+
const ref = 0;
159+
ensureLike(ref, 0);
160+
ensureLike(ref, 1);
161+
ensureLike(ref, 0.1);
162+
163+
assertThrows(() => ensureLike(ref, "a"));
164+
assertThrows(() => ensureLike(ref, []));
165+
assertThrows(() => ensureLike(ref, {}));
166+
assertThrows(() => ensureLike(ref, function () {}));
167+
assertThrows(() => ensureLike(ref, undefined));
168+
assertThrows(() => ensureLike(ref, null));
169+
});
170+
Deno.test("ensureLike does it's job on array", () => {
171+
const ref: unknown[] = [];
172+
ensureLike(ref, []);
173+
ensureLike(ref, [0, 1, 2]);
174+
ensureLike(ref, ["a", "b", "c"]);
175+
176+
assertThrows(() => ensureLike(ref, "a"));
177+
assertThrows(() => ensureLike(ref, 0));
178+
assertThrows(() => ensureLike(ref, {}));
179+
assertThrows(() => ensureLike(ref, function () {}));
180+
assertThrows(() => ensureLike(ref, undefined));
181+
assertThrows(() => ensureLike(ref, null));
182+
});
183+
Deno.test("ensureLike does it's job on T array", () => {
184+
const ref: unknown[] = [];
185+
ensureLike(ref, [0, 1, 2], isNumber);
186+
ensureLike(ref, ["a", "b", "c"], isString);
187+
188+
assertThrows(() => ensureLike(ref, [0, 1, 2], isString));
189+
assertThrows(() => ensureLike(ref, ["a", "b", "c"], isNumber));
190+
});
191+
Deno.test("ensureLike does it's job on tuple", () => {
192+
const ref = ["", 0, ""];
193+
ensureLike(ref, ["", 0, ""]);
194+
ensureLike(ref, ["Hello", 100, "World"]);
195+
196+
assertThrows(() => ensureLike(ref, ["Hello", 100, "World", "foo"]));
197+
assertThrows(() => ensureLike(ref, [0, 0, 0]));
198+
assertThrows(() => ensureLike(ref, ["", "", ""]));
199+
assertThrows(() => ensureLike(ref, [0, "", 0]));
200+
});
201+
Deno.test("ensureLike does it's job on object", () => {
202+
const ref = {};
203+
ensureLike(ref, {});
204+
ensureLike(ref, { a: 0 });
205+
ensureLike(ref, { a: "a" });
206+
207+
assertThrows(() => ensureLike(ref, "a"));
208+
assertThrows(() => ensureLike(ref, 0));
209+
assertThrows(() => ensureLike(ref, []));
210+
assertThrows(() => ensureLike(ref, function () {}));
211+
assertThrows(() => ensureLike(ref, undefined));
212+
assertThrows(() => ensureLike(ref, null));
213+
});
214+
Deno.test("ensureLike does it's job on T object", () => {
215+
const ref = {};
216+
ensureLike(ref, { a: 0 }, isNumber);
217+
ensureLike(ref, { a: "a" }, isString);
218+
219+
assertThrows(() => ensureLike(ref, { a: 0 }, isString));
220+
assertThrows(() => ensureLike(ref, { a: "a" }, isNumber));
221+
});
222+
Deno.test("ensureLike does it's job on struct", () => {
223+
const ref = { foo: "", bar: 0 };
224+
ensureLike(ref, { foo: "", bar: 0 });
225+
ensureLike(ref, { foo: "", bar: 0, hoge: "" });
226+
227+
assertThrows(() => ensureLike(ref, {}));
228+
assertThrows(() => ensureLike(ref, { foo: "" }));
229+
assertThrows(() => ensureLike(ref, { bar: 0 }));
230+
});
231+
Deno.test("ensureLike does it's job on function", () => {
232+
const ref = () => {};
233+
ensureLike(ref, ensureFunction);
234+
ensureLike(ref, function () {});
235+
ensureLike(ref, () => {});
236+
ensureLike(ref, setTimeout);
237+
238+
assertThrows(() => ensureLike(ref, "a"));
239+
assertThrows(() => ensureLike(ref, 0));
240+
assertThrows(() => ensureLike(ref, []));
241+
assertThrows(() => ensureLike(ref, {}));
242+
assertThrows(() => ensureLike(ref, undefined));
243+
assertThrows(() => ensureLike(ref, null));
244+
});
245+
Deno.test("ensureLike does it's job on null", () => {
246+
const ref = null;
247+
ensureLike(ref, null);
248+
249+
assertThrows(() => ensureLike(ref, "a"));
250+
assertThrows(() => ensureLike(ref, 0));
251+
assertThrows(() => ensureLike(ref, []));
252+
assertThrows(() => ensureLike(ref, {}));
253+
assertThrows(() => ensureLike(ref, function () {}));
254+
assertThrows(() => ensureLike(ref, undefined));
255+
});
256+
Deno.test("ensureLike does it's job on undefined", () => {
257+
const ref = undefined;
258+
ensureLike(ref, undefined);
259+
260+
assertThrows(() => ensureLike(ref, "a"));
261+
assertThrows(() => ensureLike(ref, 0));
262+
assertThrows(() => ensureLike(ref, []));
263+
assertThrows(() => ensureLike(ref, {}));
264+
assertThrows(() => ensureLike(ref, function () {}));
265+
assertThrows(() => ensureLike(ref, null));
266+
});

is.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,39 @@ export function isUndefined(x: unknown): x is undefined {
6666
export function isNone(x: unknown): x is null | undefined {
6767
return x == null;
6868
}
69+
70+
/**
71+
* Return true if a type of value is like a type of reference.
72+
*/
73+
export function isLike<R, T extends unknown>(
74+
ref: R,
75+
x: unknown,
76+
pred?: Predicate<T>,
77+
): x is R {
78+
if (isString(ref) && isString(x)) {
79+
return true;
80+
}
81+
if (isNumber(ref) && isNumber(x)) {
82+
return true;
83+
}
84+
if (isArray(ref, pred) && isArray(x, pred)) {
85+
return ref.length === 0 || (
86+
ref.length === x.length &&
87+
ref.every((r, i) => isLike(r, x[i]))
88+
);
89+
}
90+
if (isObject(ref, pred) && isObject(x, pred)) {
91+
const es = Object.entries(ref);
92+
return es.length === 0 || es.every(([k, v]) => isLike(v, x[k]));
93+
}
94+
if (isFunction(ref) && isFunction(x)) {
95+
return true;
96+
}
97+
if (isNull(ref) && isNull(x)) {
98+
return true;
99+
}
100+
if (isUndefined(ref) && isUndefined(x)) {
101+
return true;
102+
}
103+
return false;
104+
}

0 commit comments

Comments
 (0)