-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
value-types.ts
90 lines (80 loc) · 2.45 KB
/
value-types.ts
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
import { ValueType } from './types.ts'
import { ok, err } from './utils.ts'
import { NotANumber, NotAnInteger, InvalidChoice } from './value-errors.ts'
const sharedProps = (typeName: string) => ({
[Symbol.toStringTag]: typeName,
})
/** Type and parser of text (string) value */
export const Text: ValueType<string, readonly [string]> = {
extract: ([raw]) => ok(raw),
getTypeName: () => 'text',
...sharedProps('Text'),
}
/** Type and parser of all number values except NaN and Infinity */
export const FiniteNumber: ValueType<number, readonly [string]> = {
extract([raw]) {
const value = Number(raw)
return isFinite(value) ? ok(value) : err(new NotANumber(raw))
},
getTypeName: () => 'number',
...sharedProps('FiniteNumber'),
}
/** Type and parser of all BigInt values */
export const Integer: ValueType<bigint, readonly [string]> = {
extract([raw]) {
try {
return ok(BigInt(raw))
} catch (error) {
return err(new NotAnInteger(raw, error))
}
},
getTypeName: () => 'integer',
...sharedProps('Integer'),
}
/**
* Create type and parser of choice (union)
* @template Value Union type of choices to make
* @param choices Choices to make
* @returns Type and parser of choices
*/
export function Choice<
Value extends number | string,
>(
...choices: {
readonly value: Value
readonly describe?: string
}[]
): ValueType<Value, readonly [string]> {
const values = choices.map(x => x.value)
const valueStrings = values.map(x => String(x))
{ // check for duplication
const duplications = valueStrings.filter((x, i) => valueStrings.indexOf(x) !== i)
if (duplications.length) {
throw new RangeError(`Duplicated choices: ${duplications.join(' ')}`)
}
}
{ // check for invalid numbers
const invalidNumbers = values.filter(x => typeof x === 'number' && !isFinite(x))
if (invalidNumbers.length) {
throw new RangeError(`Invalid numbers: ${invalidNumbers.join(' ')}`)
}
}
return {
extract([raw]) {
for (const value of values) {
if (value === raw || value === Number(raw)) return ok(value)
}
return err(new InvalidChoice(raw, values))
},
getTypeName: () => 'choice',
help() {
let text = ''
for (const { value, describe } of choices) {
const suffix = describe ? `${value}: ${describe}` : String(value)
text += '‣ ' + suffix + '\n'
}
return text.trim()
},
...sharedProps(`Choice(${values.join(',')})`),
}
}