schema-shield 0.0.6 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -65
- package/dist/formats.d.ts.map +1 -1
- package/dist/index.d.ts +25 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1837 -484
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1837 -484
- package/dist/keywords/array-keywords.d.ts.map +1 -1
- package/dist/keywords/object-keywords.d.ts.map +1 -1
- package/dist/keywords/other-keywords.d.ts.map +1 -1
- package/dist/keywords/string-keywords.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/deep-freeze.d.ts +5 -0
- package/dist/utils/deep-freeze.d.ts.map +1 -0
- package/dist/utils/has-changed.d.ts +2 -0
- package/dist/utils/has-changed.d.ts.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/{utils.d.ts → utils/main-utils.d.ts} +7 -9
- package/dist/utils/main-utils.d.ts.map +1 -0
- package/dist/utils/pattern-matcher.d.ts +3 -0
- package/dist/utils/pattern-matcher.d.ts.map +1 -0
- package/lib/formats.ts +468 -155
- package/lib/index.ts +702 -107
- package/lib/keywords/array-keywords.ts +260 -52
- package/lib/keywords/number-keywords.ts +1 -1
- package/lib/keywords/object-keywords.ts +295 -88
- package/lib/keywords/other-keywords.ts +263 -70
- package/lib/keywords/string-keywords.ts +123 -7
- package/lib/types.ts +5 -18
- package/lib/utils/deep-freeze.ts +208 -0
- package/lib/utils/has-changed.ts +51 -0
- package/lib/utils/index.ts +4 -0
- package/lib/{utils.ts → utils/main-utils.ts} +63 -77
- package/lib/utils/pattern-matcher.ts +66 -0
- package/package.json +2 -2
- package/tsconfig.json +4 -4
- package/dist/utils.d.ts.map +0 -1
package/lib/formats.ts
CHANGED
|
@@ -1,194 +1,498 @@
|
|
|
1
1
|
import { FormatFunction } from "./index";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
// Regex helpers
|
|
4
|
+
const UUID_REGEX =
|
|
5
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
|
+
// ISO 8601 Duration (P3Y6M4DT12H30M5S)
|
|
7
|
+
const DURATION_REGEX =
|
|
8
|
+
/^P(?!$)((\d+Y)?(\d+M)?(\d+W)?(\d+D)?)(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
|
|
9
|
+
const URI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/;
|
|
10
|
+
const EMAIL_REGEX =
|
|
11
|
+
/^(?!\.)(?!.*\.$)[a-z0-9!#$%&'*+/=?^_`{|}~-]{1,20}(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]{1,21}){0,2}@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,60}[a-z0-9])?){0,3}$/i;
|
|
12
|
+
const HOSTNAME_REGEX =
|
|
13
|
+
/^[a-z0-9][a-z0-9-]{0,62}(?:\.[a-z0-9][a-z0-9-]{0,62})*[a-z0-9]$/i;
|
|
14
|
+
const DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
15
|
+
const TIME_REGEX =
|
|
16
|
+
/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(\.\d+)?(Z|([+-])([01]\d|2[0-3]):([0-5]\d))$/;
|
|
17
|
+
const URI_REFERENCE_REGEX =
|
|
18
|
+
/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#((?![^#]*\\)[^#]*))?/i;
|
|
19
|
+
const IRI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/;
|
|
20
|
+
const IRI_REFERENCE_REGEX =
|
|
21
|
+
/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#((?![^#]*\\)[^#]*))?/i;
|
|
22
|
+
const IDN_EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
23
|
+
const IDN_HOSTNAME_REGEX = /^[^\s!@#$%^&*()_+\=\[\]{};':"\\|,<>\/?]+$/;
|
|
24
|
+
|
|
25
|
+
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
26
|
+
|
|
27
|
+
function isDigitCharCode(code: number) {
|
|
28
|
+
return code >= 48 && code <= 57;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseTwoDigits(data: string, index: number) {
|
|
32
|
+
const first = data.charCodeAt(index) - 48;
|
|
33
|
+
const second = data.charCodeAt(index + 1) - 48;
|
|
34
|
+
|
|
35
|
+
if (first < 0 || first > 9 || second < 0 || second > 9) {
|
|
36
|
+
return -1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return first * 10 + second;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseFourDigits(data: string, index: number) {
|
|
43
|
+
const a = data.charCodeAt(index) - 48;
|
|
44
|
+
const b = data.charCodeAt(index + 1) - 48;
|
|
45
|
+
const c = data.charCodeAt(index + 2) - 48;
|
|
46
|
+
const d = data.charCodeAt(index + 3) - 48;
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
a < 0 ||
|
|
50
|
+
a > 9 ||
|
|
51
|
+
b < 0 ||
|
|
52
|
+
b > 9 ||
|
|
53
|
+
c < 0 ||
|
|
54
|
+
c > 9 ||
|
|
55
|
+
d < 0 ||
|
|
56
|
+
d > 9
|
|
57
|
+
) {
|
|
58
|
+
return -1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return a * 1000 + b * 100 + c * 10 + d;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isValidIpv4Range(data: string, start: number, end: number) {
|
|
65
|
+
let segmentCount = 0;
|
|
66
|
+
let segmentStart = start;
|
|
67
|
+
|
|
68
|
+
for (let i = start; i <= end; i++) {
|
|
69
|
+
if (i !== end && data.charCodeAt(i) !== 46) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
8
72
|
|
|
9
|
-
|
|
73
|
+
const segmentLength = i - segmentStart;
|
|
74
|
+
if (segmentLength < 1 || segmentLength > 3) {
|
|
10
75
|
return false;
|
|
11
76
|
}
|
|
12
77
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (match[2] === "02" && day > 29) {
|
|
78
|
+
if (segmentLength > 1 && data.charCodeAt(segmentStart) === 48) {
|
|
16
79
|
return false;
|
|
17
80
|
}
|
|
18
81
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
minuteStr,
|
|
26
|
-
secondStr,
|
|
27
|
-
timezoneSign,
|
|
28
|
-
timezoneHourStr,
|
|
29
|
-
timezoneMinuteStr
|
|
30
|
-
] = match;
|
|
82
|
+
let value = 0;
|
|
83
|
+
for (let j = segmentStart; j < i; j++) {
|
|
84
|
+
const digit = data.charCodeAt(j) - 48;
|
|
85
|
+
if (digit < 0 || digit > 9) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
31
88
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let hour = Number(hourStr);
|
|
35
|
-
let minute = Number(minuteStr);
|
|
36
|
-
let second = Number(secondStr);
|
|
89
|
+
value = value * 10 + digit;
|
|
90
|
+
}
|
|
37
91
|
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
92
|
+
if (value > 255) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
41
95
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
96
|
+
segmentCount++;
|
|
97
|
+
segmentStart = i + 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return segmentCount === 4;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isValidIpv4(data: string) {
|
|
104
|
+
return isValidIpv4Range(data, 0, data.length);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isHexCharCode(code: number) {
|
|
108
|
+
return (
|
|
109
|
+
(code >= 48 && code <= 57) ||
|
|
110
|
+
(code >= 65 && code <= 70) ||
|
|
111
|
+
(code >= 97 && code <= 102)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isValidIpv6(data: string) {
|
|
116
|
+
const length = data.length;
|
|
117
|
+
if (length === 0) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let hasColon = false;
|
|
122
|
+
let hasDoubleColon = false;
|
|
123
|
+
let hextetCount = 0;
|
|
124
|
+
let i = 0;
|
|
125
|
+
|
|
126
|
+
while (i < length) {
|
|
127
|
+
if (data.charCodeAt(i) === 58) {
|
|
128
|
+
hasColon = true;
|
|
129
|
+
|
|
130
|
+
if (i + 1 < length && data.charCodeAt(i + 1) === 58) {
|
|
131
|
+
if (hasDoubleColon) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
hasDoubleColon = true;
|
|
136
|
+
i += 2;
|
|
137
|
+
|
|
138
|
+
if (i === length) {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
continue;
|
|
48
143
|
}
|
|
49
144
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const segmentStart = i;
|
|
149
|
+
let segmentLength = 0;
|
|
150
|
+
|
|
151
|
+
while (i < length && isHexCharCode(data.charCodeAt(i))) {
|
|
152
|
+
segmentLength++;
|
|
153
|
+
if (segmentLength > 4) {
|
|
154
|
+
return false;
|
|
56
155
|
}
|
|
57
156
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (segmentLength === 0) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (i < length && data.charCodeAt(i) === 46) {
|
|
165
|
+
if (!hasColon) {
|
|
166
|
+
return false;
|
|
64
167
|
}
|
|
65
168
|
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
day -= 31;
|
|
69
|
-
} else if (day < 1) {
|
|
70
|
-
month -= 1;
|
|
71
|
-
day += 31;
|
|
169
|
+
if (!isValidIpv4Range(data, segmentStart, length)) {
|
|
170
|
+
return false;
|
|
72
171
|
}
|
|
73
172
|
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
month -= 12;
|
|
77
|
-
} else if (month < 1) {
|
|
78
|
-
year -= 1;
|
|
79
|
-
month += 12;
|
|
173
|
+
if (hasDoubleColon) {
|
|
174
|
+
return hextetCount < 6;
|
|
80
175
|
}
|
|
81
176
|
|
|
82
|
-
|
|
177
|
+
return hextetCount === 6;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
hextetCount++;
|
|
181
|
+
if (hextetCount > 8) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (i === length) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (data.charCodeAt(i) !== 58) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
hasColon = true;
|
|
194
|
+
i++;
|
|
195
|
+
|
|
196
|
+
if (i === length) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (data.charCodeAt(i) === 58) {
|
|
201
|
+
if (hasDoubleColon) {
|
|
83
202
|
return false;
|
|
84
203
|
}
|
|
204
|
+
|
|
205
|
+
hasDoubleColon = true;
|
|
206
|
+
i++;
|
|
207
|
+
|
|
208
|
+
if (i === length) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
85
211
|
}
|
|
212
|
+
}
|
|
86
213
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
? year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
|
91
|
-
? 29
|
|
92
|
-
: 28
|
|
93
|
-
: daysInMonth[month - 1];
|
|
214
|
+
if (!hasColon) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
94
217
|
|
|
95
|
-
|
|
96
|
-
|
|
218
|
+
if (hasDoubleColon) {
|
|
219
|
+
return hextetCount < 8;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return hextetCount === 8;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function isValidJsonPointer(data: string) {
|
|
226
|
+
if (data === "") {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (data.charCodeAt(0) !== 47) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (let i = 1; i < data.length; i++) {
|
|
235
|
+
if (data.charCodeAt(i) !== 126) {
|
|
236
|
+
continue;
|
|
97
237
|
}
|
|
98
238
|
|
|
99
|
-
|
|
100
|
-
if (
|
|
239
|
+
const next = data.charCodeAt(i + 1);
|
|
240
|
+
if (next !== 48 && next !== 49) {
|
|
101
241
|
return false;
|
|
102
242
|
}
|
|
103
243
|
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isValidRelativeJsonPointer(data: string) {
|
|
251
|
+
if (data.length === 0) {
|
|
104
252
|
return true;
|
|
105
|
-
}
|
|
106
|
-
uri(data) {
|
|
107
|
-
return /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/.test(data);
|
|
108
|
-
},
|
|
109
|
-
email(data) {
|
|
110
|
-
return /^(?!\.)(?!.*\.$)[a-z0-9!#$%&'*+/=?^_`{|}~-]{1,20}(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]{1,21}){0,2}@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,60}[a-z0-9])?){0,3}$/i.test(
|
|
111
|
-
data
|
|
112
|
-
);
|
|
113
|
-
},
|
|
114
|
-
ipv4(data) {
|
|
115
|
-
// Matches a string formed by 4 numbers between 0 and 255 separated by dots without leading zeros
|
|
116
|
-
// /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])$/
|
|
117
|
-
return /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])$/.test(
|
|
118
|
-
data
|
|
119
|
-
);
|
|
120
|
-
},
|
|
253
|
+
}
|
|
121
254
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
255
|
+
let i = 0;
|
|
256
|
+
while (i < data.length) {
|
|
257
|
+
const code = data.charCodeAt(i);
|
|
258
|
+
if (code < 48 || code > 57) {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
i++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (i === 0) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (i === data.length) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (data.charCodeAt(i) === 35) {
|
|
273
|
+
return i + 1 === data.length;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (data.charCodeAt(i) !== 47) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (i = i + 1; i < data.length; i++) {
|
|
281
|
+
if (data.charCodeAt(i) !== 126) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const next = data.charCodeAt(i + 1);
|
|
286
|
+
if (next !== 48 && next !== 49) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
i++;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function isValidUriTemplate(data: string) {
|
|
297
|
+
for (let i = 0; i < data.length; i++) {
|
|
298
|
+
const code = data.charCodeAt(i);
|
|
299
|
+
|
|
300
|
+
if (code === 125) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (code !== 123) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const closeIndex = data.indexOf("}", i + 1);
|
|
309
|
+
if (closeIndex === -1 || closeIndex === i + 1) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
i = closeIndex;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export const Formats: Record<string, FormatFunction | false> = {
|
|
320
|
+
["date-time"](data) {
|
|
321
|
+
const length = data.length;
|
|
322
|
+
if (length < 19) {
|
|
323
|
+
return false;
|
|
126
324
|
}
|
|
127
325
|
|
|
128
326
|
if (
|
|
129
|
-
data.
|
|
130
|
-
|
|
327
|
+
data.charCodeAt(4) !== 45 ||
|
|
328
|
+
data.charCodeAt(7) !== 45 ||
|
|
329
|
+
data.charCodeAt(13) !== 58 ||
|
|
330
|
+
data.charCodeAt(16) !== 58
|
|
131
331
|
) {
|
|
132
332
|
return false;
|
|
133
333
|
}
|
|
134
334
|
|
|
135
|
-
const
|
|
136
|
-
|
|
335
|
+
const tCode = data.charCodeAt(10);
|
|
336
|
+
if (tCode !== 84 && tCode !== 116) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
137
339
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
340
|
+
const year = parseFourDigits(data, 0);
|
|
341
|
+
const month = parseTwoDigits(data, 5);
|
|
342
|
+
const day = parseTwoDigits(data, 8);
|
|
343
|
+
const hour = parseTwoDigits(data, 11);
|
|
344
|
+
const minute = parseTwoDigits(data, 14);
|
|
345
|
+
const second = parseTwoDigits(data, 17);
|
|
346
|
+
|
|
347
|
+
if (
|
|
348
|
+
year < 0 ||
|
|
349
|
+
month < 0 ||
|
|
350
|
+
day < 0 ||
|
|
351
|
+
hour < 0 ||
|
|
352
|
+
minute < 0 ||
|
|
353
|
+
second < 0
|
|
354
|
+
) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (hour > 23 || minute > 59 || second > 60) {
|
|
359
|
+
return false;
|
|
148
360
|
}
|
|
149
361
|
|
|
150
|
-
|
|
151
|
-
|
|
362
|
+
let cursor = 19;
|
|
363
|
+
let offsetSign: "+" | "-" | null = null;
|
|
364
|
+
let offsetHour = 0;
|
|
365
|
+
let offsetMinute = 0;
|
|
366
|
+
|
|
367
|
+
if (cursor < length && data.charCodeAt(cursor) === 46) {
|
|
368
|
+
cursor++;
|
|
369
|
+
const fracStart = cursor;
|
|
370
|
+
while (cursor < length && isDigitCharCode(data.charCodeAt(cursor))) {
|
|
371
|
+
cursor++;
|
|
372
|
+
}
|
|
152
373
|
|
|
153
|
-
|
|
154
|
-
if (ipv6Part.split("::").length - 1 > 1) {
|
|
374
|
+
if (cursor === fracStart) {
|
|
155
375
|
return false;
|
|
156
376
|
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (cursor < length) {
|
|
380
|
+
const tzCode = data.charCodeAt(cursor);
|
|
381
|
+
|
|
382
|
+
if (tzCode === 90 || tzCode === 122) {
|
|
383
|
+
cursor++;
|
|
384
|
+
} else if (tzCode === 43 || tzCode === 45) {
|
|
385
|
+
offsetSign = tzCode === 43 ? "+" : "-";
|
|
386
|
+
|
|
387
|
+
if (cursor + 6 > length || data.charCodeAt(cursor + 3) !== 58) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
offsetHour = parseTwoDigits(data, cursor + 1);
|
|
392
|
+
offsetMinute = parseTwoDigits(data, cursor + 4);
|
|
157
393
|
|
|
158
|
-
|
|
394
|
+
if (
|
|
395
|
+
offsetHour < 0 ||
|
|
396
|
+
offsetMinute < 0 ||
|
|
397
|
+
offsetHour > 23 ||
|
|
398
|
+
offsetMinute > 59
|
|
399
|
+
) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
cursor += 6;
|
|
404
|
+
} else {
|
|
159
405
|
return false;
|
|
160
406
|
}
|
|
407
|
+
}
|
|
161
408
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
);
|
|
409
|
+
if (cursor !== length) {
|
|
410
|
+
return false;
|
|
165
411
|
}
|
|
166
412
|
|
|
167
|
-
|
|
168
|
-
|
|
413
|
+
// Mes 1–12
|
|
414
|
+
if (month < 1 || month > 12) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
// Día >= 1
|
|
418
|
+
if (day < 1) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
169
421
|
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
422
|
+
const maxDays =
|
|
423
|
+
month === 2
|
|
424
|
+
? year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
|
425
|
+
? 29
|
|
426
|
+
: 28
|
|
427
|
+
: DAYS_IN_MONTH[month - 1];
|
|
428
|
+
|
|
429
|
+
if (!maxDays || day > maxDays) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (second === 60) {
|
|
434
|
+
let utcTotalMinutes = hour * 60 + minute;
|
|
435
|
+
|
|
436
|
+
if (offsetSign) {
|
|
437
|
+
const offsetTotalMinutes = offsetHour * 60 + offsetMinute;
|
|
438
|
+
utcTotalMinutes +=
|
|
439
|
+
offsetSign === "+" ? -offsetTotalMinutes : offsetTotalMinutes;
|
|
440
|
+
utcTotalMinutes %= 24 * 60;
|
|
441
|
+
if (utcTotalMinutes < 0) {
|
|
442
|
+
utcTotalMinutes += 24 * 60;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
173
445
|
|
|
174
|
-
|
|
175
|
-
|
|
446
|
+
if (utcTotalMinutes !== 23 * 60 + 59) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
176
449
|
}
|
|
177
450
|
|
|
178
|
-
return
|
|
451
|
+
return true;
|
|
452
|
+
},
|
|
453
|
+
uri(data) {
|
|
454
|
+
return URI_REGEX.test(data);
|
|
455
|
+
},
|
|
456
|
+
email(data) {
|
|
457
|
+
return EMAIL_REGEX.test(data);
|
|
458
|
+
},
|
|
459
|
+
ipv4(data) {
|
|
460
|
+
return isValidIpv4(data);
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
ipv6(data) {
|
|
464
|
+
return isValidIpv6(data);
|
|
179
465
|
},
|
|
180
466
|
|
|
181
467
|
hostname(data) {
|
|
182
|
-
return
|
|
183
|
-
data
|
|
184
|
-
);
|
|
468
|
+
return HOSTNAME_REGEX.test(data);
|
|
185
469
|
},
|
|
186
470
|
date(data) {
|
|
187
|
-
|
|
471
|
+
const match = DATE_REGEX.exec(data);
|
|
472
|
+
if (!match) {
|
|
188
473
|
return false;
|
|
189
474
|
}
|
|
190
475
|
|
|
191
|
-
|
|
476
|
+
const [, yearStr, monthStr, dayStr] = match;
|
|
477
|
+
const year = Number(yearStr);
|
|
478
|
+
const month = Number(monthStr);
|
|
479
|
+
const day = Number(dayStr);
|
|
480
|
+
|
|
481
|
+
if (month < 1 || month > 12) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
if (day < 1) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const maxDays =
|
|
489
|
+
month === 2
|
|
490
|
+
? year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
|
491
|
+
? 29
|
|
492
|
+
: 28
|
|
493
|
+
: DAYS_IN_MONTH[month - 1];
|
|
494
|
+
|
|
495
|
+
return !!maxDays && day <= maxDays;
|
|
192
496
|
},
|
|
193
497
|
regex(data) {
|
|
194
498
|
try {
|
|
@@ -199,44 +503,53 @@ export const Formats: Record<string, FormatFunction | false> = {
|
|
|
199
503
|
}
|
|
200
504
|
},
|
|
201
505
|
"json-pointer"(data) {
|
|
202
|
-
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return /^\/(?:[^~]|~0|~1)*$/.test(data);
|
|
506
|
+
return isValidJsonPointer(data);
|
|
207
507
|
},
|
|
208
508
|
"relative-json-pointer"(data) {
|
|
209
|
-
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/.test(data);
|
|
509
|
+
return isValidRelativeJsonPointer(data);
|
|
214
510
|
},
|
|
215
511
|
time(data) {
|
|
216
|
-
return
|
|
217
|
-
data
|
|
218
|
-
);
|
|
512
|
+
return TIME_REGEX.test(data);
|
|
219
513
|
},
|
|
220
514
|
"uri-reference"(data) {
|
|
221
|
-
if (
|
|
515
|
+
if (data.includes("\\")) {
|
|
222
516
|
return false;
|
|
223
517
|
}
|
|
224
518
|
|
|
225
|
-
return
|
|
226
|
-
data
|
|
227
|
-
);
|
|
519
|
+
return URI_REFERENCE_REGEX.test(data);
|
|
228
520
|
},
|
|
521
|
+
|
|
229
522
|
"uri-template"(data) {
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
uuid
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
523
|
+
return isValidUriTemplate(data);
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
duration(data) {
|
|
527
|
+
return DURATION_REGEX.test(data);
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
uuid(data) {
|
|
531
|
+
return UUID_REGEX.test(data);
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
// IRI is like URI but allows Unicode. We reuse a permissive logic.
|
|
535
|
+
iri(data) {
|
|
536
|
+
return IRI_REGEX.test(data);
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
"iri-reference"(data) {
|
|
540
|
+
if (data.includes("\\")) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
return IRI_REFERENCE_REGEX.test(data);
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// Best-effort structural validation for IDN (no punycode/tables)
|
|
547
|
+
"idn-email"(data) {
|
|
548
|
+
return IDN_EMAIL_REGEX.test(data);
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
"idn-hostname"(data) {
|
|
552
|
+
// Allows unicode, forbids spaces and typical invalid URL chars
|
|
553
|
+
return IDN_HOSTNAME_REGEX.test(data);
|
|
554
|
+
}
|
|
242
555
|
};
|