generated from sapphiredev/sapphire-template
/
StringConstraints.ts
160 lines (137 loc) · 5.33 KB
/
StringConstraints.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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { isIP, isIPv4, isIPv6 } from 'node:net';
import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError';
import { Result } from '../lib/Result';
import type { IConstraint } from './base/IConstraint';
import { validateEmail } from './util/emailValidator';
import { Comparator, equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual } from './util/operators';
import { createUrlValidators } from './util/urlValidators';
export type StringConstraintName =
| `s.string.${
| `length${'LessThan' | 'LessThanOrEqual' | 'GreaterThan' | 'GreaterThanOrEqual' | 'Equal' | 'NotEqual'}`
| 'regex'
| 'url'
| 'uuid'
| 'email'
| `ip${'v4' | 'v6' | ''}`
| 'date'}`;
export type StringProtocol = `${string}:`;
export type StringDomain = `${string}.${string}`;
export interface UrlOptions {
allowedProtocols?: StringProtocol[];
allowedDomains?: StringDomain[];
}
export type UUIDVersion = 1 | 3 | 4 | 5;
export interface StringUuidOptions {
version?: UUIDVersion | `${UUIDVersion}-${UUIDVersion}` | null;
nullable?: boolean;
}
function stringLengthComparator(comparator: Comparator, name: StringConstraintName, expected: string, length: number): IConstraint<string> {
return {
run(input: string) {
return comparator(input.length, length) //
? Result.ok(input)
: Result.err(new ExpectedConstraintError(name, 'Invalid string length', input, expected));
}
};
}
export function stringLengthLessThan(length: number): IConstraint<string> {
const expected = `expected.length < ${length}`;
return stringLengthComparator(lessThan, 's.string.lengthLessThan', expected, length);
}
export function stringLengthLessThanOrEqual(length: number): IConstraint<string> {
const expected = `expected.length <= ${length}`;
return stringLengthComparator(lessThanOrEqual, 's.string.lengthLessThanOrEqual', expected, length);
}
export function stringLengthGreaterThan(length: number): IConstraint<string> {
const expected = `expected.length > ${length}`;
return stringLengthComparator(greaterThan, 's.string.lengthGreaterThan', expected, length);
}
export function stringLengthGreaterThanOrEqual(length: number): IConstraint<string> {
const expected = `expected.length >= ${length}`;
return stringLengthComparator(greaterThanOrEqual, 's.string.lengthGreaterThanOrEqual', expected, length);
}
export function stringLengthEqual(length: number): IConstraint<string> {
const expected = `expected.length === ${length}`;
return stringLengthComparator(equal, 's.string.lengthEqual', expected, length);
}
export function stringLengthNotEqual(length: number): IConstraint<string> {
const expected = `expected.length !== ${length}`;
return stringLengthComparator(notEqual, 's.string.lengthNotEqual', expected, length);
}
export function stringEmail(): IConstraint<string> {
return {
run(input: string) {
return validateEmail(input)
? Result.ok(input)
: Result.err(new ExpectedConstraintError('s.string.email', 'Invalid email address', input, 'expected to be an email address'));
}
};
}
function stringRegexValidator(type: StringConstraintName, expected: string, regex: RegExp): IConstraint<string> {
return {
run(input: string) {
return regex.test(input) //
? Result.ok(input)
: Result.err(new ExpectedConstraintError(type, 'Invalid string format', input, expected));
}
};
}
export function stringUrl(options?: UrlOptions): IConstraint<string> {
const validatorFn = createUrlValidators(options);
return {
run(input: string) {
let url: URL;
try {
url = new URL(input);
} catch {
return Result.err(new ExpectedConstraintError('s.string.url', 'Invalid URL', input, 'expected to match an URL'));
}
const validatorFnResult = validatorFn(input, url);
if (validatorFnResult === null) return Result.ok(input);
return Result.err(validatorFnResult);
}
};
}
export function stringIp(version?: 4 | 6): IConstraint<string> {
const ipVersion = version ? (`v${version}` as const) : '';
const validatorFn = version === 4 ? isIPv4 : version === 6 ? isIPv6 : isIP;
const name = `s.string.ip${ipVersion}` as const;
const message = `Invalid IP${ipVersion} address`;
const expected = `expected to be an IP${ipVersion} address`;
return {
run(input: string) {
return validatorFn(input) ? Result.ok(input) : Result.err(new ExpectedConstraintError(name, message, input, expected));
}
};
}
export function stringRegex(regex: RegExp) {
return stringRegexValidator('s.string.regex', `expected ${regex}.test(expected) to be true`, regex);
}
export function stringUuid({ version = 4, nullable = false }: StringUuidOptions = {}) {
version ??= '1-5';
const regex = new RegExp(
`^(?:[0-9A-F]{8}-[0-9A-F]{4}-[${version}][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}${
nullable ? '|00000000-0000-0000-0000-000000000000' : ''
})$`,
'i'
);
const expected = `expected to match UUID${typeof version === 'number' ? `v${version}` : ` in range of ${version}`}`;
return stringRegexValidator('s.string.uuid', expected, regex);
}
export function stringDate(): IConstraint<string> {
return {
run(input: string) {
const time = Date.parse(input);
return Number.isNaN(time)
? Result.err(
new ExpectedConstraintError(
's.string.date',
'Invalid date string',
input,
'expected to be a valid date string (in the ISO 8601 or ECMA-262 format)'
)
)
: Result.ok(input);
}
};
}