-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
129 lines (113 loc) · 3.81 KB
/
Copy pathindex.ts
File metadata and controls
129 lines (113 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// # parse-ts
// Type-safe parsing of JSON-compatible runtime data.
// [View on github](https://github.com/jethrolarson/parse-ts)
import {some, none, Option, isSome, map} from 'fp-ts/lib/Option';
import {filterMap} from 'fp-ts/lib/Array';
import {flow} from 'fp-ts/lib/function';
/**
* Validator type
* Type of functions that validate `unknown` data. Returns Option from 'fp-ts'.
* @example
* const nan: Validator<NaN> = (x: unknown) => isNaN(x) ? some(x) : none;
*/
export type Validator<U = never> = (x: unknown) => Option<U>;
/**
* Validate string
* @example
* str('hi') // => Option<string>
*/
export const str: Validator<string> = (v) => (typeof v === 'string' ? some(v) : none);
/**
* Validate number
* @example
* num(1) // => Option<number>
*/
export const num: Validator<number> = (v) => (typeof v === 'number' ? some(v) : none);
/**
* Validate that value matches the passed one. Uses `Object.is`
* @example
* exactly('hi')('hi') // => Option<'hi'>
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export const exactly = <T>(val: T) => (v: any): Option<T> => (Object.is(v, val) ? some(v) : none);
/**
* Validate that value is null
* @example
* bool(null) // => Option<null>
*/
export const nil: Validator<null> = exactly(null);
/**
* Validate boolean
* @example
* bool(true) // => Option<boolean>
*/
export const bool: Validator<boolean> = (v) => (typeof v === 'boolean' ? some(v) : none);
/**
* Validate that value is false
* @example
* F(false) // Option<false>
*/
export const F: Validator<false> = exactly(false);
/**
* Validate the value is true
* @example
* T(true) // Option<true>
*/
export const T: Validator<true> = exactly(true);
/**
* Validates that all values of a potential array match the passed validator
* @example
* arrayOf(bool)([true, false]) //=> Option<bool[]>
*/
export const arrayOf = <T>(childValidator: Validator<T>) => (v: unknown): Option<T[]> => {
if (Array.isArray(v)) {
const cleaned = filterMap(childValidator)(v);
if (cleaned.length === v.length) {
return some(v);
}
}
return none;
};
/**
* A mapping of object keys to validators for it's values.
* Pass the desired interface as the generic to ensure validator is correct
* @example
* interface MyFace {foo: bool}
* const recSpec:RecordSpec<MyFace> = {foo: bool};
*/
export type RecordSpec<T = never> = {[K in keyof T]: Validator<T[K]>};
type ValidatedRecord<T> = {[K in keyof T]: Option<T[K]>};
const isPlainObject = (obj: unknown): obj is Record<string, unknown> =>
Object.prototype.toString.call(obj) === '[object Object]';
const hasOwnProperty = (k: string, v: Record<string, unknown>): v is Record<typeof k, unknown> =>
Object.prototype.hasOwnProperty.call(v, k);
const validateObj = <T>(mapping: RecordSpec<T>) => (v: Record<string, unknown>): ValidatedRecord<T> => {
const part: Partial<ValidatedRecord<T>> = {};
for (const k in mapping) {
part[k] = hasOwnProperty(k, v) ? mapping[k](v[k]) : none;
}
// Force after going over every key
return part as ValidatedRecord<T>;
};
const extractValidatedRecord = <T>(rec: ValidatedRecord<T>): Option<T> => {
const part: Partial<T> = {};
for (const k in rec) {
if (isSome(rec[k])) {
map((a: T[typeof k]) => {
part[k] = a;
})(rec[k]);
} else {
return none;
}
}
return some(part as T);
};
/**
* Validate that a potential object matches the passed spec.
* @example
* record({foo: bool})({foo: true}) // => Option<{foo: boolean}>
*/
export const record = <T>(mapping: RecordSpec<T>) => (v: unknown): Option<T> => {
if (!isPlainObject(v)) return none;
return flow(validateObj(mapping), extractValidatedRecord)(v);
};